def function_name (parameter_name_1, ..., parameter_name_n): statement ... statement
The def keyword takes the body of the function definition (everything indented beneath the line starting with def) and packages it into a function object that can be evaluated later. It then associates that function object with the function_name
you provided. It also takes the list of parameter names along with any default values and stores them in the function object.
>>> def the_answer(): ... return 42 ... >>> the_answer() 42 >>> the_answer <function> >>> f = the_answer >>> f() 42
Notice that if we type the name of the function rather than a call to the function at the REPL prompt, the system tells us that it is a
function
. Furthermore, notice that we can assign the value of a function (not it's result) to a variable, then use that to call the function.
Creating a function within a function
Remember when we talked about scope: the part of the code in which a name is known and has a value? It so happens that the body of a function is a scope. Things like the function's parameters live in that scope. So do local variables you create (in Python you create local variables simply by assigning a value to a name within the scope). So do functions you define in that scope. Yes, you can create functions that are local to a function.
Here is a function to find square roots. It uses several helper functions. Since these helper functions are specific to the sqrt function, we define them inside it.
def sqrt(x): def square(a): return a * a def good_enough(g): return abs(square(g) - x) < 0.001 def average(a, b): return (a + b) / 2 def improve(g): return average(g, x / g) guess = 1.0 while not good_enough(guess): guess = improve(guess) return guess
If you take a minute to understand this code, you might think "Why can't you just write it like this:"
def sqrt(x): guess = 1.0 while abs(guess * guess - x) > 0.001: guess = (guess + x / guess) / 2 return guess
You could. It works the same. And if you were constrained for space, like on a SAMD21, it could be worth the denser code. But this series is about things you can do on the SAMD51 MCU. You have room to write nicer, cleaner, better code. The first version is easier to read, understand, and tweak as required. That's worth some space, if you can afford it. The first version is simply the second decomposed into meaningful chunks and given names that explain what each one does. Because they are defined inside the sqrt
function, they don't pollute the namespace with single use functions.
Returning functions
Functions in Python are first class data. One thing that means is that they can be created in another function and returned. That's pretty cool. As an example, consider the following:
def make_adder(x): def adder(a): return a + x return adder
The function make_adder
defines and returns a function that has one parameter and returns the sum of it's argument and the argument to make_adder
when the returned function was defined. Whew! Now we can try it out.
>>> inc = make_adder(1) >>> inc <closure> >>> inc(3) 4 >>> inc(5) 6
That's interesting. The type of the returned function isn't function
, it's closure
. A closure is the combination of a function and the scope in which it was defined. In this example, the returned function refers to its creator function's parameter x
. When the function is executed later (and is a different scope) it still has a hold of the value that x
had when it was created: 1 in this case.
We can save the returned closure in a variable (we called it inc
) and call it later just like a regularly defined function. Given the above we can now do:
>>> dec = make_adder(-1) >>> dec(1) 0 >>> dec(5) 4 >>> inc(3) 4
The dec
function is totally separate from inc
, which continues to work as before. Each time a new function is created and returned by make_adder
it has a different copy of the scope, and so its own value for x
.
def sqrt(x): def square(a): return a * a def good_enough(g): return abs(square(g) - x) < 0.001 def average(a, b): return (a + b) / 2 def improve(g): return average(g, x / g) guess = 1.0 while not good_enough(guess): guess = improve(guess) return guess
At the core of this is a general algorithm for finding a solution:
while not good_enough(guess): guess = improve(guess)
In English this is "While your guess isn't good enough, make a better guess."
The algorithm doesn't really care what good_enough
and improve
mean. Because functions in Python are first class data, they can not only be returned, but also used as arguments. So we can write a general solver:
def solver(good_enough, improve, initial_guess): def solve(x): guess = initial_guess while not good_enough(x, guess): guess = improve(x, guess) return guess return solve
Now we can use this to create a square root solver given the two functions and an initial guess:
def sqrt_good_enough(x, guess): def square(a): return a * a return abs(square(guess) - x) < 0.001 def sqrt_improve(x, guess): def average(a, b): return (a + b) / 2 return average(guess, x / guess)
Putting it all together:
sqrt = solver(sqrt_good_enough, sqrt_improve, 1.0) >>> sqrt(25) 5.00002
Page last edited March 31, 2024
Text editor powered by tinymce.