Python属性描述符设计:为什么要复制而不是变异? [英] Python property descriptor design: why copy rather than mutate?

查看:98
本文介绍了Python属性描述符设计:为什么要复制而不是变异?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I was looking at how Python implements the property descriptor internally. According to the docs property() is implemented in terms of the descriptor protocol, reproducing it here for convenience:

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

我的问题是:为什么最后三种方法的实现方式如下:

My question is: why aren't the last three methods implemented as follows:

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

Is there a reason for returing new instances of property, internally pointing to basically the same get and set functions?

推荐答案

让我们从一些历史开始,因为最初的实现已经等同于你的选择(因为属性是在CPython中的C中实现的,所以 getter 等是用C编写的而不是普通的Python )。

Let's start with a bit of history, because the original implementation had been equivalent to your alternative (equivalent because property is implemented in C in CPython so the getter, etc. are written in C not "plain Python").

然而,它被报告为 issue(1620)on 2007年的Python bug追踪器:

However it was reported as issue (1620) on the Python bug tracker back in 2007:


据Duncan Booth报道,
http://permalink.gmane.org/gmane.comp.python.general/551183 新的
@ spam.getter语法修改了该属性,但它应该创建
a新的。

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.

该补丁是修复的第一个草稿。我要将单元测试写入
验证补丁。如果doc字符串最初来自
getter,它会复制属性并作为奖励从getter获取
__ doc __ 字符串。

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.

不幸的是链接不会去任何地方(我真的不知道为什么它被称为永久链接......)。它被归类为bug并更改为当前表单(请参阅此补丁或相应的< A HREF = https://github.com/python/cpython/commit/0449f63f53fa0c2fcf779703d2db77d8a658cf7d#diff-a34ae826f869897e56e081e212b3d75a 的rel = noreferrer> Github上提交(但它是几个补丁的组合))。如果您不想关注链接,则更改为:

Unfortunately the link doesn't go anywhere (I really don't know why it's called a "permalink" ...). It was classified as bug and changed to the current form (see this patch or the corresponding Github commit (but it's a combination of several patches)). In case you don't want to follow the link the change was:

 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 删除。如果你不知道C,重要的是:

And similar for setter and deleter. If you don't know C the important lines are:

((propertyobject *)self)->prop_get = getter;

return self;

其余主要是Python C API样板。但是这两行相当于你的:

the rest is mostly "Python C API boilerplate". However these two lines are equivalent to your:

self.fget = fget
return self

它已更改为:

return property_copy(self, getter, NULL, NULL, NULL);

基本上是这样的:

return type(self)(fget, self.fset, self.fdel, self.__doc__)



为什么会改变?



由于链接已关闭我不知道确切的原因,但我可以推测基于添加的试验例在提交

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

这与其他答案已经提供的示例相似。问题是您希望能够在不影响父类的情况下更改子类中的行为:

That's similar to the examples the other answers already provided. The problem is that you want to be able to change the behavior in a subclass without affecting the parent class:

>>> b = BaseClass()
>>> b.spam
5

然而,对于你的财产,它会导致:

However with your property it would result in this:

>>> b = BaseClass()
>>> b.spam
---------------------------------------------------------------------------
PropertyGet                               Traceback (most recent call last)
PropertyGet: 5

这是因为 BaseClass.spam.getter (在 SubClass 中使用)实际修改并返回 BaseClass.spam 属性!

That happens because BaseClass.spam.getter (which is used in SubClass) actually modifies and returns the BaseClass.spam property!

所以是的,它已被更改(很可能)因为它允许修改子类中属性的行为而不改变父类的行为。

So yes, it had been changed (very likely) because it allows to modify the behavior of a property in a subclass without changing the behavior on the parent class.

请注意还有一个原因,实际上有点傻但实际上值得一提(在我看来):

Note that there is an additional reason, which is a bit silly but actually worth mentioning (in my opinion):

让我们回顾一下:装饰师只是作业的语法糖,所以:

Let's recap shortly: A decorator is just syntactic sugar for an assignment, so:

@decorator
def decoratee():
    pass

相当于:

def func():
    pass

decoratee = decorator(func)
del func

这里的重点是将装饰器的结果分配给装饰函数的名称。因此,虽然您通常对getter / setter / deleter使用相同的函数名称 - 但您不必这样做!

The important point here is that the result of the decorator is assigned to the name of the decorated function. So while you generally use the same "function name" for the getter/setter/deleter - you don't have to!

例如:

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

在此示例中,您使用描述符for a b 创建另一个描述符,其行为类似于 a 除了它有 setter

In this example you use the descriptor for a to create another descriptor for b that behaves like a except that it got a setter.

这是一个相当奇怪的例子,可能不经常使用(或者在所有)。但即使它很奇怪而且(对我来说)不是很好的风格 - 它应该说明仅仅因为你使用 property_name.setter (或 getter / deleter )它必须绑定到 property_name 。它可以绑定任何名称!我不希望它传播回原来的房产(虽然我不确定我会在这里期待什么)。

It's a rather weird example and probably not used very often (or at all). But even if it's rather odd and (to me) not very good style - it should illustrate that just because you use property_name.setter (or getter/deleter) that it has to be bound to property_name. It could be bound to any name! And I wouldn't expect it to propagate back to the original property (although I'm not really sure what I would expect here).


  • CPython实际上在 getter中使用了修改并返回 self 方法 setter 删除一次。

  • 由于错误报告,它已被更改。

  • 当与子类一起使用时,它表现为buggy,它覆盖了父类的属性。

  • 更一般地说:装饰者不能影响他们将被绑定的名称,所以假设它总是对有效的假设在装饰器中可能是有问题的(对于通用装饰器)。

  • CPython actually used the "modify and return self" approach in the getter, setter and deleter once.
  • It had been changed because of a bug report.
  • It behaved "buggy" when used with a subclass that overwrote a property of the parent class.
  • More generally: Decorators cannot influence to what name they will be bound so the assumption that it's always valid to return self in a decorator might be questionable (for a general-purpose decorator).

这篇关于Python属性描述符设计:为什么要复制而不是变异?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆