Welcome, guest | Sign In | My Account | Store | Cart

When creating a class, we often end up writing lots get/set methods which essentially do the same thing e.g. get_name, get_age, ... , set_name, set_age, ... - each such method will simply set or return the value of its associated attribute.

This recipe is a stategy for automating the creation of such simple get/set methods and exposing them as properties.

Python, 86 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# person.py
# Let's make a person!
from accessor import Accessorizor, make_properties, init_from_attrs

class Person(Accessorizor):
    # delcare read-only and read-write attributes
    _attrs = { 'ro'  : ('name' , 'race', 'father'), 'rw' : ('alias',  'loves') }

    def __init__(self, **kwds):
        init_from_attrs(self, kwds, class_ = Person)

make_properties(Person)

class Geek(Person):
    _attrs = {'rw': ('fav_os', 'fav_hero'), 'ro' : ('iq',)}
    def __init__(self, **kwds):
        Person.__init__(self, **kwds) # initialize superclass attributes
        init_from_attrs(self, kwds, class_ = Geek)

make_properties(Geek)

# Now let's use them
if __name__ == '__main__':
    p = Person(name = 'aragorn', race = 'man', alias = 'elessar', father = 'arathorn')

    print p.__dict__
    try:
        p.race = 'dwarf'
    except AttributeError:
        print "Can't change attribute 'race'"
    p.alias = 'strider'
    print "name = %s, alias = %s, race = %s" % (p.name, p.alias, p.race)

    p.loves = 'arwen'
    print p.__dict__

    g = Geek(name='Palin', race='dwarf', iq=200)
    print g.__dict__
    print "name = %s, race = %s" %(g.name, g.race) # test inherited attrs
    g.fav_os = 'freebsd'
    g.fav_hero = 'aragorn'
    g.loves = 'gold'
    print g.__dict__


# Under the hood ...
# accessor.py

class Accessorizor(object):
    """ A base class for classes wanting to use make_properties and init_from_attrs.
         Subclasses must have an attribute called _attrs of the form:
         { 'rw' : ('read_write_attr1', ...), 'ro': ('read_only_attr1', ...) }
    """
    ro_attrs = classmethod(lambda class_: class_._attrs['ro'])
    rw_attrs = classmethod(lambda class_: class_._attrs['rw'])

def init_from_attrs(obj, kwds, class_ = None):
    """Initialize the object using keyword args.
       If the client class C is intended to be subclassed,
       then C should be passed as the class_ parameter, otherwise
       the class_ parameter can be omitted.
    """
    if class_ is None:
        class_ = obj.__class__
    for attr in class_.ro_attrs() + class_.rw_attrs():
        attr_private = privatize(obj.__class__, attr)
        if kwds.has_key(attr):
            obj.__dict__[attr_private] = kwds[attr]
        else:
            obj.__dict__[attr_private] = None

def make_properties(class_):
    for attr in class_.rw_attrs():
        setattr(class_, attr, property(fget = make_getter(attr), fset = make_setter(attr)))
    for attr in class_.ro_attrs():
        setattr(class_, attr, property(fget = make_getter(attr)))

def make_getter(attr):
    return lambda obj: obj.__dict__[privatize(obj.__class__, attr)]

def make_setter(attr):
    def setter(obj, val): obj.__dict__[privatize(obj.__class__, attr)] = val
    return setter

def privatize(class_, attr):
    return "_%s__%s" % (class_.__name__, attr) 

In accessor.py, make_getter and make_setter return closures. A closure is a nested function that remembers the variables from its surrounding scope that were used in its body.

Used in this way, a closure is in effect a function template - make_getter and make_setter produce functions that are identical except for the name of the attribute they are providing access to.

Helpful references: Python in a Nutshell

Created by Arun Persad on Tue, 23 Dec 2003 (PSF)
Python recipes (4591)
Arun Persad's recipes (1)

Required Modules

Other Information and Tasks