Skip to content

Latest commit

 

History

History
153 lines (115 loc) · 3.56 KB

ex5_4.md

File metadata and controls

153 lines (115 loc) · 3.56 KB

[ Index | Exercise 5.3 | Exercise 5.5 ]

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:

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:

>>> 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, you developed a collection of descriptor classes that allowed type-checking of object attributes. For example:

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:

# 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:

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:

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:

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 | Index | Exercise 5.3 | Exercise 5.5 ]


>>> Advanced Python Mastery
... A course by dabeaz
... Copyright 2007-2023

. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License