【问题标题】:Do function objects belonging to a class receive special treatment upon attribute lookup?属于一个类的函数对象在属性查找时是否接受特殊处理?
【发布时间】:2015-01-25 03:20:33
【问题描述】:

我的问题是如何在属性查找时将function 对象转换为绑定或未绑定instancemethod。具体来说,我对暗示 function 对象的行为感兴趣,尽管它是一个描述符,但实际上并没有被这样对待。对于以下内容,请假设 Python 2。

Python 中用户定义的函数对象实现了描述符接口。也就是一个函数对象有一个__get__方法:

>>> def bar(): return 42
>>> bar.__get__
<method-wrapper '__get__' of function object at 0x104b2eaa0>

我的理解是,method-wrapper 是一个 cPython 特有的类型,它创建一个未绑定或绑定的方法对象,将 bar 关联到一个类或实例:

>>> class Foo(object): pass
>>> bar.__get__(None, Foo)
<unbound method Foo.bar>

现在开始写

>>> Foo.bar = bar

具有将bar函数对象添加到Foo.__dict__的效果。而且由于bar是一个描述符,所以写

>>> Foo.bar

调用

>>> Foo.__dict__["bar"].__get__(None, Foo)
<unbound method Foo.bar>

我的问题本质上是最后一个说法是否属实。 Foo.bar 真的 打电话给Foo.__dict__["bar"].get__(None, Foo) 吗?以下似乎是相反的证据:我们可以将bar__get__ 属性替换为绝对不是 method-wrapper 的东西,并且它不会改变函数的方式边界。例如,

>>> def bar(): return 42
>>> bar.__get__ = "This won't bind bar to anything"
>>> class Foo(object): pass
>>> Foo.bar = bar
>>> Foo.bar
<unbound method Foo.bar>
>>> Foo.__dict__["bar"].__get__
"This won't bind bar to anything"

所以bar 尽管是一个描述符,但似乎不像一个那样使用。

这是我对写Foo.bar 时实际发生的情况的猜测:解释器在Foo.__dict__ 中找到函数对象bar。它不会像使用任何其他描述符那样调用bar.__get__,而是执行仅由function 对象调用的特殊行为:它会根据需要自动将function 包装在未绑定或绑定的方法对象中。

我的问题是:

  • 我关于function对象在属性访问时如何转换为方法对象的理论准确吗?
  • 一般来说,这是如何实现的?如果和我猜的一样,除了function 对象之外,还有其他类型得到特殊处理吗?
  • 如果我想了解更多信息,这些记录在哪里?

【问题讨论】:

    标签: python python-2.7


    【解决方案1】:

    它确实调用了__get__。然而,像所有特殊方法一样,__get__ 是在类而不是实例上查找的,如here 所述。也就是说,它不调用Foo.__dict__["bar"].__get__(None, Foo);它调用Foo.__dict__["bar"].__class__.__get__(Foo.__dict__["bar"], None, Foo)。所以在个别函数上设置__get__是没有效果的。

    如果您同样尝试在实例上设置__get__,您不仅可以通过函数看到相同的行为,还可以通过普通的用户定义描述符看到相同的行为:

    class Descriptor(object):
        def __get__(self, obj, cls):
            print("Getting")
            return 42
    
    desc = Descriptor()
    
    def fakeGet(self, obj, cls):
        print("This will not be called")
        return 88
    
    class Foo(object):
        pass
    
    Foo.bar = desc
    
    >>> Foo.bar
    Getting
    42
    # setting a new __get__ directly on the descriptor instance won't work
    >>> desc.__get__ = fakeGet
    >>> Foo.bar
    Getting
    42
    

    【讨论】:

    • +1 换句话说,每个函数都是types.FunctionType 的一个实例,因此在单个函数(即实例)上设置__get__ 不会有任何区别。
    • 啊,这很简单。您的用户定义描述符证明了这种情况。我现在对documentation on descriptors 有点困惑,上面写着“如果绑定到新样式的类,A.x 将转换为调用:A.__dict__['x'].__get__(None, A)。”,这表明正在查找__get__函数 instance (我们看到的情况并非如此),而不是类。我读错了吗?
    • @jme:你说得对,这令人困惑。我认为只是在整个文档中给出了各种示例,例如instance.__specialmethod__(...),但实际上意味着instance.__class__.__specialmethod__(instance, ...)。在实例没有定义自己的方法的通常情况下,效果是相同的(因为当在实例上找不到它时,查找会继续到类),但如果实例确实有自己的,那么这些示例无法从字面上阅读,这仅在我链接到的文档页面上进行了解释。我想可能会为此引发一个文档错误。
    • @BrenBarn 我可能会这样做并提出建议。正如您在回答中提到的那样,同一份文档说:“对于新型类,只有在对象类型上定义的特殊方法的隐式调用才能保证正确工作,而不是在对象的实例字典中。”我想A.x 在这里算作隐式调用,尽管下面的示例并没有说明这一点。他们倾向于建议隐式调用类似于len(x) 代替x.__len__(),或hash(x) 而不是x.__hash__()。无论如何,您的回答有很大帮助!谢谢!
    • @jme: A.x 绝对是一个隐式调用,因为它根本没有提到__get__A.x 在后台调用 __get__,所以它是隐含的。但是,您在评论中提到的示例,令人困惑的部分是 (A.__dict__['x'].__get__(None, A)) 明确的,因此具有误导性,因为正如您所指出的那样,它实际上并不是这样工作的。
    猜你喜欢
    • 2020-06-15
    • 2016-03-21
    • 1970-01-01
    • 1970-01-01
    • 2014-09-17
    • 2023-04-09
    • 2011-10-23
    • 2020-11-30
    • 1970-01-01
    相关资源
    最近更新 更多