TLDW logo

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...

Loading video analysis...