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

Presented in this recipe is a function prop, with that a property myprop can be created as simple as:

@prop
def myprop(): pass

It has only 7 lines of code, easy to understand, easy to customize, will make the code look much netter and will save you a lot of typing.

See discussion and the doc test string for more complicated usages.

Note: This is a recipe created with python v.2.5.2.

Python, 73 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
#
#
def prop(func):
    '''A decorator function for easy property creation.

        >>> class CLS(object):
        ...   def __init__(self):
        ...      self._name='Runsun Pan'
        ...      self._mod='panprop'
        ...      self.CLS_crazy = 'Customized internal name'
        ...
        ...   @prop
        ...   def name(): pass           # Simply pass
        ...
        ...   @prop
        ...   def mod():                 # Read-only, customized get
        ...      return {'fset':None,
        ...              'fget': lambda self: "{%s}"%self._mod  }
        ...
        ...   @prop
        ...   def crazy():               # Doc string and customized prefix
        ...      return {'prefix': 'CLS_',
        ...              'doc':'I can be customized!'}

        >>> cls = CLS()

        ----------------------------   default
        >>> cls.name
        'Runsun Pan'

        >>> cls.name = "Pan"
        >>> cls.name
        'Pan'

        ---------------------------   Read-only
        >>> cls.mod
        '{panprop}'

        Trying to set cls.mod=??? will get:
        AttributeError: can't set attribute 

        ---------------------------   Customized prefix for internal name
        >>> cls.crazy       
        'Customized internal name'

        >>> cls.CLS_crazy
        'Customized internal name'

        ---------------------------   docstring 
        >>> CLS.name.__doc__
        ''
      
        >>> CLS.mod.__doc__
        ''
      
        >>> CLS.crazy.__doc__
        'I can be customized!'

        ---------------------------  delete
        >>> del cls.crazy

        Trying to get cls.crazy will get:
        AttributeError: 'CLS' object has no attribute 'CLS_crazy'
      
    '''
    ops = func() or {}
    name=ops.get('prefix','_')+func.__name__ # property name
    fget=ops.get('fget',lambda self:getattr(self, name))
    fset=ops.get('fset',lambda self,value:setattr(self,name,value))
    fdel=ops.get('fdel',lambda self:delattr(self,name))
    return property ( fget, fset, fdel, ops.get('doc','') )
#
#

The standard procedure for creating properties for a class in python is quite tedious. We have to do this using the built-in function property:

class C(object):
    def __init__(self): 
        self._x = None
    def getx(self): return self._x
    def setx(self, value): self._x = value
    def delx(self): del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

That is, creating a single property - even the property is as simple as retrieving or setting an internal variable - requires 1 to 3 property-behavior functions (getx, setx, delx) that are not intended for users access but flood the class instance namespace.

The better way of creating properties would have fit the requirements: (1) No flooding in the instance namespace; (2) Simple usage for basic properties; (3) Customizable for complicated properties.

A good solution making use of the decorator is provided by Walker Hale in a comment to a recipe http://code.activestate.com/recipes/410698/ :

def newProp( fcn ):
    return property( **fcn() )

class Example(object):

    @newProp
    def myattr():
        doc = """This is the doc string."""

        def fget(self):
            return self._value

        def fset(self, value):
            self._value = value

        def fdel(self):
            del self._value

        return locals()

It moves the property-behavior functions from the class instance namespace to the locals() namespace of a function, which is returned to a decorator newProp that uses it to execute and return the result of the built-in property function.

The problem of namespace flooding is solved. But it still requires tedious input of functions for every single simple property.

I asked the question: Why not just transfer the burden of defining these *property-behavior functions to the decorator ?*

That's what I present here. In short, it instructs the decorator to take charge of defining property-behavior, so users don't have to type in tedious code for every property. Thus, creating simple, bare-bone properties can't be easier:

class MyCls(object):
    def __init__(self):
        self._p1=0
        self._p2='test'
        self._p3=[]

    @prop
    def p1():pass

    @prop
    def p2():pass

    @prop
    def p3():pass

More complicated properties can be created by returning a dict (instead of just pass):

Customized behavior functions

@prop
def count(): 
    return {'fget':lambda self: self.start+self._count}

@prop
def aName():
    return {'fset':lambda self,v: self._aName= 'Dr. '+aName }

Read-only:

@prop
def reader(): return {'fset':None}

Undead:

@prop
def reader(): return {'fdel':None}

Define docstring:

@prop
def documented(): 
    return {'doc': 'A documented property'}

Every property of name X assumes that the internal variable to store its value is _X, which can be customized. See the example CLS_crazy in docstring.

2 comments

wormwiz 14 years, 9 months ago  # | flag

Correct me if I'm wrong, but isn't it impossible to assign a variable from within a lambda function? That would make this example wrong:

@prop
def aName():
    return {'fset':lambda self,v: self._aName= 'Dr. '+aName }
Martin Miller 13 years, 8 months ago  # | flag

@wormwiz is correct, functions created with lambda expressions cannot contain statements. However in this case that can easily be worked around with the following (tested) code:

@prop
def aName():
    return {'fset': lambda self,v: setattr(self, '_aName', 'Dr. '+v) }
Created by runsun pan on Wed, 6 May 2009 (MIT)
Python recipes (4591)
runsun pan's recipes (5)

Required Modules

  • (none specified)

Other Information and Tasks