\[ [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/)