patternpythonCritical
How does the @property decorator work in Python?
Viewed 0 times
propertyhowthepythondoesworkdecorator
Problem
I would like to understand how the built-in function
This example is from the documentation:
In the code below
How are the
property works. What confuses me is that property can also be used as a decorator, but it only takes arguments when used as a built-in function and not when used as a decorator.This example is from the documentation:
class C:
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.")property's arguments are getx, setx, delx and a doc string.In the code below
property is used as a decorator. The object of it is the x function, but in the code above there is no place for an object function in the arguments.class C:
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._xHow are the
x.setter and x.deleter decorators created in this case?Solution
The
It is this object that has extra methods:
These act as decorators too. They return a new property object:
that is a copy of the old object, but with one of the functions replaced.
Remember, that the
really means the same thing as
so
The following sequence also creates a full-on property, by using those decorator methods.
First we create some functions:
Then, we create a
Next we use the
Last we add a deleter with the
Last but not least, the
The Descriptor Howto includes a pure Python sample implementation of the
property() function returns a special descriptor object:>>> property()
It is this object that has extra methods:
>>> property().getter
>>> property().setter
>>> property().deleter
These act as decorators too. They return a new property object:
>>> property().getter(None)
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._fooreally 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
TrueNext 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
TrueLast 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
TrueLast 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__)Code Snippets
>>> 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>>>> property().getter(None)
<property object at 0x10ff079f0>@property
def foo(self): return self._foodef foo(self): return self._foo
foo = property(foo)>>> def getter(self): print('Get!')
...
>>> def setter(self, value): print('Set to {!r}!'.format(value))
...
>>> def deleter(self): print('Delete!')
...Context
Stack Overflow Q#17330160, score: 1343
Revisions (0)
No revisions yet.