“__get__”参数之一是多余的吗?

Dus*_*tic 5 python python-descriptors

如此处所述:

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

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

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

  • 从对象访问属性,在这种情况下,所有者参数将等于type( instance ),因此它不会带来新信息
  • 从类(“类型”的对象)访问属性,在这种情况下,源对象仅位于所有者参数中,实例None

在我看来,如果只使用一个参数(例如instance)可以实现相同的功能,该参数将始终保存原始对象,无论它是否是“类”。如果确实需要该信息,则可以使用isinstance(instance, type).

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

Ant*_*ile 4

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

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

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

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

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

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
'''
Run Code Online (Sandbox Code Playgroud)

或考虑这个(有点不完整)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
'''
Run Code Online (Sandbox Code Playgroud)

请注意,自 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)
'''
Run Code Online (Sandbox Code Playgroud)

  • 这完全是一个单独的问题,但如果必须的话,附加到元类的描述符将无法区分(因为元类是“type”的实例) (3认同)