为什么 setter 在新式 class 和旧式 class 中表现不同
Why does setter behave differently in new-style class and old-style class
使用 python 2.7,假设我有一个 Test
class 以及下面定义的新式 class 语法。
class Test(object):
def __init__(self):
self._a = 5
@property
def a(self):
return self._a
@a.setter
def a(self, val):
self._a = val
t = Test()
print t.a
t.a = 4
print t.a
print t._a
运行 上面的代码将打印 5,4,4
,这是所需的行为。
但是,如果我将上面代码的第一行更改为 class Test:
,那么结果将变为 5,4,5
。
有谁知道造成这种输出差异的原因吗?
描述符不能保证为旧式 classes 调用。来自 docs:
The default behavior for attribute access is to get, set, or delete
the attribute from an object’s dictionary. For instance, a.x
has a
lookup chain starting with a.__dict__['x']
, then
type(a).__dict__['x']
, and continuing through the base classes of
type(a)
excluding metaclasses. If the looked-up value is an object
defining one of the descriptor methods, then Python may override the
default behavior and invoke the descriptor method instead. Where this
occurs in the precedence chain depends on which descriptor methods
were defined. Note that descriptors are only invoked for new style
objects or classes (a class is new style if it inherits from object or
type).
所以,这里发生的事情是 Test.a.__set__
永远不会被调用,您只是将 a
属性添加到 t
:
In [8]: class Test:
...: def __init__(self):
...: self._a = 5
...:
...: @property
...: def a(self):
...: return self._a
...:
...: @a.setter
...: def a(self, val):
...: self._a = val
...:
In [9]: t = Test()
In [10]: vars(t)
Out[10]: {'_a': 5}
In [11]: t.a
Out[11]: 5
In [12]: t._a
Out[12]: 5
In [13]: t.a = 100
In [14]: t.a
Out[14]: 100
In [15]: t._a
Out[15]: 5
In [16]: vars(t)
Out[16]: {'_a': 5, 'a': 100}
真正让您感到惊讶的是 为什么 T.a.__get__
在这里工作 ?
答案是在 Python 2.2 中,旧式 classes 被重新实现以使用描述符,这是一个不应依赖的实现细节。参见 this question, and the linked issue。
最重要的是,如果您使用描述符,您应该只将它们与新式 classes 一起使用。
请注意,如果我确实使用新样式 class,它会正常工作:
In [17]: class Test(object):
...: def __init__(self):
...: self._a = 5
...:
...: @property
...: def a(self):
...: return self._a
...:
...: @a.setter
...: def a(self, val):
...: self._a = val
...:
In [18]: t = Test()
In [19]: vars(t)
Out[19]: {'_a': 5}
In [20]: t.a = 100
In [21]: t.a
Out[21]: 100
In [22]: t._a
Out[22]: 100
In [23]: vars(t)
Out[23]: {'_a': 100}
使用 python 2.7,假设我有一个 Test
class 以及下面定义的新式 class 语法。
class Test(object):
def __init__(self):
self._a = 5
@property
def a(self):
return self._a
@a.setter
def a(self, val):
self._a = val
t = Test()
print t.a
t.a = 4
print t.a
print t._a
运行 上面的代码将打印 5,4,4
,这是所需的行为。
但是,如果我将上面代码的第一行更改为 class Test:
,那么结果将变为 5,4,5
。
有谁知道造成这种输出差异的原因吗?
描述符不能保证为旧式 classes 调用。来自 docs:
The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance,
a.x
has a lookup chain starting witha.__dict__['x']
, thentype(a).__dict__['x']
, and continuing through the base classes oftype(a)
excluding metaclasses. If the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined. Note that descriptors are only invoked for new style objects or classes (a class is new style if it inherits from object or type).
所以,这里发生的事情是 Test.a.__set__
永远不会被调用,您只是将 a
属性添加到 t
:
In [8]: class Test:
...: def __init__(self):
...: self._a = 5
...:
...: @property
...: def a(self):
...: return self._a
...:
...: @a.setter
...: def a(self, val):
...: self._a = val
...:
In [9]: t = Test()
In [10]: vars(t)
Out[10]: {'_a': 5}
In [11]: t.a
Out[11]: 5
In [12]: t._a
Out[12]: 5
In [13]: t.a = 100
In [14]: t.a
Out[14]: 100
In [15]: t._a
Out[15]: 5
In [16]: vars(t)
Out[16]: {'_a': 5, 'a': 100}
真正让您感到惊讶的是 为什么 T.a.__get__
在这里工作 ?
答案是在 Python 2.2 中,旧式 classes 被重新实现以使用描述符,这是一个不应依赖的实现细节。参见 this question, and the linked issue。
最重要的是,如果您使用描述符,您应该只将它们与新式 classes 一起使用。
请注意,如果我确实使用新样式 class,它会正常工作:
In [17]: class Test(object):
...: def __init__(self):
...: self._a = 5
...:
...: @property
...: def a(self):
...: return self._a
...:
...: @a.setter
...: def a(self, val):
...: self._a = val
...:
In [18]: t = Test()
In [19]: vars(t)
Out[19]: {'_a': 5}
In [20]: t.a = 100
In [21]: t.a
Out[21]: 100
In [22]: t._a
Out[22]: 100
In [23]: vars(t)
Out[23]: {'_a': 100}