Python 属性是如何工作的?

我已经成功地使用了 Python 属性,但是我不知道它们是如何工作的。如果我在类之外解引用一个属性,我只会得到一个类型为 property的对象:

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


hello  # <property object at 0x9870a8>

但是如果我把一个属性放到一个类中,它的行为是非常不同的:

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


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

我已经注意到未绑定的 Foo.hello仍然是 property对象,所以类实例化必须做魔术,但是那是什么魔术?

56325 次浏览

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

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

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.

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