我在看内部Python如何实现property descriptor。根据文档property()
是根据描述符协议实现的,为方便起见,在此处复制它:
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
我的问题是:为什么最后三种方法的实现方式如下:
def getter(self, fget):
self.fget = fget
return self
def setter(self, fset):
self.fset = fset
return self
def deleter(self, fdel):
self.fdel= fdel
return self
是否有理由撤销新的属性实例,内部指向基本相同的get和set函数?
答案 0 :(得分:11)
让我们从一些历史开始,因为最初的实现已经等同于您的替代(相当于因为property
在CPython中的C中实现,所以getter
等是用C语言写的不是"普通的Python")。
然而,2007年报道为issue (1620) on the Python bug tracker:
Duncan Booth报道 http://permalink.gmane.org/gmane.comp.python.general/551183新的 @ spam.getter语法修改了该属性,但它应该创建 一个新的。
补丁是修复的第一个草稿。我要编写单元测试 验证补丁。它复制财产,并作为奖金抓住 如果doc字符串最初来自getter,则来自getter的
__doc__
字符串 吸气鬼。
不幸的是,链接并没有去任何地方(我真的不知道为什么它被称为"永久链接" ...)。它被归类为错误并更改为当前表单(请参阅this patch或相应的Github commit (but it's a combination of several patches))。如果您不想关注该链接,则更改为:
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
和deleter
类似。如果你不了解C,重要的是:
((propertyobject *)self)->prop_get = getter;
和
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__)
由于链接已关闭,我不知道具体原因,但我可以根据添加的test-cases in that commit进行推测:
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
(或getter
/ deleter
)被绑定到property_name
。它可以绑定任何名称!而且我不希望它传播回原来的房产(虽然我不确定我在这里会发生什么)。
self
"在getter
,setter
和deleter
处理一次。return self
始终有效的假设可能是有问题的(对于通用装饰器)。答案 1 :(得分:8)
TL; DR - return self
允许子类改变其父母的行为。请参阅以下故障的MCVE。
在父类中创建属性x
时,该类具有带有特定setter,getter和deleter的属性x
。您第一次在子类中说@Parent.x.getter
或类似内容时,您正在父 x
成员上调用方法。如果x.getter
未复制property
实例,则从子类调用它将更改父级的 getter。这会阻止父类以其设计的方式运行。 (感谢Martijn Pieters(毫不奇怪)here。)
此外,docs需要它:
属性对象具有getter,setter和deleter方法,可用作创建属性副本的装饰器...
示例,显示内部:
class P:
## @property --- inner workings shown below, marked "##"
def x(self):
return self.__x
x = property(x) ## what @property does
## @x.setter
def some_internal_name(self, x):
self.__x = x
x = x.setter(some_internal_name) ## what @x.setter does
class C(P):
## @P.x.getter # x is defined in parent P, so you have to specify P.x
def another_internal_name(self):
return 42
# Remember, P.x is defined in the parent.
# If P.x.getter changes self, the parent's P.x changes.
x = P.x.getter(another_internal_name) ## what @P.x.getter does
# Now an x exists in the child as well as in the parent.
如果getter
变异并按照您的建议返回self
,那么孩子的x
将完全是父x
,并且两者都会被修改。
但是,由于规范要求getter
返回副本,因此子项x
是another_internal_name
为fget
的新副本,而父项为x
没有动过。
它有点长,但在Py 2.7.14上显示行为。
class OopsProperty(object):
"Shows what happens if getter()/setter()/deleter() don't copy"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
########## getter/setter/deleter modified as the OP suggested
def getter(self, fget):
self.fget = fget
return self
def setter(self, fset):
self.fset = fset
return self
def deleter(self, fdel):
self.fdel = fdel
return self
class OopsParent(object): # Uses OopsProperty() instead of property()
def __init__(self):
self.__x = 0
@OopsProperty
def x(self):
print("OopsParent.x getter")
return self.__x
@x.setter
def x(self, x):
print("OopsParent.x setter")
self.__x = x
class OopsChild(OopsParent):
@OopsParent.x.getter # changes OopsParent.x!
def x(self):
print("OopsChild.x getter")
return 42;
parent = OopsParent()
print("OopsParent x is",parent.x);
child = OopsChild()
print("OopsChild x is",child.x);
class Parent(object): # Same thing, but using property()
def __init__(self):
self.__x = 0
@property
def x(self):
print("Parent.x getter")
return self.__x
@x.setter
def x(self, x):
print("Parent.x setter")
self.__x = x
class Child(Parent):
@Parent.x.getter
def x(self):
print("Child.x getter")
return 42;
parent = Parent()
print("Parent x is",parent.x);
child = Child()
print("Child x is",child.x);
跑步:
$ python foo.py
OopsChild.x getter <-- Oops! parent.x called the child's getter
('OopsParent x is', 42) <-- Oops!
OopsChild.x getter
('OopsChild x is', 42)
Parent.x getter <-- Using property(), it's OK
('Parent x is', 0) <-- What we expected from the parent class
Child.x getter
('Child x is', 42)
答案 2 :(得分:7)
所以你可以使用带继承的属性吗?
只是尝试通过举例来回答:
class Base(object):
def __init__(self):
self._value = 0
@property
def value(self):
return self._value
@value.setter
def value(self, val):
self._value = val
class Child(Base):
def __init__(self):
super().__init__()
self._double = 0
@Base.value.setter
def value(self, val):
Base.value.fset(self, val)
self._double = val * 2
如果按照你编写它的方式实现它,那么Base.value.setter
也会设置double,这是不需要的。我们想要一个全新的设定器,而不是修改基础设置器。
Base.value.fset
调用自己。