Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the @property decorator work in Python?

I would like to understand how the built-in function 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._x 

How are the x.setter and x.deleter decorators created in this case?

like image 452
ashim Avatar asked Jun 26 '13 20:06

ashim


People also ask

How does @property work in Python?

The @property is a built-in decorator for the property() function in Python. It is used to give "special" functionality to certain methods to make them act as getters, setters, or deleters when we define properties in a class.

What is @property decorator in Python?

The @property Decorator In Python, property() is a built-in function that creates and returns a property object. The syntax of this function is: property(fget=None, fset=None, fdel=None, doc=None) where, fget is function to get value of the attribute. fset is function to set value of the attribute.

How does a decorator works in Python?

A decorator in Python is a function that takes another function as its argument, and returns yet another function . Decorators can be extremely useful as they allow the extension of an existing function, without any modification to the original function source code.

How is Python property decorator implemented?

The property decorator is implemented with a pattern similar to the my_decorator function. Using the Python @decorator syntax, it receives the decorated function as an argument, just like in my example: some_func_decorated = my_decorator(some_func) . Note that I changed some function names for clarity.


2 Answers

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 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 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__) 
like image 189
Martijn Pieters Avatar answered Oct 13 '22 11:10

Martijn Pieters


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

@property def x(self):     return self._x 

is equivalent to

def getx(self):     return self._x x = property(getx) 
like image 22
J0HN Avatar answered Oct 13 '22 11:10

J0HN