ActiveState Code

Recipe 205183: A tidy property idiom


This recipe suggests an idiom for property creation that avoids cluttering the class space with get/set/del methods that will not be used directly.

Python
 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
"""
Rather than defining your get/set/del methods at the class level, as is usually done, e.g.

class MyClass(object):
    def __init__(self):
        self._foo = "foo"

    def getfoo(self):
        return self._foo
    def setfoo(self, value):
        self._foo = value
    def delfoo(self):
        del self._foo
    foo = property(getfoo, setfoo, delfoo, "property foo's doc string")

I would like to suggest the following alternative idiom:
"""


class MyClass(object):
    def __init__(self):
        self._foo = "foo"
        self._bar = "bar"

    def foo():
        doc = "property foo's doc string"
        def fget(self):
            return self._foo
        def fset(self, value):
            self._foo = value
        def fdel(self):
            del self._foo
        return locals()  # credit: David Niergarth
    foo = property(**foo())

    def bar():
        doc = "bar is readonly"
        def fget(self):
            return self._bar
        return locals()    
    bar = property(**bar())

Discussion

Using the standard idiom to create properties unnecessarily clutters the class space with get/set/del methods that will not be called directly by users of your class (although, of course, they could be). Using the above idiom removes the get/set/del methods from the class space by nesting them inside of a function with the same name as the property you will create. This function is then used to create your new property, at which time said function will no longer have any referrers. As such, it will not be accessible to users of your class - only the newly created property will remain accessible.

Following this idiom is, of course, unnecessary as the standard method works just fine. And, in fact, using this idiom for creating properties like bar (above), will seem excessive. Still, it does provide a clean method for restricting the avenues of access to your classes by their users - if there are no get/set/del methods available, then users of your classes will have to use your property, which was your intention.

NOTE: see comment "Just so users are aware" below.

Comments

  1. 1. At 9:34 p.m. on 15 jun 2003, Raymond Hettinger said:

    Nice use of an enclosing namespace.

  2. 2. At 12:50 a.m. on 20 jun 2003, David Niergarth said:

    A variation. property() accepts the keyword arguments fget, fset, fdel, and doc. A variation would be to use those names and return locals() rather an explicit tuple. For example,

    def foo():
        doc = "property foo's doc string"  # use doc rather than fdoc
        def fget(self):
            return self._foo
        def fset(self, value):
            self._foo = value
        def fdel(self):
            del self._foo
        return locals()  # returns a dictionary
    foo = property(**foo())  # pass in as keyword args
    

    Returning locals() does seem to violate "explicit is better than implicit" but it simplifies the return value a little for read-only properties. Here's the read-only property example:

    def bar():
        doc = "bar is readonly"
        def fget(self):
            return self._bar
        return locals()  # rather than:  return fget, None, None, doc
    bar = property(**bar())
    
  3. 3. At 9:45 a.m. on 20 jun 2003, Sean Ross (the author) said:

    Just so users are aware. Using locals() is a clean solution, however, it must be used with care. The nested method names must be as David has prescribed, and you may not introduce any other names into the local scope, e.g.

    def bar():

    doc = "bar's doc string"
    
    def fget(self):
    
        return self._bar
    
    x = 2  # here's a local name that property() won't handle
    
    return locals()
    

    bar = property(**bar())

    will give the following error:

    bar = property(**bar())
    

    TypeError: 'x' is an invalid keyword argument for this function

    My idiom does not suffer from this restriction, however, you are required to return the get/set/del/doc in the correct order, and to not return anything other than those items.

    A possible compromise would be to return an explicit dictionary rather than a tuple or locals(), e.g.

    requires python 2.3+

    return dict(fget=fget, doc=doc)

  4. 4. At 7:38 a.m. on 28 jun 2003, Stephan Diehl said:

    using a metaclass? If one has a lot of getter/setter methods, using a metaclass approach might be easier in the long run. Maybe something like the following. I have to admit though, that the code for the meta class is much more complicated than the original recipe.

    def getmeth(attr):
        def _func(self):
            return getattr(self,'_%s' % attr)
    
        return _func
    
    def setmeth(attr):
        def _func(self,value):
            return setattr(self,'_%s' % attr,value)
    
        return _func
    
    def delmeth(attr):
        def _func(self):
            return delattr(self,'_%s' % attr)
    
        return _func
    
    class attrib(object):
        def __init__(self,default=None,opt='rwd',doc='doc string for %(key)s'):
            self.default = default
            self.opt = opt
            self.doc = doc
    
    class meta(type):
        def __new__(cls,classname,bases,classdict):
            argdict = {}
    
            # we need the appropriate attributes from superclasses
            # going reverse order (first base has highest priority)
    
            revbases = list(bases[:])
            revbases.reverse()
            for klass in revbases:
                if hasattr(klass,'__default_attr__'):
                    for key,value in klass.__default_attr__.items():
                        argdict[key] = value
    
            # look for attributes whos value we are interested in
    
            for key,value in classdict.items():
                if isinstance(value,attrib):
                    argdict[key] = value
                    fset = fget = fdel = None
                    opt = value.opt
                    if 'r' in opt:
                        fget = getmeth(key)
                    if 'w' in opt:
                        fset = setmeth(key)
                    if 'd' in opt:
                        fdel = delmeth(key)
                    classdict[key] = property(fget,fset,fdel,value.doc % locals())
    
            classdict['__default_attr__'] = argdict
    
            # look for __init__ method
            classdict['__original_init__'] = classdict.get('__init__')
    
            # define special init method that sets the default values
            def __init__(self,*argl,**argd):
                for key,value in self.__default_attr__.items():
                    if value.default is not None:
                        setattr(self,'_%s' % key,value.default)
                if self.__original_init__ is not None:
                    self.__original_init__(*argl,**argd)
    
            classdict['__init__'] = __init__
    
            return type.__new__(cls,classname,bases,classdict)
    
    
    class class1(object):
        __metaclass__ = meta
        b = attrib(7)
    
    class class2(class1):
        a = attrib('hallo','rw','doc a')
    
    if __name__ == '__main__':
        c = class2()
        print c.b
        print c.a
    
  5. 5. At 1:01 p.m. on 28 jun 2003, Sean Ross (the author) said:

    The solution you've provided allows for the automated creation of simple properties. But that is not the issue being addressed here. While the examples provided above show only simple properties, the intended use for this idiom is to encapsulate the get/set/del methods of more complex properties, such as the following:

    suppose we're inside a class, and we want to make

    a read-only property 'average', that returns the

    average fitness of all individuals in a population

    def average():

    doc = "returns average fitness of individuals in population"
    
    def fget(self):
    
        return sum([individual.fitness for individual in \
             self.population.individuals])/float(self.population.size)
    
    return dict(fget=fget, doc=doc)
    

    average = property(**average())

    The idea is not about avoiding having to create the get/set/del methods yourself; rather it is about avoiding leaving those methods accessible to users of your class by removing those methods from the class space (where they are normally defined). For simple property creation alone, I would refer people to my own makeproperty recipe, or to your recipe using metaclasses. For a more related metaclass solution to this problem I would refer people to the following attempt:

    http://aspn.activestate.com/ASPN/Mail/Message/python-list/1660895

  6. 6. At 4:27 a.m. on 29 jun 2003, Stephan Diehl said:

    You are right, of course. I guess I got carried away a bit and solved a problem that was not asked for :-)

  7. 7. At 10:08 a.m. on 5 jul 2003, Michele Simionato said:

    Nice, indeed.

  8. 8. At 2:18 p.m. on 14 mar 2004, Anonymous said:

    It seems you can use 'del' This appears to work:

    class foobar(object):
         def __init__(self):
             self._foo='bar'
         def _get_foo(self):
             return self._foo
         def _set_foo(self, val):
             self._foo=val
         foo=property(_get_foo, _set_foo)
         del(_get_foo, _set_foo)
    
  9. 9. At 12:31 p.m. on 15 mar 2004, Sean Ross (the author) said:

    Yes, you can certainly use del to explicitly remove the get/set/del methods from the class' namespace (note: the parenthesis are not required). This idiom does that implicitly for you. There are a couple of other things it does for you:

    It groups the methods together visually through indentation, offsetting them from other method definitions, and tying them more directly to the property definition.

    It allows for a consistent and repeatable naming convention. The names of the get/set/del methods are, or can be, identical for every property. There's no need to have get_foo, set_foo, get_bar, set_bar for properties foo and bar when you can just use fget, fset for both.

    Whether these provide any real benefit can be debated. Personally, I like grouping related things together, and seperating them out from unrelated things. And I like having every property definition look nearly identical, by being able to re-use the names fget, fset, fdel for each one. The fact that these auxiliary methods are also automatically removed from the class' namespace, reducing namespace pollution and unintended avenues for access, is a nice side benefit.

  10. 10. At 7:54 a.m. on 1 apr 2004, Denis Otkidach said:

    I like the idea to use enclosed namespace. Here is another solution:

    class Property:
    
        class __metaclass__(type):
            def __new__(cls, name, bases, dict):
                if dict.get('_is_base'):
                    return type.__new__(cls, name, bases, dict)
                else:
                    return type.__new__(cls, name, bases, dict)()
    
        _is_base = 1
    
        def __get__(self, inst, cls):
            if inst is None:
                return self
            else:
                return self.get(inst)
    
        def __set__(self, inst, value):
            self.set(inst, value)
    
        def __delete__(self, inst):
            self.delete(inst)
    
        def get(self, inst):
            raise AttributeError('unreadable property')
    
        def set(self, inst, value):
            raise AttributeError("can't set attribute")
    
        def delete(self, inst):
            raise AttributeError("can't delete attribute")
    

    Now property definition is very simple:

    class Test(object):
        class foo(Property):
            '''foo docstring'''
            def get(self, inst):
                return inst._foo
    

    Unnecessary self argument can be avoided by automatially aplying staticmethod decorator in metaclass.

  11. 11. At 7:39 a.m. on 2 apr 2004, Sean Ross (the author) said:

    Possible idiom after PEP318. There is ongoing discussion on python-dev about accepting PEP318 - Decorators for Functions, Methods and Classes - for inclusion into Python 2.4. If accepted, some small changes to the property descriptor would allow this idiom to be re-expressed as follows:

    # eventual decorator syntax may differ
    def foo()[property]:
        "property foo's doc string"
        def fget(self):
            return self._foo
        def fset(self, value):
            self._foo = value
        return locals()
    

    This is better, but still not ideal: it's still a function definition masquerading as a property definition; and it requires you to return locals() to gain access to the get/set/del methods. Perhaps, one day, the language will grow a property block, e.g.

    property foo:
        "property foo's doc string"
        def get(self):
            return self._foo
        def set(self, value):
            self._foo = value
    

    As an aside: Ruby's approach is interesting, though not applicable

    def foo
        ...
    def foo=(value)
        ...
    
  12. 12. At 2:15 p.m. on 6 may 2009, runsun pan said:

    Please check out my recipe:

    Easy Property Creation in Python

    http://code.activestate.com/recipes/576742/

    Only 7 lines of code, easy to use, easy to understand, easy to customize, make the code much netter and will save you a lot of typing.

Sign in to comment