How does the @property decorator work in Python? How does the @property decorator work in Python? python python

How does the @property decorator work in Python?


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:

@propertydef foo(self): return self._foo

really means the same thing as

def foo(self): return self._foofoo = 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 and a property object with just a getter:

>>> def getter(self): print('Get!')... >>> def setter(self, value): print('Set to {!r}!'.format(value))... >>> def deleter(self): print('Delete!')... >>> prop = property(getter)>>> prop.fget is getterTrue>>> prop.fset is NoneTrue>>> prop.fdel is NoneTrue

Next we use the .setter() method to add a setter:

>>> prop = prop.setter(setter)>>> prop.fget is getterTrue>>> prop.fset is setterTrue>>> prop.fdel is NoneTrue

Last we add a deleter with the .deleter() method:

>>> prop = prop.deleter(deleter)>>> prop.fget is getterTrue>>> prop.fset is setterTrue>>> prop.fdel is deleterTrue

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__)


Documentation says it's just a shortcut for creating readonly properties. So

@propertydef x(self):    return self._x

is equivalent to

def getx(self):    return self._xx = property(getx)


Here is a minimal example of how @property can be implemented:

class Thing:    def __init__(self, my_word):        self._word = my_word     @property    def word(self):        return self._word>>> print( Thing('ok').word )'ok'

Otherwise word remains a method instead of a property.

class Thing:    def __init__(self, my_word):        self._word = my_word    def word(self):        return self._word>>> print( Thing('ok').word() )'ok'