154 lines
3.6 KiB
Markdown
154 lines
3.6 KiB
Markdown
|
\[ [Index](index.md) | [Exercise 5.3](ex5_3.md) | [Exercise 5.5](ex5_5.md) \]
|
||
|
|
||
|
# Exercise 5.4
|
||
|
|
||
|
*Objectives:*
|
||
|
|
||
|
- Learn more about closures
|
||
|
|
||
|
In this section, we look briefly at a few of the more unusual aspects of
|
||
|
closures.
|
||
|
|
||
|
## (a) Closures as a data structure
|
||
|
|
||
|
One potential use of closures is as a tool for data encapsulation. Try this
|
||
|
example:
|
||
|
|
||
|
```python
|
||
|
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:
|
||
|
|
||
|
```python
|
||
|
>>> 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.
|
||
|
|
||
|
## (b) Closures as a code generator
|
||
|
|
||
|
In [Exercise 4.3](ex4_3.md), you developed a collection of
|
||
|
descriptor classes that allowed type-checking of object attributes.
|
||
|
For example:
|
||
|
|
||
|
```python
|
||
|
|
||
|
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:
|
||
|
|
||
|
```python
|
||
|
# 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:
|
||
|
|
||
|
```python
|
||
|
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:
|
||
|
|
||
|
```python
|
||
|
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
|
||
|
```
|
||
|
|
||
|
## (c) Challenge: Eliminating names
|
||
|
|
||
|
Modify the `typedproperty.py` code so that attribute names are no-longer required:
|
||
|
|
||
|
```python
|
||
|
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.
|
||
|
|
||
|
\[ [Solution](soln5_4.md) | [Index](index.md) | [Exercise 5.3](ex5_3.md) | [Exercise 5.5](ex5_5.md) \]
|
||
|
|
||
|
----
|
||
|
`>>>` Advanced Python Mastery
|
||
|
`...` A course by [dabeaz](https://www.dabeaz.com)
|
||
|
`...` Copyright 2007-2023
|
||
|
|
||
|
. This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
|