【问题标题】:Is one of the "__get__" arguments redundant? [duplicate]“__get__”参数之一是多余的吗? [复制]
【发布时间】:2020-08-12 16:21:30
【问题描述】:

如此处所述:

https://docs.python.org/3/reference/datamodel.html#object.__get__

传递给 __get__ 方法的两个参数(不包括“self”)分别是对象和访问属性的类。第二个参数不是多余的吗? 此外,当“类”也是对象时,为什么需要区分对象和类访问?

所以,在我看来有两种可能性:

  • 从对象访问属性,在这种情况下,owner 参数将等于 type(instance) em>,所以它没有带来新的信息
  • 从类(“类型”对象)访问属性,在这种情况下,源对象仅位于 owner 参数中,并带有 实例

在我看来,如果只使用一个参数(例如 instance)将始终保存原始对象,则无论是否它是不是一个“类”。如果确实需要这些信息,可以使用isinstance(instance, type) 进行检查。

那么,为什么需要两个参数?

【问题讨论】:

  • 我会说它与子类有关。也就是说,您在class A 中定义一个描述符,然后创建一个子类class B(A),然后创建一个对象b = B() 并在该对象上使用该描述符。根据语义,您可能需要知道它是在哪个确切类型中定义的。
  • @rodrigo 我在 python3 中尝试了你的示例。从a=A()访问时,所有者参数是A类;当从b=B() 访问时,所有者参数是类 B。它看起来不像所有者持有定义描述符的类,它只是持有第一个参数的类型。
  • 这是因为子类定义了“is-a”关系。 B 类自动包含 A 类所做的一切,以及更多。
  • 我刚刚测试过,你是对的!不过,我仍然认为这与子类有关。也许你可以使用这个额外的参数来链接到基类,比如为描述符实现super

标签: python python-descriptors


【解决方案1】:

他们分开的原因来自PEP 252中的原始散文

__get__():一个可调用的函数,带有一个或两个参数,用于从对象中检索属性值。这也称为“绑定”操作,因为在方法描述符的情况下它可能返回“绑定方法”对象。第一个参数 X 是必须从中检索或绑定属性的对象。当 X 为 None 时,可选的第二个参数 T 应该是元对象,并且绑定操作可能会返回仅限于 T 实例的未绑定方法。当同时指定 X 和 T 时,X 应该是 T 的实例。究竟是什么由绑定操作返回取决于描述符的语义;例如,静态方法和类方法(见下文)会忽略实例并改为绑定到类型。

换句话说,这两个参数允许区分“未绑定”描述符(一个调用类)和一个“绑定”描述符(调用实例)。 classmethod(它使用 owner 参数并忽略 instance 参数)是您经常看到但没有真正考虑的一个示例。

如果您总是使用“绑定”描述符,那么owner 有点多余,因为instance 应该是该类型的实例。

也许更容易看到的是用纯 python 实现的classmethod 描述符:

class MyClassmethod(object):
    def __init__(self, func):
        self._func = func

    def __get__(self, instance, owner = None):
        # instance is ignored, `owner` is bound to the first arg
        return self._func.__get__(owner)


class C:
    @MyClassmethod
    def func(cls, x):
        print(cls)
        print(x)

C.func(1)
C().func(2)

OUTPUT = '''\
$ python3 t.py 
<class '__main__.C'>
1
<class '__main__.C'>
2
'''

或考虑这个(有些不完整)cached_class_property

class cached_class_property:
    def __init__(self, fget):
        self.fget = fget

    def __get__(self, obj, owner):
        val = self.fget(owner)
        setattr(owner, self.fget.__name__, val)
        return val


class C:
    @cached_class_property
    def f(self):
        print('calculating...')
        return 42


print(C.f)
print(C().f)

OUTPUT = '''\
$ python3 t.py
calculating...
42
42
'''

请注意,由于 python3,“未绑定”和“绑定”方法不再是一个真正的概念,但 api 在描述符级别持续存在——特别是类上的函数不再验证实例的类型是否匹配所有者:

class C:
    def d(self):
        print(self)

class D:
    pass

C().d()
C.d(D())

OUTPUT = '''\
$ python3 t.py
<__main__.C object at 0x7f09576d3040>
<__main__.D object at 0x7f09576d3040>

$ python2 t.py
<__main__.C instance at 0x7efe2c8a7910>
Traceback (most recent call last):
  File "t2.py", line 9, in <module>
    C.d(D())
TypeError: unbound method d() must be called with C instance as first argument (got D instance instead)
'''

【讨论】:

  • " 换句话说,这两个参数允许区分“未绑定”描述符(一个调用类)和一个“绑定”描述符(调用实例)。单个instance 参数也允许这样的区别,这要感谢OP 建议的测试isinstance(instance, type)
  • @Maggyero no -- 类调用将有 instance=None -- 只有一个参数,您将无法恢复被调用的类
  • 我应该更明确一点,但就像 OP 一样,我的问题假设 instance 参数始终绑定到原始对象,而不是 None,无论原始对象是否是类实例或类(即元类实例)。如果确实需要该信息,可以使用isinstance(instance, type) 进行检查。你明白我们的意思吗?
  • 不,如果从非实例访问,instance 将是 None - 假设它将始终绑定到原始对象是一个错误的假设。当从类instance 访问时,将是None。如果您感到困惑,请重新阅读我的答案
  • 这完全是一个单独的问题,但如果必须,附加到元类的描述符将无法区分(因为元类是 type 的实例)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-19
  • 1970-01-01
  • 1970-01-01
  • 2012-02-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-07
相关资源
最近更新 更多