gpt4 book ai didi

Python属性描述符设计: why copy rather than mutate?

转载 作者:IT老高 更新时间:2023-10-28 22:12:34 25 4
gpt4 key购买 nike

我正在研究 Python 如何实现 property descriptor内部。根据文档 property() 是根据描述符协议(protocol)实现的,为方便起见,在此处复制它:

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 函数?

最佳答案

让我们从一些历史开始,因为原始实现与您的替代方案等效(等效因为 property 在 CPython 中用 C 实现,因此 getter 等. 是用 C 而不是“普通 Python”编写的)。

但它被报告为 issue (1620) on the Python bug tracker早在 2007 年:

As reported by Duncan Booth at http://permalink.gmane.org/gmane.comp.python.general/551183 the new @spam.getter syntax modifies the property in place but it should create a new one.

The patch is the first draft of a fix. I've to write unit tests to verify the patch. It copies the property and as a bonus grabs the __doc__ string from the getter if the doc string initially came from the getter as well.

不幸的是,链接没有去任何地方(我真的不知道为什么它被称为“永久链接”......)。它被归类为错误并更改为当前形式(参见 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);
}

setterdeleter 也类似。如果您不知道 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

但是对于您的属性(property),它会导致:

>>> 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),它必须绑定(bind)到 property_name。它可以绑定(bind)到任何名称!而且我不希望它传播回原始属性(尽管我不确定我会在这里期待什么)。

总结

  • CPython实际上在gettersetterdeleter中使用了一次“修改并返回self”的方式.
  • 由于错误报告而更改。
  • 与覆盖父类属性的子类一起使用时,它的行为“有问题”。
  • 更一般地说:装饰器无法影响它们将绑定(bind)的名称,因此在装饰器中 return self 始终有效的假设可能是有问题的(对于通用装饰器)。<

关于Python属性描述符设计: why copy rather than mutate?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49081819/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com