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

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, 41 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
"""
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())

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.

12 comments

Raymond Hettinger 20 years, 10 months ago  # | flag

Nice use of an enclosing namespace.

David Niergarth 20 years, 10 months ago  # | flag

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())
Sean Ross (author) 20 years, 10 months ago  # | flag

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)

Stephan Diehl 20 years, 10 months ago  # | flag

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
Sean Ross (author) 20 years, 10 months ago  # | flag

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

Stephan Diehl 20 years, 10 months ago  # | flag

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

Michele Simionato 20 years, 10 months ago  # | flag

Nice, indeed.

boff1984 20 years, 1 month ago  # | flag

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)
Sean Ross (author) 20 years, 1 month ago  # | flag

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.

Denis Otkidach 20 years, 1 month ago  # | flag

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.

Sean Ross (author) 20 years, 1 month ago  # | flag

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)
    ...
runsun pan 14 years, 12 months ago  # | flag

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.

Created by Sean Ross on Thu, 12 Jun 2003 (PSF)
Python recipes (4591)
Sean Ross's recipes (8)

Required Modules

  • (none specified)

Other Information and Tasks