Exercise 10.3

Objectives:

  • Playing with closures

Files Created: typedproperty.py

Files Modified: None

(a) Using Closures to Avoid Repetition

One of the more powerful features of closures is their use in generating repetitive code. If you refer back to exercise Exercise 6.2, recall the code for defining a property with type checking.

class Stock(object):
    def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price
    ...
    @property
    def shares(self):
        return self._shares

    @shares.setter
    def shares(self,value):
        if not isinstance(value,int):
            raise TypeError('Expected int')
        self._shares = value
    ...

Instead of repeatedly typing that code over and over again, you can automatically create it using a closure.

Make a file typedproperty.py and put the following code in it:

# typedproperty.py

def typedproperty(name,expected_type):
    private_name = '_' + name
    @property
    def prop(self):
        return getattr(self, private_name)

    @prop.setter
    def prop(self, value):
        if not isinstance(value, expected_type):
            raise TypeError('Expected %s' % expected_type)
        setattr(self, private_name, value)

    return prop

Now, try it out by defining a class like this:

from typedproperty import typedproperty

class Stock(object):
    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

Try creating an instance and verifying that type-checking works.

>>> s = Stock('IBM', 50, 91.1)
>>> s.name
'IBM'
>>> s.shares = '100'
... should get a TypeError ...
>>>

(b) Simplifying Function Calls

In the above example, users might find calls such as typedproperty('shares', int) a bit verbose to type—especially if they’re repeated a lot. Add the following definitions to the typedproperty.py file:

String = lambda name: typedproperty(name, str)
Integer = lambda name: typedproperty(name, int)
Float = lambda name: typedproperty(name, float)

Now, rewrite the Stock class to use these functions instead:

class Stock(object):
    name = String('name')
    shares = Integer('shares')
    price = Float('price')

    def __init__(self,name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

Ah, that’s a bit better. The main takeaway here is that closures and lambda can often be used to simplify code and eliminate annoying repetition. This is usually good.

Links