How does the @property decorator work?

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(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.")

property's arguments are getx, setx, delx and a doc string.

In the code below property is used as 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(object):
        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

And, how are the x.setter and x.deleter decorators created? I am confused.

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(object): 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(object):
>         "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__)
>

From: stackoverflow.com/q/17330160