Coder Perfect

In Python, how does the @property decorator work?

Problem

I’m curious in how the built-in function property works. What perplexes me is that property may be used as a decorator as well, yet it only accepts parameters when used as a built-in function, not as a decorator.

The following is an example 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.")

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

Property is used as a decorator in the code below. The x function is the object of it, but there is no place for an object function in the arguments in the code above.

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?

Asked by ashim

Solution #1

The property() function returns a descriptor object with the following properties:

>>> property()
<property object at 0x10ff07940>

This object has additional 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 also serve as decorations. They provide you a new property object to work with:

>>> property().getter(None)
<property object at 0x10ff079f0>

That’s a clone of the original object, but one of the functions has been changed.

tic sugar; the syntax is as follows:

@property
def foo(self): return self._foo

means exactly the same thing as

def foo(self): return self._foo
foo = property(foo)

as a result The function is replaced by property(foo), which is a special object as we saw earlier. When you use @foo.setter(), you’re actually calling that property (). The decorated method replaces the setter function in the above-mentioned setter method, which returns a new copy of the property.

Using those decorator methods, the following sequence also builds a full-on property.

With only a getter, we first build several functions and a property object:

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

The.setter() method is then used to create a setter:

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

Finally, we use the.deleter() method to create a deleter:

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

Last but not least, because the property object is a descriptor object, it contains methods to hook into instance attribute fetching, setting, and deleting:. get (),. set (), and. delete ().

>>> class Foo: pass
... 
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

A pure Python sample implementation of the property() type is included in the Descriptor Howto:

Answered by Martijn Pieters

Solution #2

It’s basically a shortcut for generating readonly properties, according to the documentation. So

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

is equivalent to

def getx(self):
    return self._x
x = property(getx)

Answered by J0HN

Solution #3

The following is a simple example of how @property can be used:

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 is treated as a method rather than a property.

class Thing:
    def __init__(self, my_word):
        self._word = my_word
    def word(self):
        return self._word

>>> print( Thing('ok').word() )
'ok'

Answered by AlexG

Solution #4

The first component is straightforward:

@property
def x(self): ...

is equivalent to

def x(self): ...
x = property(x)

The next step would be to add a setter and a deleter to this property. And it happens when the right procedures are used:

@x.setter
def x(self, value): ...

produces a new property that inherits the old x as well as the specified setter.

The same is true for x.deleter.

Answered by glglgl

Solution #5

Another example of how @property can aid when refactoring code can be found here (I’ve merely summarized it below):

Assume you’ve developed a class called Money that looks like this:

class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents

When a user constructs a library based on this class, in which he/she utilizes, for example,

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

Let’s say you want to update your Money class and remove the dollars and cents properties, opting instead to monitor the total amount of cents:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

If the above-mentioned user now attempts to launch his or her library in the same manner as previously,

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))

It will result in a mistake.

That means that anyone who uses your original Money class will have to replace all lines of code that use dollars and cents, which can be a nuisance… So, what can be done to avoid this? Using the @property tag!

That is how:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

    # Getter and setter for dollars...
    @property
    def dollars(self):
        return self.total_cents // 100

    @dollars.setter
    def dollars(self, new_dollars):
        self.total_cents = 100 * new_dollars + self.cents

    # And the getter and setter for cents.
    @property
    def cents(self):
        return self.total_cents % 100

    @cents.setter
    def cents(self, new_cents):
        self.total_cents = 100 * self.dollars + new_cents

when we make a phone call from the library

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

We didn’t have to change a single line of code in our library, and it will work as planned! We wouldn’t even have to be aware that the library we rely on had altered.

Also, the setter does admirably:

money.dollars += 2
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 12 cents.

money.cents += 10
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 22 cents.

@property can also be used in abstract classes; I provide a simple example here.

Answered by Cleb

Post is based on https://stackoverflow.com/questions/17330160/how-does-the-property-decorator-work-in-python