Introduction
This section introduces the idea of using functions to create other functions.
This section introduces the idea of using functions to create other functions.
Consider the following function.
def add(x, y):
def do_add():
print('Adding', x, y)
return x + y
return do_add
This is a function that returns another function.
>>> a = add(3,4)
>>> a
<function add.<locals>.do_add at 0x7f27d8a38790>
>>> a()
Adding 3 4
7
Observe how the inner function refers to variables defined by the outer function.
def add(x, y):
def do_add():
## `x` and `y` are defined above `add(x, y)`
print('Adding', x, y)
return x + y
return do_add
Further observe that those variables are somehow kept alive after add()
has finished.
>>> a = add(3,4)
>>> a
<function do_add at 0x6a670>
>>> a()
Adding 3 4 ## Where are these values coming from?
7
When an inner function is returned as a result, that inner function is known as a closure.
def add(x, y):
## `do_add` is a closure
def do_add():
print('Adding', x, y)
return x + y
return do_add
Essential feature: A closure retains the values of all variables needed for the function to run properly later on. Think of a closure as a function plus an extra environment that holds the values of variables that it depends on.
Closure are an essential feature of Python. However, their use if often subtle. Common applications:
Consider a function like this:
def after(seconds, func):
import time
time.sleep(seconds)
func()
Usage example:
def greeting():
print('Hello Guido')
after(30, greeting)
after
executes the supplied function... later.
Closures carry extra information around.
def add(x, y):
def do_add():
print(f'Adding {x} + {y} -> {x+y}')
return do_add
def after(seconds, func):
import time
time.sleep(seconds)
func()
after(30, add(2, 3))
## `do_add` has the references x -> 2 and y -> 3
Closures can also be used as technique for avoiding excessive code repetition. You can write functions that make code.
One of the more powerful features of closures is their use in generating repetitive code. If you refer back to Exercise 5.7, recall the code for defining a property with type checking.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
...
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value, int):
raise TypeError('Expected int')
self._shares = value
...
Instead of repeatedly typing that code over and over again, you can automatically create it using a closure.
Make a file typedproperty.py
and put the following code in it:
## typedproperty.py
def typedproperty(name, expected_type):
private_name = '_' + name
@property
def prop(self):
return getattr(self, private_name)
@prop.setter
def prop(self, value):
if not isinstance(value, expected_type):
raise TypeError(f'Expected {expected_type}')
setattr(self, private_name, value)
return prop
Now, try it out by defining a class like this:
from typedproperty import typedproperty
class Stock:
name = typedproperty('name', str)
shares = typedproperty('shares', int)
price = typedproperty('price', float)
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
Try creating an instance and verifying that type-checking works.
>>> s = Stock('IBM', 50, 91.1)
>>> s.name
'IBM'
>>> s.shares = '100'
... should get a TypeError ...
>>>
In the above example, users might find calls such as typedproperty('shares', int)
a bit verbose to type--especially if they're repeated a lot. Add the following definitions to the typedproperty.py
file:
String = lambda name: typedproperty(name, str)
Integer = lambda name: typedproperty(name, int)
Float = lambda name: typedproperty(name, float)
Now, rewrite the Stock
class to use these functions instead:
class Stock:
name = String('name')
shares = Integer('shares')
price = Float('price')
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
Ah, that's a bit better. The main takeaway here is that closures and lambda
can often be used to simplify code and eliminate annoying repetition. This is often good.
Congratulations! You have completed the Returning Functions lab. You can practice more labs in LabEx to improve your skills.