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

Is one of the "__get__" arguments redundant?

如此处所述:

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

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

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

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

进行检查

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

它们分开的原因来自PEP 252

中的原文

__get__(): a function callable with one or two arguments that retrieves the attribute value from an object. This is also referred to as a "binding" operation, because it may return a "bound method" object in the case of method descriptors. The first argument, X, is the object from which the attribute must be retrieved or to which it must be bound. When X is None, the optional second argument, T, should be meta-object and the binding operation may return an unbound method restricted to instances of T. When both X and T are specified, X should be an instance of T. Exactly what is returned by the binding operation depends on the semantics of the descriptor; for example, static methods and class methods (see below) ignore the instance and bind to the type instead.

换句话说,这两个参数允许区分“未绑定”描述符(一个调用 class)和一个“绑定”描述符(一个调用实例)。 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
'''

或者考虑这个(有点不完整)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 仍然存在于描述符级别——特别是在 classes 不再验证实例的类型是否与所有者匹配:

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