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