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