98

I've been successfully using Python properties, but I don't see how they could work. If I dereference a property outside of a class, I just get an object of type property:

@property
def hello(): return "Hello, world!"

hello  # <property object at 0x9870a8>

But if I put a property in a class, the behavior is very different:

class Foo(object):
   @property
   def hello(self): return "Hello, world!"

Foo().hello # 'Hello, world!'

I've noticed that unbound Foo.hello is still the property object, so class instantiation must be doing the magic, but what magic is that?

Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
Fred Foo
  • 355,277
  • 75
  • 744
  • 836

4 Answers4

66

As others have noted, they use a language feature called descriptors.

The reason that the actual property object is returned when you access it via a class Foo.hello lies in how the property implements the __get__(self, instance, owner) special method:

  • If a descriptor is accessed on an instance, then that instance is passed as the appropriate argument, and owner is the class of that instance.
  • When it is accessed through the class, then instance is None and only owner is passed. The property object recognizes this and returns self.

Besides the Descriptors howto, see also the documentation on Implementing Descriptors and Invoking Descriptors in the Language Guide.

Tim Yates
  • 5,151
  • 2
  • 29
  • 29
26

In order for @properties to work properly the class needs to be a subclass of object. when the class is not a subclass of object then the first time you try access the setter it actually makes a new attribute with the shorter name instead of accessing through the setter.

The following does not work correctly.

class C(): # <-- Notice that object is missing

    def __init__(self):
        self._x = None

    @property
    def x(self):
        print 'getting value of x'
        return self._x

    @x.setter
    def x(self, x):
        print 'setting value of x'
        self._x = x

>>> c = C()
>>> c.x = 1
>>> print c.x, c._x
1 0

The following will work correctly

class C(object):

    def __init__(self):
        self._x = None

    @property
    def x(self):
        print 'getting value of x'
        return self._x

    @x.setter
    def x(self, x):
        print 'setting value of x'
        self._x = x

>>> c = C()
>>> c.x = 1
setting value of x
>>> print c.x, c._x
getting value of x
1 1
Timothy Vann
  • 2,537
  • 2
  • 20
  • 23
15

Properties are descriptors, and descriptors behave specially when member of a class instance. In short, if a is an instance of type A, and A.foo is a descriptor, then a.foo is equivalent to A.foo.__get__(a).

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Actually, they also seems to work with an old-style class (in Python 2.7). But thanks for the link, will read. – Fred Foo May 31 '11 at 21:14
  • 1
    Note that the method signature for `__get__()` is incorrect. It has two arguments (in addition to self). Otherwise well explained. – Tim Yates May 31 '11 at 21:18
  • @larsmans: From the linked page: "Note that descriptors are only invoked for new style objects or classes." I also remember that I once tried them for old style classes. – Sven Marnach May 31 '11 at 21:19
  • @Tim: The second argument is optional. – Sven Marnach May 31 '11 at 21:20
  • @Sven: Maybe true for how properties are implemented, but they are always passed when the descriptor is invoked. Language Guide: http://docs.python.org/reference/datamodel.html#invoking-descriptors – Tim Yates May 31 '11 at 21:23
  • @Sven: then Guido must be playing tricks on me. They really work for old-style classes in my Python 2.7.1. – Fred Foo May 31 '11 at 21:26
  • @Tim: Read carefully the page your link points to, for example the part "Direct call". Or look at the prototype of `__get__()` given on the page linked in my post. The second argument **must** be optional, that's part of the protocol. – Sven Marnach May 31 '11 at 21:27
  • 2
    @larsmans: I did some quick tests. The type of the descriptor itself must be a new-style class, otherwise it won't work. The class containing the descriptor does not matter. The above quotation could be read in either way. – Sven Marnach May 31 '11 at 21:31
  • @Sven: I think we're both right. In the spec for `__get__()` in the previous section, it says "owner is always the owner class", and when the descriptor is actually invoked as a descriptor, it is always passed. It's not that important--I was mostly trying to clarify how it could produce a different result when accessed via the class. – Tim Yates May 31 '11 at 21:40
3

The property object just implements the descriptor protocol: http://docs.python.org/howto/descriptor.html

Achim
  • 15,415
  • 15
  • 80
  • 144
  • 18
    I certainly understand the reasoning behind a dw in this case, both this answer and the one accepted are devoid of a *real* explanation, and are just basically links. While this is sufficient in some error-solving cases, it seems the OP was looking for a human explanation on the subject. – Morgan Wilde May 28 '13 at 00:07