Introduction
Objectives:
- Learn more about closures
In this section, we look briefly at a few of the more unusual aspects of closures.
Objectives:
In this section, we look briefly at a few of the more unusual aspects of closures.
One potential use of closures is as a tool for data encapsulation. Try this example:
def counter(value):
def incr():
nonlocal value
value += 1
return value
def decr():
nonlocal value
value -= 1
return value
return incr, decr
This code defines two inner functions that manipulate a value. Try it out:
>>> up, down = counter(0)
>>> up()
1
>>> up()
2
>>> up()
3
>>> down()
2
>>> down()
1
>>>
Notice how there is no class definition involved here. Moreover, there is no global variable either. Yet, the up()
and down()
functions are manipulating some "behind the scenes" value. It's fairly magical.
In Exercise 4.3, you developed a collection of descriptor classes that allowed type-checking of object attributes. For example:
class Stock:
name = String()
shares = Integer()
price = Float()
This kind of thing can also be implemented using closures. Define a file typedproperty.py
and put the following code in it:
## typedproperty.py
def typedproperty(name, expected_type):
private_name = '_' + name
@property
def value(self):
return getattr(self, private_name)
@value.setter
def value(self, val):
if not isinstance(val, expected_type):
raise TypeError(f'Expected {expected_type}')
setattr(self, private_name, val)
return value
This look pretty wild, but the function is effectively making code. You'd use it in a class definition 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
Verify that this class performs type-checking in the same way as the descriptor code.
Add function String()
, Integer()
, and Float()
to the typedproperty.py
file so that you can write the following code:
from typedproperty import String, Integer, Float
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
Modify the typedproperty.py
code so that attribute names are no-longer required:
from typedproperty import String, Integer, Float
class Stock:
name = String()
shares = Integer()
price = Float()
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
Hint: To do this, recall the __set_name__()
method of descriptor objects that gets called when descriptors are placed in a class definition.
Congratulations! You have completed the Learn More About Closures lab. You can practice more labs in LabEx to improve your skills.