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.
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