python-mastery/Exercises/ex5_4.md
2023-07-16 20:21:00 -05:00

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
![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)