如果为什么有一个描述符的`__get__`方法返回另一个描述符是有用的?

时间:2018-04-26 18:41:02

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

我有一个容器,它包含一个对象的方法,可能还有一些描述符。我想通过检查是否有“ 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)

2 个答案:

答案 0 :(得分: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_method在定义{Bar期间评估函数而不是未绑定方法一样1}}。

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

现在,未绑定的方法已不存在,方法对象的__get__方法是多余的,但尚未删除。

答案 1 :(得分:0)

关于为什么绑定方法是描述符的具体问题(为什么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__方法构建对象来实现其中任何一项,有时这值得做(例如,您可以使用classmethod实现partial,或者使用像partial这样的东西,在这种情况下没有理由添加__get__) - 但是当函数正常工作时,为什么不使用函数?

关于你是否可以找到这个功能的使用的更抽象的问题......嗯,当然。您可以做的事情包括:

  • 创建明确的2.x样式的未绑定方法 - 然后可以作为方法检查,而不是作为函数检查。
  • 创建“可重新绑定”方法,作为构建原型对象系统的一部分。 (普通方法对象只是忽略它们在__get__中的参数,除了检查第二个是类型,或者第二个是None,第一个是类型......)
  • 创建一个模仿函数的对象(而不仅仅是一个可调用的方法,方法,部分等等),包括能够作为非绑定方法,静态方法的结果等。

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