Multiple Environments
By John DeNero
Summary
## Key takeaways - **Environments: Multiple Frames, One Diagram**: Python can evaluate expressions in multiple environments simultaneously, and these can be represented within a single environment diagram, showing a sequence of frames. [00:09] - **Def Statements Create Functions**: A 'def' statement defines a function, giving it a name, formal parameters for arguments, and a body of code to execute. [00:37] - **Function Calls Create New Environments**: When a user-defined function is called, a new frame is created where formal parameters are bound to arguments, and the function's body executes within this new environment. [02:46] - **Environments Define Name Meaning**: Names in Python derive their meaning from environments, which are sequences of frames. A name's value is found in the earliest frame of the current environment where that name is bound. [06:43] - **Name Shadowing in Nested Calls**: A name can have different meanings across environments. When a function is called, its body executes in an environment that starts with a new frame, potentially shadowing names from outer frames. [08:17], [09:30]
Topics Covered
- How do function definition and calling differ?
- How does Python execute a user-defined function?
- What are environments and how many can exist?
- How Python's environment lookup gives names meaning.
- Why call expressions and function bodies evaluate differently?
Full Transcript
When Python executes a program different expressions can be evaluated in different
environments. so there can actually be multiple environments in the same environment diagram.
we're going to look at one of the examples that I showed you last time in detail with diagrams so
that we know exactly what's going on. but first let's review everything that I told you before
about user-defined functions, here on one slide in summary form with all the vocabulary that you need.
so first we have a def statement. a def statement is what creates functions. it looks like this.
that whole thing is a def statement which spans multiple lines. it has a name for the function
you're defining, a formal parameter, which is the name you give to the argument of the function.
there can be more than one formal parameter in which case they're separated by commas, one for
each argument. the body of the DEF statement is everything indented after the first line and
in this case it's just a single return statement though it is possible to have more than one line
this return statement has a return expression which multiplies X and X together. when a def
statement is executed a new function is created. the name, which we see here is square, is bound
to that function in the current frame. now when I've defined this function that squares things
I still haven't actually multiplied anything by anything else because I haven't called that
function yet. that happens with a call expression. so later on in my program I might say square two
plus two. that's a call expression. the operator is the name square. its value is the function that
squares, the one that we just defined. okay there's an operand in between parentheses. an operand is an
expression in this case two plus two. it evaluates to a value, 4, which is the argument of the function
so when we reach a call expression we evaluate it by evaluating the operators and operands and then
calling the function on the arguments. calling or applying a user-defined function is also a
process that we need to spell out so I showed you a diagram that looked like this, which was
the function signature inside of a little tube. in comes the argument out comes the return value
how does this happen? well we start by creating a new frame in which the formal parameters of the
function we're calling are bound to the arguments for passing it. in this case X would be bound to
4 and then the body of the function that we're calling is executed in that new environment so
we would in this case compute the multiplication of X and X where X is bound to 4. 4 times 4 is 16
okay so that's the simplest possible example. a slightly more complicated one is when we import
mul. we define square just as we did before but now we're going to square the square of 3. this diagram
indicates that we've already executed the def statement which bound the name Square to a newly
created square function and we're about to square the square of 3. how do we square the square of 3?
it's a call expression so we just use our rule for evaluating call expressions which means the
operator is evaluated. it means the function that squares. the operand is also a call expression so
we need to apply that procedure again. squares the function that squares. 3 is the number 3. and now
we can apply our user-defined function Square to the number 3. how do we do that? well it has three
steps: first I create a new frame. then I bind the formal parameter X to the argument value 3.
and there's the binding right there. finally I execute the body of the function, which in this
case has returned mul X X, so I have to multiply X and X together I get a return value of 9 and
that's how I get the value of this call expression. now that I know what the value of this operand
expression is, I can apply this function square to the argument 9 and repeat the process again
okay so that means introducing a frame, binding the formal parameter X to the argument value
9, and then executing the body which multiplies 9 times 9 and gives me 81. so let's look around
here for a second. we have one square function. we created two frames from that function by
calling the same function twice. each of those frames is different which is why we gave it a
different frame label f1 or f2. it's also different because we passed in different arguments. so we got
different bindings from the formal parameter to the argument and that led to different return
values. an environment is a sequence of frames, like so far we've had an environment which was
just the global frame alone. we had those before we ever even had def statements. but
once we started calling user-defined functions we started getting multiframe environments, ones
that have a local frame and then a global frame. let's try to find all the different environments.
in this diagram. there's one just the global frame alone. there's another: f1 followed by the
global frame. and there's a third: the frame f2 followed by the global frame. so there's three
different environments here. none of them include all three frames but there's one environment per
frame. if you start with a particular frame you can always find the whole environment just by
following the parents of the frames. so let's say we're interested in the environment that
starts with the frame f2. well we know the next frame is the parent of this frame, which is the
global frame, says right there. global frames don't have any parents; they're always the last frame in
an environment. A very important point is that names have no meaning without these environments
these are the things that endow mul and X and square with some sort of meaning, some value. every
expression is evaluated in the context of an environment which allows us to figure out what
names mean. and a name evaluates to the value bound to that name in the earliest frame of the
current environment in which that name is found. in this case when we evaluate mul X X for the second
time, it's in an environment that starts with the frame f2 followed by the global frame and we have
two different names we have to look up: mul and X so we look up X. the first thing we do is look in
the first frame of the current environment and we find X there. so this is the earliest frame of the
environment in which X is found. so X is 9. when we look up mul we first look at f2 to see if mul is
there. it's not. so then we look in the next frame of the environment, and lo and behold there
is mul bound to the function that multiplies. so, that's another case where we found the name in
the earliest frame of the current environment in which that name was found. it just happened
to be the global frame, but we did check f2 first. names can have different meanings in
different environments because each frame can have a different binding for the same name. in
particular, a call expression and the body of the function being called are evaluated in different
environments. here's an example where we use the name Square for both the name of a function and
the name of the formal parameter. def square square return mul square square. now, I'm not recommending
you do this, but if you square 4 in this case you really do get 16. why is that? well in the
environment diagram for this example there's a global frame in which square is bound to the
squaring function. there's a local frame in which square is bound to 4 because we call the squaring
function on 4 so the difference is that when we evaluate square 4's operator we're evaluating
that expression in the global frame. that's the call expression. it's not indented at all which
indicates that it gets evaluated in the global frame. on the other hand, this line is indented. it's
part of the body of the square function and so it's always going to be executed in an environment
that starts with a square frame because we create this frame and then we execute the body. so this
square is evaluated in an environment that starts with f1 followed by the global frame. but when we
go looking for what square means we always look in f1 first there it is and we found 4. so we never
find this binding because we're only interested in the earliest frame of the current environment.
Loading video analysis...