Videos
The property() function returns a special descriptor object:
>>> property()
<property object at 0x10ff07940>
It is this object that has extra methods:
>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>
These act as decorators too. They return a new property object:
>>> property().getter(None)
<property object at 0x10ff079f0>
that is a copy of the old object, but with one of the functions replaced.
Remember, that the @decorator syntax is just syntactic sugar; the syntax:
@property
def foo(self): return self._foo
really means the same thing as
def foo(self): return self._foo
foo = property(foo)
so foo the function is replaced by property(foo), which we saw above is a special object. Then when you use @foo.setter(), what you are doing is call that property().setter method I showed you above, which returns a new copy of the property, but this time with the setter function replaced with the decorated method.
The following sequence also creates a full-on property, by using those decorator methods.
First we create some functions:
>>> def getter(self): print('Get!')
...
>>> def setter(self, value): print('Set to {!r}!'.format(value))
...
>>> def deleter(self): print('Delete!')
...
Then, we create a property object with only a getter:
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True
Next we use the .setter() method to add a setter:
>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True
Last we add a deleter with the .deleter() method:
>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True
Last but not least, the property object acts as a descriptor object, so it has .__get__(), .__set__() and .__delete__() methods to hook into instance attribute getting, setting and deleting:
>>> class Foo: pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!
The Descriptor Howto includes a pure Python sample implementation of the property() type:
class Property: "Emulate PyProperty_Type() in Objects/descrobject.c" def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError("unreadable attribute") return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(obj) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__)
The documentation says it's just a shortcut for creating read-only properties. So
@property
def x(self):
return self._x
is equivalent to
def getx(self):
return self._x
x = property(getx)
Other examples would be validation/filtering of the set attributes (forcing them to be in bounds or acceptable) and lazy evaluation of complex or rapidly changing terms.
Complex calculation hidden behind an attribute:
class PDB_Calculator(object):
...
@property
def protein_folding_angle(self):
# number crunching, remote server calls, etc
# all results in an angle set in 'some_angle'
# It could also reference a cache, remote or otherwise,
# that holds the latest value for this angle
return some_angle
>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276
Validation:
class Pedometer(object)
...
@property
def stride_length(self):
return self._stride_length
@stride_length.setter
def stride_length(self, value):
if value > 10:
raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
else:
self._stride_length = value
One simple use case will be to set a read only instance attribute , as you know leading a variable name with one underscore _x in python usually mean it's private (internal use) but sometimes we want to be able to read the instance attribute and not to write it so we can use property for this:
>>> class C(object):
def __init__(self, x):
self._x = x
@property
def x(self):
return self._x
>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError Traceback (most recent call last)
AttributeError: can't set attribute