5

如此处所述:

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

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

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

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

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

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

4

1 回答 1

2

它们分开的原因来自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
'''

或考虑这个(有点不完整)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)
'''
于 2020-09-03T01:48:01.060 回答