让我们从一些历史开始,因为最初的实现等同于您的替代方案(等同于因为property
在 CPython 中用 C 实现,因此getter
等都是用 C 编写的,而不是“纯 Python”)。
然而据报道称Python 错误跟踪器上的问题 (1620) https://bugs.python.org/issue1620早在 2007 年:
据邓肯·布斯 (Duncan Booth) 报道http://permalink.gmane.org/gmane.comp.python.general/551183 http://permalink.gmane.org/gmane.comp.python.general/551183新的
@spam.getter 语法会就地修改属性,但它应该创建
一个新的。
该补丁是修复程序的初稿。我必须编写单元测试
验证补丁。它复制该属性并作为奖励获取__doc__
如果文档字符串最初来自 getter,则来自 getter 的字符串
吸气剂也是如此。
不幸的是,该链接没有去任何地方(我真的不知道为什么它被称为“永久链接”......)。它被归类为错误并更改为当前形式(请参阅这个补丁 https://bugs.python.org/file8947/py3k_copy_property.patch或相应的Github 提交(但它是几个补丁的组合) https://github.com/python/cpython/commit/0449f63f53fa0c2fcf779703d2db77d8a658cf7d#diff-a34ae826f869897e56e081e212b3d75a)。如果您不想点击链接,则更改为:
PyObject *
property_getter(PyObject *self, PyObject *getter)
{
- Py_XDECREF(((propertyobject *)self)->prop_get);
- if (getter == Py_None)
- getter = NULL;
- Py_XINCREF(getter);
- ((propertyobject *)self)->prop_get = getter;
- Py_INCREF(self);
- return self;
+ return property_copy(self, getter, NULL, NULL, NULL);
}
和类似的setter
and deleter
。如果您不了解 C,重要的几行是:
((propertyobject *)self)->prop_get = getter;
and
return self;
其余的大部分是“Python C API 样板”。然而这两行相当于你的:
self.fget = fget
return self
并且修改为:
return property_copy(self, getter, NULL, NULL, NULL);
这本质上是:
return type(self)(fget, self.fset, self.fdel, self.__doc__)
为什么改变了?
由于链接已关闭,我不知道确切的原因,但我可以根据添加的内容进行推测该提交中的测试用例 https://github.com/python/cpython/commit/0449f63f53fa0c2fcf779703d2db77d8a658cf7d#diff-ea5875d237692b8c64af41a0dea6f3a9:
import unittest
class PropertyBase(Exception):
pass
class PropertyGet(PropertyBase):
pass
class PropertySet(PropertyBase):
pass
class PropertyDel(PropertyBase):
pass
class BaseClass(object):
def __init__(self):
self._spam = 5
@property
def spam(self):
"""BaseClass.getter"""
return self._spam
@spam.setter
def spam(self, value):
self._spam = value
@spam.deleter
def spam(self):
del self._spam
class SubClass(BaseClass):
@BaseClass.spam.getter
def spam(self):
"""SubClass.getter"""
raise PropertyGet(self._spam)
@spam.setter
def spam(self, value):
raise PropertySet(self._spam)
@spam.deleter
def spam(self):
raise PropertyDel(self._spam)
class PropertyTests(unittest.TestCase):
def test_property_decorator_baseclass(self):
# see #1620
base = BaseClass()
self.assertEqual(base.spam, 5)
self.assertEqual(base._spam, 5)
base.spam = 10
self.assertEqual(base.spam, 10)
self.assertEqual(base._spam, 10)
delattr(base, "spam")
self.assert_(not hasattr(base, "spam"))
self.assert_(not hasattr(base, "_spam"))
base.spam = 20
self.assertEqual(base.spam, 20)
self.assertEqual(base._spam, 20)
self.assertEqual(base.__class__.spam.__doc__, "BaseClass.getter")
def test_property_decorator_subclass(self):
# see #1620
sub = SubClass()
self.assertRaises(PropertyGet, getattr, sub, "spam")
self.assertRaises(PropertySet, setattr, sub, "spam", None)
self.assertRaises(PropertyDel, delattr, sub, "spam")
self.assertEqual(sub.__class__.spam.__doc__, "SubClass.getter")
这与其他答案已经提供的示例类似。问题是您希望能够更改子类中的行为而不影响父类:
>>> b = BaseClass()
>>> b.spam
5
但是,对于您的财产,会导致以下结果:
>>> b = BaseClass()
>>> b.spam
---------------------------------------------------------------------------
PropertyGet Traceback (most recent call last)
PropertyGet: 5
发生这种情况是因为BaseClass.spam.getter
(用于SubClass
)实际上修改并返回BaseClass.spam
财产!
所以是的,它已经被更改(很可能),因为它允许修改子类中属性的行为而不更改父类的行为。
另一个原因 (?)
请注意,还有一个额外的原因,这有点愚蠢但实际上值得一提(在我看来):
让我们简单回顾一下:装饰器只是赋值的语法糖,所以:
@decorator
def decoratee():
pass
相当于:
def func():
pass
decoratee = decorator(func)
del func
这里重要的一点是装饰器的结果被分配给修饰函数的名称。因此,虽然您通常对 getter/setter/deleter 使用相同的“函数名称”,但您不必这样做!
例如:
class Fun(object):
@property
def a(self):
return self._a
@a.setter
def b(self, value):
self._a = value
>>> o = Fun()
>>> o.b = 100
>>> o.a
100
>>> o.b
100
>>> o.a = 100
AttributeError: can't set attribute
在此示例中,您使用描述符a
创建另一个描述符b
其行为就像a
除了它有一个setter
.
这是一个相当奇怪的例子,可能不经常使用(或根本不使用)。但即使它相当奇怪并且(对我来说)不是很好的风格 - 它应该说明这一点只是因为你使用property_name.setter
(or getter
/deleter
)它必须绑定到property_name
。它可以绑定到任何名称!而且我不希望它传播回原始属性(尽管我不太确定我在这里期望什么)。
Summary
- CPython实际上使用了“修改并返回
self
” 中的方法getter
, setter
and deleter
once.
- 由于错误报告,它已被更改。
- 当与覆盖父类属性的子类一起使用时,它的行为会出现“错误”。
- 更一般地说:装饰器无法影响它们将绑定的名称,因此假设它始终有效
return self
在装饰器中可能是有问题的(对于通用装饰器)。