Explains how def creates function objects and how calls create frames in the Environment Model; covers parameters vs. arguments, first-class functions, lambda, and closures.
Abstraction
We will be discussing how functions fit into our Environment Model. As we build larger systems, we need to have ways to break them down. We can think about primitives, namely the smallest/simplest building blocks. We should also consider a means of combination: how do we tie those building blocks together? We also need a means of abstraction to treat more complicated structures as building blocks themselves.
The third idea is most important and allows us to keep steering the ship when things get huge. We want to be able to build a complicated structure, give it a name, and then call it as something simple in an even larger structure. This idea has legs.
We can apply these three principles to Python:
Python has primitive operations, including arithmetic (+, *, etc.), comparisons, bool operators, build-in functions (abs, len, etc.), and more.
We can also combine these things in conditionals, loops, and function composition (f(g(x)))).
When we have a system built, we can then abstract it through functions (def or lambda)
The beauty of functions is that all the interesting behavior follows from a small set of rules. If we understand those rules, we can understand all the high-level behaviors functions have.
The def keyword in Python creates a new function object in the heap, containing the formal parameters of the function (internal names for arguments), the code, and a reference (enclosing frame) to the frame we were running in when we encountered the def statement. It then associates that function object with a name in the frame in which we were running.
Example
def quad_eval(a, b, c, v): """ Given coefficients a, b, and c representing a quadratic polynomial ax^2 + bx + c, as well as a value v, return the value of the polynomial at the given point. """ term1 = a * v ** 2 term2 = b * v return term1 + term2 + c
Notice that we don’t run the code in the function; we just store this information in an object which we can later call.
In our environment diagram, the top region contains the parameters of the function, the bottom region contains the body, and the little box at the top right shows a reference back to the frame we were running in. We call this the enclosing frame. Note that we also associated this object with a name in the global frame.
When we call a function, Python performs the following:
Evaluates the function to be called followed by its arguments
Creates a new frame for the function call
Binds the names of the parameters of the function to the arguments passed in, inside the new frame
Executes the body of the function.
If Python tries to evaluate a name not bound in the frame, it looks in the parent frame
Note
There is a distinction between arguments and parameters.
parameters are variables used inside the function
arguments are values passed when the function is called, becoming the values of the parameters upon execution
Functions are “First-Class” objects
Functions are first-class, meaning they can be manipulated in many ways that other data can. Our environment diagrams reflect this, as they are represented by objects on the right side. We can even pass functions as arguments!
We can use _ for intentionally-unused variables so linters don’t complain about unused variable names.
Lambda
Python has another way of defining functions: lambda.
Example
Square of a number:
lambda x: x * x
The function returns whatever is outputted from the expression after the colon.
Closures
A function object remembers the frame in which it was defined, so it has access to the variables in that frame and can reference them from its body. We call this combination of a function and its enclosing frame a closure.
Example
x = 0def outer(): x = 1 def inner(): print('inner:', x) inner() print('outer:', x)print('global:', x)outer()inner()print('global:', x)