 So welcome back, I would say we start and red light is on, so I think everything is recorded hopefully. So regarding last time, are there any questions? I think we started very easy, but maybe there are some open questions. Anything? I know I sent too many emails to you, but Moodle makes it very easy. Okay, then let's start. So I will continue where we left off, and we left off where we defined two variables that's like what we need to know. So the first one as we see here, A equals or is set to 1, 2, 3, and B is set to 42. So what that means in memory is of course that 1, 2, 3 is created as an integer. Once this is here, the name A is created, it points there, at the same time the number 42 as an integer is created, the name B is created, and it references the object. So that's what we always want to remember what goes on in memory and also the order by which you won't do this too many times because it's a bit tedious to always draw the memory diagram, but whenever something goes wrong, I promise you that usually if you draw the memory diagram, this will be the solution, this will show you what goes wrong. For now, what I want to do is I want to talk a little bit about some conceptual difference. So in particular, I want to talk about the difference between what is an expression and what is a statement because these two terms, they are rather important, and I think in the beginning, you know, the subtleties will not be super clear for a beginner, so I want to work them out a little bit. So the first code cell here, A, when I execute it, it gives me back a 1, 2, 3. So what happens is, I try to use another color here. In Jupyter, I ask Python, hey, what is A, and then Python goes ahead in the list of our names, it looks up A, it goes to the 1, 2, 3, it follows it, and then because it's Jupyter and this does not hold for all of Python, like if you work in the command line or in a script mode, which we will also see, this will not work like this, but in Jupyter Notebook, what happens is this 1, 2, 3 is given back, you know, somewhere. I would say it to us as a user, so that's why we say it. In other words, Jupyter Notebook reads out the memory. It doesn't do anything on the right-hand side. It just looks at it and reads it. And the same happens if I execute the next cell. So what the next cell, what the execution does is it creates a new 42, at least we have to assume it creates a new 42 object, and it does not make any name point at it, however, it gives it back to us. So the red I will always use to, this is just given back to Jupyter Notebook without a name, so we see it. But then immediately after we see it, what happens is there is no reference there, and Python does what is called reference counting. So whenever there is an object in memory that has no references there, then Python may decide to get rid of it, to give free the memory. And so this might sound trivial, but also we have to understand that Python does this for us. If we were to program in another language, let's say, for example, C or Java, then we would have to do this on our own. And this is one reason why Python is so easy to learn in the beginning, because Python does that for us. There is a cost, however. We have no real influence what goes on in memory. And because of that, Python may be slower. It may use more memory than necessary, because it doesn't clean up the memory right away. So that may be a downside. And when you go on the internet and you read critiques about Python, so what is Python good at? What is Python bad at? Then many people will say Python is slow, and Python might need too much memory. And the reason why it needs too much memory is because it does the memory management for us. We don't have to do a lot with it. And then to continue the idea to work out the difference between what is an expression and what is a statement, let's look at the next cell and execute it. Let's add A plus B, two numbers. So what this does is Python goes ahead and it tries to look up A and B, finds it, it knows the two numbers, it knows where they are, and then it will create a new int object. And this should be 165. It's an object of type int. And then this is, again, given back to us. We see it, we see the output. And then shortly after, most likely, it will be gone again in memory. And so what I want you to focus on is that both the line above, where we just entered 42, but also now A plus B, they did something in memory. They created a new object in memory. However, after the cell was executed, we have no way to reference this object, which means from our point of view, when we look at the entire program or at the entire memory, we could say the state of the program didn't change. Or in other words, how can we know that something in memory is different if we have no way to find out? Because there is no reference going into the memory to this object. We don't know if it's there or not, we just can't tell. And whenever you have this type of behavior, we say that's an expression. So an expression might do something in memory, but we can't see it. It has no side effect. That's what when I write in the materials, there is no permanent side effect. That's what I mean, right? So something happens in memory, but we just can't see it after the fact. So expression does not mean nothing happens in memory. Something can happen, but we just don't see it. And so that's a subtle difference here. And then of course, expressions can be more complex. We can take the A plus B, and we take it to the power of three. What happens is when this gets evaluated, Python would temporarily create a 165 object by looking at the 123 and the 45. And then it would take the power of three, and it won't write the number down. It will create a new number, a new integer. We'll most likely forget about this again. Then it will give back this to us, and then we will forget about this number again. We don't know if this is removed right away or not. We just can't tell. So again, now we had two objects that were newly created. We cannot see them, so it's kind of like there is no change in state concerning the entire program. And that's an expression, other examples of an expression. Here, Y. We had this in the last lecture. Y, if I give it out, it's a list. That's the list we ended the last lecture with. So if I access, let's say, the last element, I say the third element, I just index with two. So what this would do, I'm not going to draw this here, but let's imagine we had a list just as we had last time. And then Python would follow the Y first to the list object, and then it would follow the third reference in the list to the two object or to the number two. It would give back to us the number. And then it would immediately do nothing, basically. And then in this case, nothing is forgotten because the list just remains as is. But what we do here is we are reading out the memory. That's an expression as well. It has no side effects. And then also surprisingly, if I call the function sum on X and I sum up the entries in the list X, I get back a new number, 104. And this number is given back to me, but it's not stored somewhere. It's created. Physically, the ones and zeros to create to mean the number 104, they are put in sum back in memory that's given back to us into the notebook and then immediately forgotten. So these are all examples of expressions. And then regarding expressions, there is a side note. Maybe some of you have already seen this in the notes. Whenever we use operators, for example, addition, then we would think that this is just mathematics, right? But we can, of course, in Python also overload operators. And then we can do something like a string plus a string. And in terms of strings, in terms of text, what has just been is we just add the two text together with no space in between. That's why the first variable creating has a space here, right? Otherwise, I wouldn't have the space here. I can also multiply a number b, for example, time secreting to get this. So Python is very flexible in how we can use the operators. And this is also something that will not work in many other languages. This is an example of an object-oriented dynamic language where this works. And now, to contrast what is an expression with the other big thing, the other big thing is a statement. And the word statement has the word state in it. So it has to do something with the state of the overall program. And this is basically anything that changes the state of the program. So for example, let's create, or let's basically assign the number 1, 2, 3 to the variable a. So currently, if you look in our memory, the number a, or the variable a is already pointing at an object 1, 2, 3 here. But what happens when I execute this cell is actually this. Python creates a new object, 1, 2, 3, labels it with an int, deletes this reference, and then you don't know what happens here, right? Probably, and I think it's a good practice in this lecture, I will just erase it right away because there is no way we will ever get this back. And then the thing is, a difference to the expression before, let's go back. We see when I executed an expression, like for example, creating plus audience, I see an output here, right? This is what I mean when I draw the red dotted line here. This I get something back. But if we watch closely, for a statement, there is nothing we get back. And that makes sense because a statement is not us asking the computer, can you read something out and give it back to us? It's basically us telling the computer, hey, change something in your memory. So that's a conceptual difference here. And then of course, another example of a statement would be del. So del, which we could read as delete, a better way to read it would not be as delete, but as to dereference. What this does is it basically deletes maybe the name a from the list of names and dereferences the object. And then after some regular interval, Python will garbage collector 1, 2, 3, it will be gone. And then that's what we are left with. So these are the subtleties that go on in memory. To finish the lecture, from last time, one comment about comments. Use them and use as few comments as you can. So don't write too many comments in your programs. And think about using variable names that are self-explanatory somehow, because then we can just not use a comment. For example, in the first line, when we say a distance, we have some number here. It may make sense to say it's in meters, but maybe your program only uses metric numbers. And then maybe it's not useful to say in meters, because it's kind of clear that it has to be in meters. And so just comment your programs. But think about, in the last two cells here, which one would be the better way to go? So calling the variable seconds and then put a comment that this is the seconds of a year, or just call the variable seconds in a year or seconds per year. The second one would probably be better. So but this is something that you learn. So one thing I would ask you in the beginning is just don't call your variables just x and y. Give him some short name. You can also use abbreviations, of course, but give it some name that you can understand. So that concludes this part. And then the question is, why is it important for you? Why should you understand what is a statement and what is an expression? You can just go by try an error in the beginning and then you see if something works or not. But when you read the documentation, one goal of this class is to teach you to read the documentation so that you can help yourself, so to say. The term statement and expression they're used heavily in the documentation. And sometimes it says some function or something needs an expression. And then you know, OK, you cannot give it a statement. A statement wouldn't work. OK, so let's go to chapter two. So far, what we have done last time is we have just typed our code in the code cells, executed them, and done some calculations. And it was fine, but we had a big problem. We couldn't really reuse our code. And that is a very important topic, reuse of your own code. And also, going along with that goes to the term modularization. It's important that you structure your code base if your project grows in a way that you can always extend it later on and also find whatever you want to make changes in quickly, for example, and also give other people a good chance to understand your program in a quick way. So this chapter is a lot about best practices to structure code. But let's first, when we look at functions are the way to do, but let's first look at some built-in functions. So one thing I want to also show to you is in the materials, I basically make a lot of, I put a lot of links in there, right? Everything is basically a link. If you click on the built-in functions, you will go to the documentation of Python, where you have all the built-in functions. And so what I want you to learn is, and that's also what I said, I want to teach you how to read documentation. What I found in the first one or two semesters where I did this was in the exercises, I wrote about some problem and said, and told the students what to do, and then I put in there many, many links. And my expectation was, okay, the students, they have a problem, and they have hints, and the hints are links to somewhere where the solution is. So why don't you just click on the link and read solution, right? And then I found that many students, they went to the documentation, they tried to read it, they didn't read it, or didn't understand it in a fast way. And then they went to some search engine, and they looked for it on the internet, and then usually they ended up on, I don't know, Stack Overflow or something. When I put in links, I do that for a reason, and I do that because you have to really know what is the best source for you of information, of help that you can get on the web, and there is no better source in Python than the official documentation. So the thing that makes it hard in the beginning is that you don't know the wording that they use, and that's also some goal of this course to teach you about those maybe weird sounding terms in the beginning so that you can understand documentation. And we will see an example of this in this section already. So let's go back to our example from the first lecture, set numbers to this list of numbers, the numbers one to 12 without any orders. And then if you wanna sum it up, we just use the sum function, right? And yeah, that's it, and there are other functions. And the thing is, whenever you write a function on your own, you should ask yourself, doesn't this function already exist somewhere? You have to ask yourself this for two reasons. First one is whenever you create your own function, as we will see later on, there is a good chance you miss some cases, some corner cases, and the code that you write is just wrong in some cases. And the other reason is, whenever you implement a function in Python, it runs at the speed of Python. The built-in functions in Python, in the standard installation that we use are written in C. So they are a lot faster. So when you sum something up with the sum built-in function, it will, there is no faster way to do that. And this does not play any big role for such a small list here. But if you work with big amounts of data, this could be the difference between a factor of 100 or 1,000 maybe. So this is the scale at which functions that built-in functions are faster than non-built-in functions. So what are functions? The interesting thing, there's this saying that everything in Python is an object. So for example, and we had those three properties that every object has, we said it has an identity, it has a type and a value. So what I do here is, I call the ID function and I pass it in the sum function. And note, I don't put parentheses here, so I don't call the sum function, I just pass in sum. Now the question is, why is that possible? And the answer is, when Python starts, what it does, it puts a lot of things in your computer's memory that you haven't created. For example, what Python does, it creates and I just write, I don't know, basically nothing here. Some code and we put the label here, let's call it for short, just func. So Python creates a back in memory where it writes code for something and let's say this is the sum function and then it places the name sum in the list of our names and makes it point here. In other words, we have the same idea regarding built-in functions than regarding the objects we built already. In other words, all of which I told you about object holds also for built-in functions. For example, the built-in sum function has a type and the type is built-in function. So this might be weird, but that's a good idea to understand. We can go as far as passing ID to itself. We can say what's the ID function and what that basically is, is it basically asks Python to ask the Python function to look at itself and tell us where it is in memory. So this is something and you will appreciate this in maybe a more advanced course. This is something that you couldn't do in languages like C, Java and so on and that's one of the reasons why you can write code that basically looks like math at the end of the day as we will see towards the end of the semester. So this is a big concept. This is not meaningful for you in the beginning, but just understand when you read on the internet everything is an object. That's all it means, right? We have on the left-hand side a list of all names. On the right-hand side we have bags. The bags contain zeros and ones. If you would want to understand the zeros and ones, we would have to really study computer science. So we don't look at the zeros and ones. We treat it as some semantic concept and every box has a label and some boxes have a name pointing at them. Some boxes may not have a name pointing at them. But this is how object orientation works. And here is our first big concept that is a bit, you know, I would call it an abstract concept. So what is sum? Well, in Python language we call it a callable. And how can we find out if something is callable? Well, we use the callable build in function and pass it whatever argument or whatever function we want to check if we can call it and then we get back a true or false telling us whatever we passed in can be called or not. And we saw that sum can be called right because that's what we did three slides prior where we just set sum and we called it with, you know, a reference to the list numbers. So in other words, the parentheses here, they do not belong to the function. This is what I would call an operator because in the beginning, so how does Python read this? Well, first it looks up what does sum point to and it figures out sum points to a callable function and because it's callable, we just call it as usually what you do with callables. And in this case, we pass in an argument, the numbers list and we get back some result. But the big idea to understand is we call the built-in functions to be callables and then there are other things that are callable as well. And the other things that are callable, we will see in this lecture already. So there are three different, three big ideas of things that are callable that have basically nothing to do with each other. One of them are built-in functions. So let's call callable with numbers pass in and we get back a false and this makes sense. Why does this make sense? So if I go ahead and I create a new code cell, it doesn't work, but we can also do this. We can say numbers and we try to call it, right? Because that's what callable means. And of course, I will get back an error, I get a type error and it says list object is not callable. So why do we have to, why do I tell you this? Well, one of the reasons why Python is maybe slower is when we call the sum function here, we have no idea at this point that this is actually callable. So every time we call sum, what happens is Python follows the sum to the object and then it checks the object, hey, are you callable? Do you have code in yourself or are you a list or are you something else? And when this thing basically says hey, you can call me, then calling it will not cause an error. But the thing is, every time we call the function, Python has to basically ask the object, are you callable and then it calls it. And that makes it slow, right? In other languages that don't have this concept, this object-oriented concept, then maybe it is clear that something is not callable by just the name of it and the language doesn't have to ask, is this thing callable? So this is a reason why Python is a little bit slower, but also a little bit more flexible. So, and the other big thing, and here are my empty cells. So let's get rid of them. The second big group of callables is what I would call constructors. And constructors is also another big word. So let's look at the example here. We have seen how we create floating point numbers. I could, I can create a floating point number by just using this dot notation here. Seven dot zero will give me a floating point. I could actually get rid of the zero. I would still get a floating point number. And now let's say I wanna get an integer out of it. Well, I can use this int thing that looks like a function. So what this looks like is, it looks like there is a function int that I call with the floating point number seven, 7.0. And then I get back the integer seven, right? The thing is this, int is not a function. Another example, we can call int with a text string saying text string seven, I can execute this. It gives me back an integer seven. So what is int? Let's see one more example. Let's call int with 7.99. And what we will see is this gives us back the integer part of the floating point number, right? So that 0.99 is gone. And this is not rounding, this is just truncating. This is just cutting off the decimals. And this is different than rounding because rounding of course would give us back eight. So round is something else. So by the way, round is a built-in function just like sum, but int is again something else. So we have to figure out what is this? The other way around also works. So we can take the integer seven and create a float out of it or we can take the integer seven and create a text string with the symbol, the text symbol seven in it. What is int? First of all, it's an object again. So again, int is some box where we have something here and we have something up here which I won't tell you yet. And the word int when Python starts points at this box. And now let's figure out what is in this label by just calling type, the type built-in again, and we get back type. So obviously it says the int built-in is not a function. It's not a built-in function or method which was shown to us before, but it's now a type. So it's something else. But what we understand is I can use int and I can call it, right? So in that sense, because that's what we did before, so in that sense, int is definitely callable, right? So second example, a constructor, second thing that is callable. And note that how we built this up, right? Let's do this. What is a callable in Python? Callable. Well, the first example we have seen is called a built-in function. And the example was, let's say, some or len, len we have also seen before. The second big thing is called type and I also called it constructor and we just called it. This is callable. And now comes the third thing. The third thing is what is formerly called a user defined function. So it's a function that we write, where we write the logic. So let's look at this. So maybe I'll just write it here to finish this. User defined function. So now I repeat one more time. Why is this important? I didn't teach this in the first two semesters because I thought, okay, this may be too theoretical, right? But then, let's quickly go to the built-in functions here again and let's search for the word callable. We, of course, find the function callable, but then if we look more, there is another function called help that we have also seen before and it says something about what help does. It describes how this help function works and then in the text it says for callables, it does something else. For callables, it reports the signatures. Whatever that means, we don't care for now what that means, but what I wanna show you is that in the documentation, the word callable is used, right? So that's important because that's why I included this in the lecture that you now understand. There is a theoretical and abstract concept called the callable and there are three different types of examples and we looked at two of them already and now comes the third one and whenever you read the term callable, you know you can plug in any one of these because callable means all the thing that has to support is being called, that's it. And we will see many, many more of those examples. I'll give you one more, one of maybe the biggest one that you have seen already or not seen. It's called iterable. So I have 68 hits in the search here on the website for the term iterable. So let's see how often do I find list because we know list, right? List I find 39 times. List is one of the most important data types in Python but it's mentioned less often on this page than is the word iterable. I can already tell you that a list is a specific example of an iterable. So again, just as we had callable as the abstract thing and a built-in function as the concrete thing, we have iterable being the abstract idea of I can iterate over something, I can loop over something and a list is just one thing because it enumerates many things. It has many, many elements in it. So it's just one example of many, many examples in Python that are iterable. And that's why I teach you those words because then you can actually read documentation. And once you can read the documentation, you have one big advantage. You never have to Google again or go on Stack Overflow for this type of stuff. You can always just go to the documentation, read it and then you know how a function works. And the important thing is, this is just a documentation on core Python. It's python.org. But many, many third-party libraries that may be super important for you. For example, libraries that like NumPy that we will also see that is there to do numeric computing, matrix algebra, stuff that we could actually use as business people because we work with financial data and financial data often comes in matrices. If you look at this documentation, they will use those terms also. So that's why I teach you the terms. To, you know, it's not to, you know, scare you off for an exam or something, but it's to really make you, to really enable you to understand the documentation so that you are somewhat self-reliant after the course, that you don't have to like ask someone all the time. You can read the documentation on your own. And that's also, I cannot say this on tape, but there is the saying, read the DTD or the PEEP manual, right? That's a saying among software developers that you read all the time on the internet. Someone asks a question and then the first comment you see is read the PEEP manual, right? And this is not meant to be developers at this point. They are not mean people. They just say, well, why do you ask this question? Why should I answer your question that is in the documentation? You can just read the documentation and then you will understand it yourself. So that's a big idea of this course, a big goal. So let's look at the third idea of a callable, which is the user-defined function. And let's look at an example. So here, what I do is, I use the example from last time where we were given a list of numbers, of whole numbers, and I wanna filter out the odd numbers so I wanna keep only the even numbers and then average them. That's the easy example we did last time. And now I wanna make this code reusable so I wanna put it in a function so that I can just by calling the function called average events, I can do the entire logic and I can do it as often as I want, where I want and so on, without having to copy, paste the code again. So how can we read this? First, here's the word def. Def is a keyword, so that's a word that you can never use as a variable because Python, you know, when Python reads def, it thinks about, hey, this is a function definition. That's what it stands for. Then we have the name of a function. Then we have parentheses. Those parentheses, they are not the call operator here, but they are used here in the same way to show whatever the things that we can pass to this function. And then comes a list of so-called parameters where we say every time we call average events, we wanna give it some input that is being worked on and this input, we give it a name so that inside the function, we can refer to it by this name. So, and I chose the word integers here. This will be different at the end of this lecture, but I use the word integers here to illustrate a point. Then I have a documentation here. It's called a doc string. You can read up on your own how they should be formatted. It's always, you know, not so, I always hate writing doc string because that's not writing code. At the beginning, if you learn the code, it seems like why would you have to document everything? But trust me, if you use Python to, let's say, type up some code for your thesis at the end or some analysis in a company in an internship that you might have, then it's important you document the functions and it's important to document the code because at the end of the day, even yourself won't be able to read, to understand your own code, let's say four weeks from now. Trust me that every time I don't work on my research for a week or two and I come back, I really need to read my own documentation, otherwise I don't know what I'm doing. And then I just copy-paste the code that we ended up with last time. So this is a list comprehension. We will look at this in the future. And then, so what this does, it creates a new list out of an existing list called integers, which you pass in. And the new list is called evens. And then we take evens, we take the sum, we take the length, the count. We divide the two to get the average. And then we write return average. And what the return is, it's a similar to def. It's also a keyword, so we cannot use return as a name in Python. And whenever Python reads return, what it does is when this function is executed later on and Python hits the return word, it will just stop executing the function and use whatever expression, now you hear me say the word expression, not statement, the expression that is to the right of the return word and gives it back, so the function ends and that's the result of the function. So now one more thing. Now I execute this, right? I hit control, enter, I execute the function and what happens, nothing happens. And when nothing happens, when you don't see an output, we already know it should be a statement, right? Because we don't get a return value back. So what happens is this. So what happened when I just execute the sellers, Python goes ahead, makes, let's say, a big box. It writes in something, zeros and ones. To zeros and ones, they mean this logic, right? This is the code that I just wrote and this logic is somehow translated into zeros and ones and this is now in the box. Then I have a label for the box, which I will tell you later what it is and then now what, what happens? Well, what happens is Python creates a variable called average events and makes it point there. So and because of this last step, because of making a name pointing to this object, we call the entire thing here a statement, right? And yeah, that's it. So that's what happened right now. Nothing else happened so far. Now the question is what can I do with average events? So at first I can reference it. Absolutely not useful here, but I could do it. And what this illustrates is, now average events exist as a name, as if I defined average events to be a variable. So I could have written average events that say one, two, three and then get rid of the one, two, three again and then I would see one, two, three here. So I couldn't tell the difference. I couldn't tell that this is a variable that I defined to be an integer or if it's a function, I cannot tell by just looking at the name. And then I get the value back, which angled brackets here and this is a convention. Basically this helps us to understand what it is. So as humans, we can now read, well, this is some function, but we couldn't copy paste this back. So if I now go ahead and copy paste this back into a cell, I will get an error, right? And before that, I told you last time what is a literal and usually when we evaluate something, we get back the value and the value is usually a literal, which means I can copy paste it back in and Python understands it. Whenever you see angled brackets, it's an indication that you cannot copy paste it back. So let's go back here, average events. And then of course, because it's an object again, because everything is an object, I can call ID and type passing average events and now I know the answer of what type this thing is. It's of type function. So I write in their function and that's now the third one, right? So back on this list here, this is now the user defined I put in parentheses and function is basically what it is. So now we have three examples of a callable and the last one, the last of which is the one that we spent most time with. And now we can of course ask the question, is average events callable? And the answer is true, right? So three examples of the same concept. And now let's call the function. So one way of calling a function is just, well, the only way to call a function really is just put the parentheses next to it. That's the call operator. And then we have to pass in something that I specified in the function, in the function definition. In the function definition, I specified a parameter called integers. Now I pass in this list, it's the same list as before, but when I pass something in, I say I pass in an argument. I pass in something as the argument. Inside the function, I say the formal parameter is. So there is a distinction between the terms, between argument and parameter, maybe exam question, I don't know, but parameter is the generic thing and argument is the specific thing that you pass in every time you call it. And then when I call it, I get back the result as before. And now the question is the call operator, it takes, if you read the documentation on it, it takes a comma separate listing, not a list, a comma separate listing of expressions, or one expression. So what is an expression? Here I wrote the list as a so-called literal, but now I could also say, I just put in the numbers variables and because any variable is always an expression because when I look up numbers, let's say I also put up numbers here, let's say I have a list, we know what lists are, lists look like this somehow. And then yeah, we have like many, many small boxes here. And that's a list. And now numbers, the name is created and I make this point. When I do this here now, when I write average events and I call it with numbers, what Python will do is, Python will call the function, but it will not copy the data, it won't copy the numbers list, what Python does is, it will give this arrow basically to the function. So every time the function is called, what Python does, what is called calling by passing in reference. So it passes just some, into some address to in memory to the function says, run yourself and use this memory address as your argument. So numbers is not copied here. That's an important idea. We saw how numbers can, how a list can be confusing. And the reason why is, as we saw last time, numbers can be changed once they are created. So the number one and integer one cannot be changed once it's created. It's there. The list of numbers, as we saw last time, we can go ahead and we can change every single element in it and override it. And we saw that this can be confusing if we have more than one variable pointing at the same list, right? That's what was a source of confusion. I think you weren't confused because I just threw the diagram. So when you give this lecture without drawing the diagram, people are most likely confused if you draw it with the diagram. People are less confused and that's a good thing. That's how it should show you that if you are confused, just draw the diagram. But now the important thing here is if I call average events with the argument numbers, then what happens is I pass in a reference to numbers and then inside the function, I have a reference to the list and outside the function, I also have a reference to the list. So I already have two reference to the same list. So having several references to a mutable object like a list, which was a source of confusion last time, happens naturally in Python. It happens all the time. And usually nothing bad happens, but sometimes it does. And then of course it doesn't really make sense to just call a function and then forget about the return value. What you usually do is you use a statement and you assign the result to a variable. So what this does, so in other words I can illustrate is what the first code cell if run does, what it goes ahead and it creates a float 7.0 float. And then it will give it back to us as the user, but it won't store it anywhere, right? So that's an example of an expression. So just a mere function call is always an expression because it has no side effect. At least it shouldn't have a side effect. There are ways for functions to also have a side effect but usually this function doesn't have a side effect. If I call the function in the second way where I assigned the result to a variable, what I do, let's now forget about this. What happens is the 7.0 is created as well. And then the variable result is created and made point there. And because of this reference that is created, we have a side effect, which means the second way of calling the function has a side effect. The second cell is a statement. The first cell is an expression. And the next cell just result is of course an expression because I just read out what is in memory. So far so good. Now comes a little bit harder part. And this is, so what happens once the function call has ended? We saw that there are two variables called integers and events that were only used inside the function. Now the function call is over. And if I look at my memory diagram, I don't see integers and events here, right? They are not there. So that means if I execute the next two cells, those two names are gone. They don't exist. They never existed by the way. They only existed inside the function as the function ran. But once the function returns, those names are gone. So the names only exist inside the function and only temporarily. That's important. So after the function is over, those variables don't exist. There are programming languages where this is not true. Names that you generate inside a function called the exist outside the function as well. Usually these languages didn't survive. There's one that actually survived that has this unfortunate feature. It's not good because then in this way, your code will have running a function means you, this can have a side effect somewhere else in your program. So you don't like this, right? So that's a good thing. And then of course, we can make, and that's a mistake. I put this in the slides because many students in the beginning make this mistake. So let's say you first used these code cells or these logic outside a function to play with it in your homework example, maybe. And then you come up with a solution and then all you do is you just indent your solution and you put the def in front of it. So you use your solution and make a function out of it. By doing this, what often happens is you make copy-paste errors. I don't know why, but students make copy-paste errors here all the time. And I show you here one such common copy and paste error. So for example, I have the same function again, average now called wrong, average wrong because it is wrong. It takes a parameter called integers, but inside the function body, I don't use integers. I only reference numbers. The problem is numbers exist. So it exists outside the function. So if I execute this, I get a new function and numbers, as I said, it exists outside the function. So if I now go ahead and I call average wrong and pass it the argument numbers, what happens is I get the correct result back and I get it back for the wrong reason. And I think that's the, because of this, this is why students, when they turn in the homework, they don't think that their solution may be wrong because when they call the function after defining it and they get back the correct result but still the function is wrong. How can I, how can you see that the function is wrong? Well, you just make up another example where you can calculate the answer on your own and by averaging these three numbers, one of them is only even. So the answer should be 456. If I execute this and I get back 7.0 again, I know something must have gone wrong. And the reason is of course pretty simple. Every time I call this function, it uses numbers from outside the function. So whatever I pass into this function is never used to calculate anything. And that's a very common source of error. And here's something, the next, the third scoping rule is something that I would say is not an error in most cases, but you still should know about it. So let's say average evens again as before. But what I do now is I put in one more line where all I do is I go over all the integers passed in and round them. Why do I do this? Well, if I say, okay, this function accepts a list of numbers, how do I know that these numbers are whole numbers, right? I just don't know. I would have to check. I would have to trust the user of my function that the user passes in something that works, something that is specified in the documentation. But then sometimes it makes sense to validate this a little bit. And a nice way to validate that the numbers passed in are integers is to just round them. So if you pass in, let's say a float, let's say the float 7.0, you just get back to integer seven. Now, the problem here is if the user passes in floats at half decimals, then of course the result will be a bit different, but then it's the user's fault because in the first place, the user obviously did not read the documentation because the former documentation, the previous version, didn't say pass me a list of float. It says pass me a list of ints, right? And if you pass a float in, then you obviously haven't read the documentation. So what this does is this additional line here, I had to come up with a name for the list that is created and I just called it numbers. Now the problem is we know that there is numbers outside the function as well and now I create a second numbers inside the function. So what happens is this bad and the answer is it's not bad because when in the second line of code, Python wants to look up numbers, it always looks inside the function first and when it finds numbers to be inside the function, then it just uses this. So this is called the innermost scope and only if, so just to show you another way of making a mistake, just in case if I call this not numbers, but numbers two. Now what would happen if I ran this function, numbers would again look at numbers outside the function and we would get exactly the same error as in the example before, but having numbers inside, defining numbers inside a function is not bad, right? It just, you know, shadows, that's the word, the variable of the same name outside it but really we don't care, we don't want our function to look outside anyways. So this is not bad. So now, I also want to show you something here. So oftentimes you see links to something called Python Tutor in the notes and for those of you who have not played with it, this is a tutorial or like a helper tool for beginners and what you can do is you can put in code and the links that I put in, they already contain code but you can also copy paste in any code you have and then you can basically automatically create a memory diagram just I do here by hand but this is created for you. So let's run this. So first line of code just creates list numbers. We have the list here. The second line of code is the dev statement. What this does is it creates a function so this is the back with the code in it and a name pointing at it. It's exactly the same thing here on the right hand side. Next line of code is of course the last line here on the left hand side and what this does is it executes the right hand side of the equal sign first. That's the expression that is evaluated first before the statement is executed. So what that happened, what here happens is the literal list here with those five numbers is created and is immediately passed in by reference to the function. So now what we see is the function starts to be called. It starts to run and now we see something that is for me a little bit harder to draw here, a bit more complicated. We see this blue color around those variable names, right? And that is the function's so-called scope. So these variables, these names, they only exist as the function runs and then we have what is called the global frame or the global list of names. These are the names that exist outside the function. So as the function runs, it will first always look up the name inside the blue box and only if it does not find the name inside the blue box, it will go to the white box and try to look up the name here and only if it does not find the name here, a name error occurs. So if we are unfortunate and we put a name, a variable by the same name outside, we might not see an error where we would have liked to see an error. And so what happens here is now I have a pointer from integers, the name inside the function scope to the list object. And now I run the next cell and what this does is it does the rounding, right? So I, and now here we see something else. So a list comprehension. A list comprehension is what I showed you, it's the equivalent to a loop, right? That's what I showed you in the first lecture in the beginning. And whenever you have a loop inside a list comprehension, then the list comprehension also gets its own scope. So in other words, the variable n, the lowercase n that I use to loop over the integers only exists inside this scope here. It would not collide with an n that possibly exists outside, right? The n is first looked up inside the blue box where it exists and everything goes correct. So now I press next and we see, if I go back a bit, we see here what the current n is 41.1. I click next is 42.2. So this is obviously a for loop that goes over all the n's. And then the return value of the list comprehension is created. And this is then assigned to the numbers variable. So now I have a list of the rounded numbers inside the function and I have a name, numbers pointing at it and I have another name, numbers pointing at the list outside. So the name numbers exist twice at the same time. And now in the next line, I run another list comprehension. So another loop. And what this loop does, it looks at numbers. And this now of course looks at the numbers inside the next scope. So what we see here is we have the blue scope, it's active, but if a name, for example, numbers does not exist in here, then we look at the next scope. Numbers exist, so we use this numbers. If numbers would not exist here, we would look at the next scope and look at this numbers. And if numbers would not exist here, we would get a name error. So we always go from inside out like an onion. No, onion is outside in. Okay. And then we get a new list, which is called events. And now we have an interesting thing. Actually what we see now is, this is a big problem, what we see. We see that in order to first round, then filter and then average the list, I have four copies in memory. So the list exists, there are four lists in memory right now. We see them, right? All next to each other. The problem is, in the worst case scenario, if I pass in only odd numbers, in the first, if there were only odd numbers in the first list, the second, the third and the fourth list would be how long? They would have the same length, right? Because nothing gets filtered away. So what this would mean is, my assumption when the program runs would be that, well, it needs memory, enough memory to store this one big list, but that's not true. We need four times the memory. So there has to be a better way, and of course there is a better way to basically work with only one list and conceptually work with lists that don't exist. And we will see that in, I think, lecture seven or eight. And this is also why, this is basically one motivating example of what I mean when I say this course should prepare you to work with big amounts of data. Because if you write this code, this code most likely will not run with big data. Because if the first list is too big, or let's say if the first list is 25 gigabytes big, and my computer in my office has 32 gigs of memory, the first list goes in. But once I start copying, once I do the rounding, my computer dies. So I'm out of luck. But you can solve this problem with code. You don't have to buy a bigger machine. You don't have to go to some cloud service. You can solve this only by code. So in other words, if you're a startup and your programmer does not know how to make this in an efficient way, you will most likely pay too much money to some cloud service because the service you require for your software to run, they are just too big. They are bigger than they have to be. And then if we execute the function to its end, then what we see is the 42 exists. And now in the only the very last click, the function, the function scope will go away. And the 42.0 is returned and assigned immediately to the result variable. But we have seen temporarily we had lots of memory. That's the whole point. So I only show you this Python Tutor. I won't show Python Tutor all the time because it takes some time to do so. But whenever you are in doubt, you can just copy paste your code into Python Tutor run it yourself and also for the homework. And then you will see exactly what goes on. One note regarding Python Tutor, the way Python Tutor shows you the data, the objects is a little bit different. So for example, for the list, the integers are written inside, those inside what is called the array here right away. But what this is lying to you, so how your computer's memory looks like is basically like here on what I draw manually. You have the arrays, but every array has only a reference to another bag. So here on the right-hand side, you have one big bag. But in reality, you would have 13 bags, one bag pointing to 12 other bags. So that's a lot of overhead. But this can also be dealt with. So let's go back. Now we execute this function, function exists. And now of course, what we should do is I wrote this new function and whenever you work with functions, what you should do is you should test your code on a small data set where you know the answer. That's called testing. There's another technical term for that, it's called unit testing because what you do is you look at one function at a time and you know for a given set of inputs, what the output is. So you test the function as a black box by some input output specification and that's called unit testing. So what we do here is I average the events here and of course I know the result because I know that the rounded even numbers are 40, 42, and 44, they average to 42, so I know it works. And then of course, as we just saw in the Python 2 example, numbers after running the function is still untouched, right? Because I didn't change. The thing is, I could change a global list, a list specified in the global namespace from within a function. This is possible, this would be a side effect. And usually when you call a function, when you write your own functions, you don't want them to have side effects. You want them to give you back the same result given the same input all the time and not change anything outside. Okay. And then of course, this is just a trivial almost. I left out the rounding again and now I just changed the name of the parameter from integers to numbers because I find numbers to be the most intuitive here as the parameter name. But now we know that numbers, the parameter within the function does not collide with numbers in the global name and because it does not collide, we can do this. Should we do this? Well, it depends. Sometimes I try to think up what is another good name or another good variable name for this parameter. Maybe integers, but then calling it integers is kind of stupid if you put a rounding number in it where you also accept floats. You wouldn't call the list integers if there can also be floats in it. Then you could call the list whole numbers maybe, but whole numbers is a whole lot longer than numbers and variable names should also be short. So we already see that it's not so easy to find good variable names, right? And so you will definitely come into situations where you have to reuse variable names and then it's good to know what the scoping rules are. So here it works again just to show you. That's it. So I think because of the memory diagram and also Python tutor, there should not be too many questions now because I think you now know every detail of what happens in memory. And also after the last lecture, some people said, this may be too easy what I did. And yes, it is in the beginning because we're just in the first week, but learning to draw the memory diagrams is super important, especially because in other coding courses for other languages, the diagrams will work differently. So this only holds what I tell you here, only holds really for Python. And for other languages like C, for example, this does not hold. So in C and also Java, for example, functions are not objects. That's a big difference. Let's continue a bit and make this function that we just defined nicer. So first thing we saw last time, is the diff-mod-build-in function. And it takes two numbers, two arguments. They are comma separate. So argument one, comma, argument two. This is what in the reference or in the documentation is called a comma separate listing of arguments. It's not a list. So when the Python documentation talks about a listing, it does not mean list, not the list type. In fact, this would be another data type that we have not yet talked about. So, but the point is, whenever my function takes more than one argument, the order matters, right? And the order is of course in correspondence to how in what order you defined your parameters. So let's assume we wanna change our average evens examples a bit, and we want to add a second parameter called scalar, which only at the end scales the average we got. Now, this might sound like a stylized example, that's not too realistic. I think it's realistic if you think about a function that averages money. So this could be averaging something in Euro and then converting it to dollars. So this is actually not so unrealistic. And then the thing is, what do we do? So we just write scalar here, give the second parameter a name called scalar. We use it, the scoping was all work fine. We put it of course in the documentation. We say scalar should be a float. Now an int is basically a more narrow type compared to a float. So we could also pass in an int. It would not, it would work. You know, passing in 10 or 10.0 is the same effectively. So this looks very good. Let's go ahead. I defined a function and now let's call it. So let's call it with numbers and then multiply the 7.0 we should get by two. It works, we get 14.0. No problem. So this works. But I don't think it's good because the reason why this is not good is what order is natural here? So should we pass in the numbers first? Is that what we would think of when we see this? For the different, it makes sense. The order, the order makes sense because having the number that gets divided first and then the number that divides the first number makes sense because that's how we taught math. We always write the number that gets divided first in a ratio or in a fraction. So in math, there are some orders that are natural but here in this example, there is no natural order. So what we could do to improve the reading of this line of the cell is we could just name the parameters of the arguments that we pass in. So we execute this and it gives us back the same result. The first way in the first example, we call this passing in the arguments by position and then they are referred to as positional arguments and in the second example, we call this to pass in the arguments by name and then they are called keyword arguments. That's the technical term here. So this works and now when we use the keyword argument version, we can of course also exchange the order because now Ivan knows what the argument is but then we could also mix it and this also works but the thing is the function we defined allows all of that. So there's no way for us as the author of the function to force the user of our function to use our function in a certain way and that's what we would like to do. So how do we do this? There's also some, yeah, let's look at this. So here's a short version of a second issue. This is just copy pasted the two functions that we had. So it's average events and scaled average events. Why did I paste them into the same code cell? It's to illustrate a point of course and the point is that everything except the last line which is here and here is the same. So three out of the four lines of code are the same, right? That's an issue. Whenever you see this in your own code base, let's say for a thesis where you always copy paste some function and you're always adapted a little bit, that's a bad style because when something major changes you have to change all your functions. So this is an example of code that is not modular and we don't like this code. So a better way but first of all, let's check if it still works, it does. What's a better way? A better way is to say one of the two functions namely the not-scaled average events function is kind of a special case of the other one, right? So what we could do is we could say we define scaled average as it is and then we change average events and just call from within the other function and just pass in the scale of one. So this is basically, I mean the scale of one is not scaled so we could do this, right? And that would be an improvement but then there is another idea, another way to look at it and the other way of looking at it is and that may sound weird because we are actually taught that there's always a generic case and then there are many, many more specific cases of this generic case. As a programmer you are not limited to this kind of view of the world. You can also say a special case, I make the special case the more generic one and this might sound not so intuitive but it makes sense and let's see why. Let's say I define average events again but now I give average events the scalar parameter and I set it by default to one. So what this does is it basically says we now have a new version of average events that would take a second argument but if we don't pass a second argument in it takes the one instead. So in a way that's a default value, a default argument for this parameter and we also write it in documentation of course and now this one function replaces both of the functions before. So now we have one case only. We don't have a generic case and a special case but we have a different way of viewing the world by saying there's only one case and basically the special case is now our generic case and if we want to use a scalar then we just pass it and if we don't pass the scalar we only have our default case. So this function I can call as before. So this is no different from calling it as you know 10 slides before. I could also give it two arguments. Takes one, okay I did not execute it, I did not execute it. So you should always execute a cell so now it takes two arguments. So now it takes two arguments and now it also works and we have also seen this before and then of course to make the code a bit more readable I can put in the second name and only name the second argument to tell the reader of the code while the second argument is the scalar and the first argument kind of is intuitive because what do you want to do? If the function is called average events it kind of makes sense to give it a list as the first argument. So now this works and now the only problem that is left is I have still two different ways of calling this function. I can either say two and don't give it a name I can pass a two by position or I can pass a two by name. What I would like to do, I would like to be able to force the user to have to use a name if the second argument is provided. So if it's not provided it will default to one but if it is provided I want my user to really specify the name so that I know that my user of the function knows what he's doing or she. And this is where keyword only arguments come into play and the only thing that is different and that may also be confusing for some of you but there's really not a whole lot different except for the star here, for the asterisks. That's the only thing that is different and what this enforces is that all the names, all the parameters that are specified after the star if they are passed in they have to be passed in by name. So, but in this example here they don't have to be passed in why? Because I have a default argument of one. So if it's not passed in it's one and if it's passed in I have to give it a name. That's what the star means. Everything else is exactly the same. And now let's look at how we can work with this function now. Well, we can still call it by just saying average events of numbers nothing different is the same as to begin with. And then I can call it in the second way by saying now the scalar is two. What I cannot do is I cannot pass the two by position because now I get an error message saying, well it takes one position argument but two were given. So the error message might be a little bit cryptic but it basically says give me a name for the second argument. And now we have solved two problems. The first problem was that we had two functions that basically looked the same except for one line of code, the last line. And now we have only one function. So we reuse our code. It's modular. It's easy to maintain because if we want to make a change in the future let's say if you want to do the rounding different if you want to do the rounding with, I don't know. Two decimals after the decimal point we could change it at one place and both functions so to say get changed by making only one change. And the second improvement is we force our user to use the function in an explicit way rather than implicit. And this will make the code base easier to read in the long run. So maintenance of code is now better and also readability of code is now better. And these are things that we should strive for but function-wise we didn't change anything as compared to the first function that I showed you. And then something interesting here because of the object orientation of the object-oriented nature of Python what I can do is I can create function objects that don't have a name. And this makes sense. Why? Because if I go back to my diagram here I create the object of the function first and then I make the name point at it. And what makes the name point at it is a def statement and def is a statement. Why? Because it makes a name point at it. What if we found a way to create a function without making this name? So now how can we do this? This works. It's just called lambda and it's called lambda expression. So it's called a def statement and the lambda expression because of those side effects that are not here in the second case. And this basically reads like a normal function definition. It says I take one parameter that I call x and then I have one expression on the right-hand side. It has to be one expression. That's what the documentation says and that's why it's good to know what is an expression to begin with. And then it says, well, I take my parameter and I just add three to it. And now there's no return and the reason why there's no return is because of this limitation that on the right-hand side of the colon there can only be one expression. This expression will always become the return value, right? So that's why we can save ourselves the return word here. And I execute this and now the interesting thing is what happens? So let's take a new diagram here. So what this does is all it does, it creates a box, writes the ones and zeros for the code in there for the logic and writes the type function here and that's it, no name. So what we just did is we created a function and didn't call it and we have no way to call it because there was no reference to it. And so now to prove to you that this works, what I do is I take the lambda expression because the entire thing, so let's break this down a little bit from right to left. So the X plus three itself is an expression, right? The entire thing is also an expression because it just creates a function with no name pointing at it and then we take this expression and we assign it to a variable called add three. So what I do now in the next code cell, if I execute this is I create this new object, functional object that's now, let's suppose that's a new one and then it creates a name called add three and makes it point there. And now the question is, does that look any different from the depth statement above and the answer is, of course, no. It looks, we cannot tell the difference. We cannot say if this was created by the depth statement or via a lambda expression, yes? So you want to do what? If I can tell, so your question is, can I read the ones and zeros and can I know what the code does? This is possible, but this is an advanced topic. So I wouldn't even do it in an advanced course here. So that's how far away it is. But you could do that. And you could actually not do that in a language like C and that is one big advantage of, there are tools for Python that analyze your code and tell you the code you wrote is good or bad and they kind of say you could improve this and this and that, it's called code introspection. And these kind of tools, there exist many of them and maybe we can also look at some of them and that's easy to do in Python, not for a beginner, of course, but at the reason why we can do this is because of this object-oriented nature. But now let's go back to the example. To prove to you that add3 is a normal function, let's just check what the type is. It's of type function and it's callable. So we can just call it and let's just call it, it works. So now you might ask yourself, why is this useful? I just put this in here first for the reason that to show you that what happens on the right-hand side in the memory is totally independent of what happens on the left-hand side. Whenever something on the left-hand side happens, we call it the statement, but we don't have to use a statement to create a function object. And then how can you use it? We will work with NumPy and Pandas, which are libraries that you should know by the end of this course because that's the libraries that will help you to work with all kinds of business-related data, financial data, and so on, time series data. And this is what will let you get rid of other languages like R, Stata, and so on once you learn this. And in those libraries, there are many, many tricks where let's say you have to filter some matrix, you have to filter out some outliers from, I don't know, you have econometric data and it's a time series and there are outliers in it and you wanna filter out the outliers. Often this is done with slumper expressions. So you will see applications of this. On their own, they are not so meaningful. Okay. So, but I think you have understood everything so far, at least most of you are not in. And yeah, so let's continue a little bit more. So how can we, now I showed you built-in functions so far in this course and also how we can create our own functions. Now, most of the time, when you write your, when you want to write a functionality that doesn't exist in Python yet, I'm pretty sure that someone else has written it already. So you have to find a way to get someone else's code into your local Python installation. The first of those ways is to use what is called a standard library. So the standard library is independent of core Python. So whenever in my materials you read core Python, what I mean is basically everything that is Python that is implemented in C and everything that is related to the syntax, to the keywords and the very narrow core that without that Python doesn't even work. And then there is stuff in a standard library that usually is very common, many, many common functionalities inside there, but they are not needed to make Python work. And the Python core developers decided to have the Python core separate from the standard library. And this has many, many reasons. One reason is the team that works on core Python is a team independent from the people that maintain the code in the standard library. So for every module or package in the standard library, you have many, many experts that only maintain this code and improve it and fix bugs and so on. But then, and maybe there is an overlap with core Python developers, but the core Python developers, those who make the language faster, those who take care of all the memory stuff for us, they don't wanna have to do anything with the standard library, so to say. Well, they wanna know that the standard library is good, but it's just a different project. So what is in the standard library? Almost anything. An example is the math module. So how do you import something? Well, you just say import and then the name of the thing. And the standard library, one module, is called just the math module. And we just import it by saying import math. And then what happens is in your computer's memory, a box appears, it has ones and zeros inside, and it has a label called module, and then it has a name called math point edit. So even for code that someone else writes for you that you include in your own Python process, this will also be an object because everything is an object in Python. So let's see what we can now do with math. First I can reference it, it's a name. So the name has the angle brackets again, meaning that we cannot copy paste it. But this is basically angle brackets means we as humans should be able to read it and understand what this is. And basically what it says, it's a module, and the code of the module lies in this file. And as we see, it lies in a file called math.cpython........S.O. And I can already tell you that this is code written in C. So we are now using code that was written in the C language. And the C language is super fast. And that means everything we do in the math module is super fast. Math is an object, nothing special of type module. If you want to know what we can do with it, just use the dir built in function. Now we call it with math. Then we see all the things that math comes with. We see cosine, sine, and E, and so on. And then for example, we can look at pi and E. Those numbers you know from math, they are in there. You don't have to define it on your own. And also note, on my machine, I have a certain number of bits that reserve for the precision of numbers. And this is pi and E to the maximum number of precision. So if I change this on my personal computer here, then this number may be longer. So this number automatically adjusts to whatever the platform is that it runs on. We can look at the square root function. We see it's a function we call help on it. It says the function it takes x as an argument and returns the square root of it. And then we can of course, path in 2, get the square root of 2. And then the thing is, as I told you before, whenever we have a function call, what we put inside is an expression. So because it's an expression, I can also, I don't have to put in a literal there or a variable. I can also put in some expression like an arithmetic expression, like 2 to the power of 2, which is 4. But I don't put 4 in, but I put 2 to the power of 2 in there. And it also works. And I can also use, I can first take the average of a list of numbers and then immediately use the result of our own function that we wrote and pass it on. So what this does here, this creates a lot of objects in memory that are immediately consumed by some other function. And then all the intermediate objects are forgotten right away. And we are only given back the number 10.0. And then the number 10.0 is also forgotten because we don't store it. So this entire cell here is also an expression because it has no side effect. But a lot of things are created in memory on the way. So just to review this, expression does not mean nothing happens in memory. It just means whatever happens in memory we cannot see permanently. So there is no permanent side effect. Random module also important, import random. This is important. This is a super easy thing to use to build your own simulations. If you want to build some function with which you can simulate random demand of something from customers or something, you can use the function or the random module. It has many, many functionalities on it. For example, the random function. So the random module has a random function. And what it gives you, it gives you an x that is uniformly distributed in the interval between 0 and 1. So some number between 0 and 1. And all numbers are equally likely. And now we can call it and we get back a number. And I call it again. And we see random numbers. They are not really random because computers cannot be random. Everything is deterministic for computers. But for our purposes, they look random. And they are safe to use. So they are random enough so that you can use them if you use that for some scientific analysis and you want to publish a paper or something. That is good to use. And then of course, there are other functions that are quite nice. For example, the random choice function. So what does the random choice function do? It takes what is called a sequence, SEQ. What is a sequence? We will learn extensively in a future lecture. But one example of a sequence, sequence is also an abstract concept. One example of a sequence is a list. So let's just pass the list numbers to the choice function in the random module. And I get back a random number out of my list. I can run again. I get always a random number out of my list. So if you have five outcomes that a customer can do in your simulation, you can just put them in a list, use random.choice, and you can always get one. Now regarding scientific publications, they have to be replicable. That means whenever another scientist uses the same data that you used for an analysis, they have to get the exact same result. How do you do that? You basically seed your randomization with a number that you know. And whenever you seed your random number sequence, so to say, what happens is I call random now. I get a random number. And now I seed again. And I want to get another random, so to say, number. And I get the same number. So the sequence starts at the same point. And we see that it must be deterministic, right? Because if I set the seed to start value, so to say, to the same number, and I get two random numbers and they are the same, we know it must be some deterministic thing behind it. So I will leave it at that. And I will finish up the rest in the next section.