【问题标题】:When any why is it useful to have a descriptor's `__get__` method return another descriptor?什么时候让描述符的`__get__`方法返回另一个描述符很有用?
【发布时间】:2018-04-26 18:41:02
【问题描述】:

我有一个容器,其中包含一个对象的方法,可能还有一些描述符。我想通过检查它们是否有一个'get'方法来测试描述符是否已经被解包。令我惊讶的是,类方法的__get__ 方法返回一个对象,该对象也有一个__get__ 方法。你知道这种行为什么时候有用吗?是否与在派生类中重写类方法有关?

import inspect

class K:    
    @classmethod
    def cm(cls, a, b, c):
        pass

def get_attr_info(attr):
    try:
        sig = inspect.signature(attr)
    except:
        sig = None

    attr_info = [
        ('id ', id(attr),),
        ('type  ', type(attr),),
        ('hasattr ', '__get__', hasattr(attr, '__get__'),),
        ('hasattr ', '__call__', hasattr(attr, '__call__'),),
        ('SIG:  ', sig,)
    ]
    return attr_info

get_label = lambda tpl: ' '.join([str(x) for x in tpl[0:-1]]).ljust(20)

kinst = K()
cm = object.__getattribute__(type(kinst), '__dict__')['cm']

try:
    for idx in range(0, 5):
        info = get_attr_info(cm)        
        print('\n' + '\n'.join([get_label(tpl) + str(tpl[-1]) for tpl in info]))
        cm = cm.__get__(kinst, type(kinst))
except AttributeError:
    print(idx)

输出是:

id                  44545808
type                <class 'classmethod'>
hasattr  __get__    True
hasattr  __call__   False
SIG:                None

id                  6437832
type                <class 'method'>
hasattr  __get__    True
hasattr  __call__   True
SIG:                (a, b, c)

id                  6437832
type                <class 'method'>
hasattr  __get__    True
hasattr  __call__   True
SIG:                (a, b, c)

id                  6437832
type                <class 'method'>
hasattr  __get__    True
hasattr  __call__   True
SIG:                (a, b, c)

id                  6437832
type                <class 'method'>
hasattr  __get__    True
hasattr  __call__   True
SIG:                (a, b, c)

【问题讨论】:

  • 你是在问为什么types.MethodType.__get__ 存在,还是在你的标题中问更一般的问题?
  • 另外,考虑到大多数非数据描述符将从__get__ 返回一个普通的旧函数。有关示例,请参见描述符 HOWTO 末尾的纯 Python ClassMethod。而且函数显然必须是非数据描述符,否则方法根本无法工作。

标签: python python-3.x descriptor getattr getattribute


【解决方案1】:

由于未绑定的方法对象,您看到的特定行为过去是有意义的。回到 Python 2,如果你这样做了

class Foo(object):
    def useful_method(self):
        do_stuff()

class Bar(object):
    useful_method = Foo.useful_method

Foo.useful_method 将评估为 未绑定的方法对象,类似于函数但略有不同。未绑定的方法对象需要__get__,因此Bar().useful_method 将自动绑定self,就像Foo.useful_methodBar 的定义过程中评估为函数而不是未绑定的方法一样。

未绑定方法对象和绑定方法对象使用相同的类型实现,因此绑定方法共享相同的__get__ 方法未绑定方法。但是,对于绑定的方法对象,__get__ 只会返回绑定的方法对象,就好像没有涉及描述符逻辑一样。

现在未绑定的方法已经不存在了,方法对象的__get__方法是多余的,但还没有去掉。

【讨论】:

  • 我认为有人认为它不是多余的,因为“方法应该继续像函数一样工作”。尽管该参数可能是虚假的(他们的__get__ 可能存在,但它与函数的__get__ 做的事情不同;解释器从不进行这种反射;如果最终用户想要进行这种反射,他们应该是使用inspect,这将在所有方面都将它们视为不同,除了两者都是可调用的;等等),实际上没有完全散列参数,默认值总是在向后兼容性方面犯错。
【解决方案2】:

关于为什么绑定方法是描述符的具体问题(应该很明显为什么classmethod 对象是,对吧?),请参阅user's answer


对于为什么需要允许非数据描述符的一般问题:编写它们会更痛苦。毕竟,编写非数据描述符最明显的方法通常是只返回一个函数。并且函数必须是非数据描述符,否则方法将无法正常工作,从而破坏了将描述符添加到语言中的全部原因。

例如,考虑 the HOWTO 中的“纯 Python classmethod”示例:

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

或者,更简单地说,考虑staticmethod,即使在内置实现中,它也只是在绑定时返回函数本身。

当然,您也可以通过使用自定义 __call__ 方法构建对象来实现其中的任何一个,有时这是值得的(例如,您可以使用 partial 或类似 @ 的东西来实现 classmethod 987654330@,在这种情况下没有理由添加__get__)——但是当一个函数正常工作时,为什么不使用一个函数呢?


对于更抽象的问题,您是否可以找到此功能的用途……嗯,当然。您可以做的事情包括:

  • 创建显式 2.x 风格的未绑定方法,然后将其作为方法而非函数进行检查。
  • 创建“可重新绑定”方法作为构建原型对象系统的一部分。 (普通方法对象只是忽略它们在__get__ 中的参数,除了检查第二个是类型,还是第二个是None 并且第一个是类型……)
  • 创建一个模仿函数的对象(而不仅仅是一个可调用的,方法、部分等的方式),包括能够充当未绑定的方法、静态方法的结果等。

这些都不是你经常想做的事情,但它们也不是 Python 有任何理由阻止你做的事情。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-17
    相关资源
    最近更新 更多