 In this video we are going to review the scoping rules we learned about in the previous video, but this time we don't draw memory diagrams by hand, but we use Python Tutor to see that we can also use Python Tutor to automate the memory diagrams. So let's go to Python Tutor, let's click on start visualizing your code. And now we have to write our code right here in the browser. And we're going to use the same example as in the previous videos, so given a list of numbers that contains even and odd numbers, find the average of all the even numbers in the list. So let's first go ahead and create a numbers list. And we give it the numbers 7, 11, 8, 5, 3 and 12, 2, 6 and 9, 10, 1 and 4. So these are the numbers from 1 through 12 without any particular order. Now let's go ahead and create a function, let's call it average evens as in the previous video. So let's say define average evens. Let's write down the parameter list as a parameter for now. We are going to specify the parameter as simply integers. We will change that in a bit. And the line with a colon. Now we should put down the doc string here. And I only leave it this way in a short form. But in the long form, especially in Tuba notebooks and wherever you develop code, you should please go ahead and in your own interest use a good doc string such as we learned in the previous videos. But now let's focus on only the code inside the function. So we are going to first create a second list that has only the even numbers in it and we use a list comprehension to do that. So let's go ahead and write n for n in numbers or in integers that was already almost the first mistake I made. So we have to refer to of course the parameter specified as the input to the function. So n for n in integers, if n divided, modulo divided by 2 has no rest. So that is the mathematical definition of an even number. Let's assign the new created list to a variable called events. In the next step, we calculate the average to be the sum of all the events divided by the length of all the events or the number of even numbers. And in the last line, we have to specify what is going to be returned at the end as the result of the function. And we are going to return a reference to the average. And now we have to call the function and what we are going to do is we are going to assign the result to a variable called result. And we simply go ahead and now we call the function average events. And we give it the numbers list and let's maybe do two examples. So at first we will create a result one and then we are going to create a result two. And result two is going to be average events and we don't give it the numbers list. But we are going to create a list object on the fly. So we are going to create a list with the numbers 40, 41, 42, 43 and 44. And again, why would I choose these numbers? Well, it is always good to test the function with input for which we can predict the output and the output should be 42 here, 42.0 to be exact. So let's now click on visualize code and let's see what's going to happen. On the left hand side in the memory, we have frames and previously and especially in my hand drawn diagrams, I call that left part just names. We will see now in this video where the term comes from. And on the right hand side, we have the object. So let's first execute the first line and create a numbers list. So we have a name called numbers, a variable that references a list with the numbers. And again, as mentioned previously, the numbers are actually not inside the list, but the list only contains references. So arrows to integer objects, that would be the precise picture. But I will just remind you that Python 2.0 does some simplifications when showing the memory. Now next step, note how the entire dev statement, so all the function body here is going to be executed in one step. So if I click next, the red arrow is going to go down to result one, as you will see. And that means in memory on the right hand side, we create a function object. So an object of type function that does not contain references to numbers just like the list does, but it contains the actual code that is in the body of the function. And on the left hand side, in the global frame, it says average events. So that is the name. And now let's go ahead and call the function that's executed with a reference to the numbers list. So let's do that. So what we see is what's going to happen is we get a new blue box here. And we see also the upper box. It's actually a box. So we see here is a cray line on the left hand side. So this is actually a box. The box has the name global frame. And down here, the box has the name of the function we are currently executing. So the function to which it belongs. And what that means is Python manages where we can see which variable. That is basically what scoping rules are. So we see that numbers and average events are in the global frame. The global frame is a special frame that is visible from everywhere within the program. And the average events frame basically includes only the names of variables that live as the function is being executed. And the first name that is created is the formal parameter integers. And see how as the function is being called. So what happens is when I now click next, I just went back one step. If I now click next, the only thing that happens is Python fills in a reference for every formal parameter. So that is why integers receives a reference to the numbers list. So note how the numbers list is not copied. The only thing that the function gets is a reference to the same object. It also has a reference from the global scope. And now what's going to happen is I click next. And Python starts to execute the first line. And now what's going to happen is for the list comprehension on the right-hand side, we get another scope. So that means all the variables, in particular the letter n, the variable with just the letter n, only lives inside the scope of the list comprehension. So what we see is a list comprehension. We saw that in previous videos. It's basically just a hidden for loop. So we see how we are basically looping over all the numbers one by one that are in the numbers list. And once we are done, what's going to happen is the list comprehension as the scope on the left-hand side disappears. And the result is we get back a list object. So that is the return value, so to say, of the list comprehension. And a reference to that is stored under the name events. And the name events, as we see, is inside the blue box that models the scope of only the function as it is being executed. So in other words, the color blue just indicates what is the current frame that is active. And all the boxes on the left-hand side, technically speaking, these are what we refer to as frames. That is why Python Tutor has the header here called frames. I prefer to use names in my diagrams. And frames are just a concept by which the names are basically grouped together. And now what we see is we have a second list here, a copy, or basically we have a second list that in the worst case scenario, if the first list contains only even numbers would be the same size. So again, a note how previously when we compared different solution approaches to this particular example, we saw how one solution approach did not need a second copy and a second list object, but some do. And the approach we're using here actually does need a second list object. So that is a memory implication here. And note how in the next line, as we go ahead, we use the event's name inside. So in the next line where we divide the sum of events by the length of events, Python looks up the variable from within the scope, from the innermost scope, the currently active scope. And the average becomes 7.0. And note how the 7.0 in here is shown on the left-hand side. However, in reality, the 7.0 would be a floating point object on the right-hand side. And we would also have a reference to it. That would be the whole truth and nothing but the truth. And now what's going to happen is, we are at the last line of the function and in the function body. And this is the return statement. And whenever a function is executed and hits a return statement, the function immediately stops. The function is over, no matter where the return statement is within the function. And here we only have one, so that makes it simple. And then the expression on the right-hand side is being evaluated and a reference to the resulting object is going to be returned. So in other words, the reference to the 7.0 floating point object that is really on the right-hand side here, that is going to be returned and stored under the variable called result1. And result1 is going to be in the global scope again. And then what we see is, after the function is done, the local scope of the function, the average event scope here, it totally goes away. It disappears. And all the names are gone. And because the names are gone, also the name events is gone. And because the list object with the even numbers only has one reference from the event's name, the list object will be garbage-collected after its last reference goes away. So this is why the list object here is also basically going away. So that was how we calculated or how we executed the function given input that also lives in the outer scope as the numbers list. And now let's do this a second time. But this time, we are not giving the function a reference to an object that already exists. But instead, we basically create a new object, a new list object on the fly. That's usually what programmers say we created on the fly. And then we give it immediately to the SD argument for the integer's parameter to the function. Therefore, what's going to happen is this time, we also get a new scope here, average events. And this time, we have a reference to a list that was the input. But this time, the list has only one reference. Before that, we had two references to the outer numbers list. But this time, we only have one reference. That also means that that implies that after this function call is over, this list object that was the input to the function call is also going away by garbage collection. So that's the difference. So sometimes the input survives. And that is the case if it lives before in the global scope. But if we create a list object on the fly, then this would also be garbage collected. So let's go ahead and see that basically, the function is going to run again. So the whole purpose of having functions in the first place is to reuse them. That is the idea of functions. So giving names to particular pieces of code, but also reusing this code. That's the whole purpose. So now we are running the same code again. And we see the list comprehension runs again. And this time, we get a second list, which is smaller than the first one, which only contains the even numbers. We stored another name events. And then we are going to calculate the average. And also, again, the 42.0 floating point object would also be on the right-hand side, of course. And the average is just a reference to this object. And then we see in the next step, we get as the return value, that's the only thing that's going to survive after the function is done, the 42.0. So these two 42.0 are actually the same 42.0. And then this is given back to the global scope and restores the variable result as 42.0. So these are two examples of how to execute functions. And now what do we learn from this? We learn the rule number one, the scoping rule number one, that local scope goes away. And local scope is the scope that belongs to one function called as it is being executed. And after the execution is done, then the local scope disappears. That is rule one. OK, let's click on Edit this code. And let's change the example a little bit. So let's, first of all, get rid of the second function call here. That was just to illustrate a point. They also get rid of the one here, of course. And now let's do the following. Maybe let's construct another example. Let's make on purpose a mistake. So let's go ahead. And instead of using the formal parameter integers that we have here, let's refer to here to the global numbers object. And now, of course, maybe it's actually a good idea to keep the second example. So that will illustrate a point further on. So let's get the second example back. So let's have 40, 41, 42, 43, and 44. So now what's the difference? The difference here is that I made a mistake on purpose. So instead of using the formal parameters integers, I'm referring to numbers. So now let's see what that means as the program runs. We create the global object numbers again. We create the function object again. And we see that functions are just objects, just like anything else. Now we call the function again. And pass it as the input, a reference to the outer list. And we call it integers. Now in the next step, the list comprehension starts to run. And the list comprehension actually refers to numbers as we see. And that it was on purpose a mistake. However, so now as the list comprehension runs, these numbers are drawn via the numbers variable. So list comprehension, the list comprehension tries to look up numbers. And it goes into the function scope, so one scope up. But it does not find it. So what the list comprehension does is it goes one further step up. And then in the global scope, in the outermost scope, so to say, it finds the variable numbers. And then it looks up the list. And this is where the numbers come from. So these numbers, the n's here, they do not come from the reference that starts at integers from the function scope. But the numbers n, they come actually from the numbers reference up here in the global scope. So let's do that. And luckily, because both references here go to the same list object, we actually get the correct result. So if we calculate the result, it will still be 7.0. However, now we are going to create a new list object with the numbers 40 through 44. And the format parameter integers is going to refer it. And now we are going to calculate the average of the events. And we should calculate the average of the events of this list, however. Because there is a mistake in the code, we use numbers instead of integers. What's going to happen is the list comprehension starts to run. And it tries to look up the n's, but it does not look up numbers. So it tries to look up numbers. It does not find numbers here again as before. And this time, it also finds numbers up here. But this time, numbers and integers go to two different lists. Therefore, we see that the n's down here in the list comprehension, they come from the outer numbers list, which is actually a semantic mistake. So if we run through this, then we get back a new events list. And the events are drawn from the outer numbers list. And that's a mistake. And if we now calculate the average, we get back 7.0 again. And that is, of course, wrong. So if we calculate the average of the numbers 40 through 44 here, we should get 42.0. So that's a mistake. And that shows us the second rule, the second scoping rule. And the rule is that global scope is everywhere. And what do we mean by this rule? Well, we mean that let me go back a couple of steps. Whenever we have several scopes, just as we have here, the global scope, the function scope, and the list comprehension scope, what happens is Python will, from inside out, look up the variables. And the first one it finds, that's the one it uses. And if it does not find a variable in the inner scope, then it goes to the outer scope. And if it finds it, then this works. It can see. So from the inside, we can see the variables on the outer side. But the other way is not possible. And that is a common source of errors. And now let's go back and modify the example even more to see the third rule for scoping. So let's go ahead and now rename the formal parameter integers into simply numbers. And now we could also keep both examples here. That's not a big deal. But now we have numbers as the formal parameter. And we also have numbers here as the outer list object. So let's see what happens. So what's going to happen, we have a global numbers list. We have a function object. And now the function is being called. And the formal parameter is now not integers anymore, but numbers. And now we have two number objects, numbers objects. We have numbers in the function scope. And we have numbers in the global scope. So now if the list companion starts to run and now it wants to look up numbers, it follows numbers from the inside here. Okay? So it does not make a difference in this example because both numbers from within the function but also from outside the function refer to the same function object. So the result would be the same no matter what. So now we get back 7.0 if I fast forward. But now let's see what happens if I call average events with a list object created on the fly. So let's create the 40 through 44 list. And now this is being called numbers, not integers. And now the list comprehension starts to run. And also the list comprehension will look up numbers from the inside out. And now numbers in the inside and the outside refer to different list objects. And now of course the function is capable of using as we see here, the correct numbers. And now we get back as the result 42.0. And the third rule of scoping, I refer to that as the concept of shadowing. So what do I mean by that? The inner numbers, shadows, or the shadow of the inner numbers goes basically over the covers, the outer numbers. So in other words, Python does not see the outer numbers. So coming from within, we only see the inner numbers. And again, the shadow makes it, it's shadow makes it impossible to see the outer numbers. That's what I mean by shadowing. So these are three scoping rules, local scope goes away. We also see that here, the local scope goes away. The global scope can be seen everywhere. And shadowing is also something you have to keep in mind. So shadowing always occurs when you reuse the same name in a different scope, in a different frame to use the terminology here. So that are the three scoping rules in Python tutor. So sometimes you will see me draw those diagrams by hand, but sometimes we just use Python tutor. And again, for Python tutor, they make some simplifications, but we can live with that. And the big point is that you understand those three scoping rules. So I'll see you in the next video.