【问题标题】:"implicit uses of special methods always rely on the class-level binding of the special method"“特殊方法的隐式使用总是依赖于特殊方法的类级绑定”
【发布时间】:2017-09-05 11:45:26
【问题描述】:

我很难理解 Python in a Nutshell 的最后一部分(粗体)

每实例方法

一个实例可以对所有属性都有特定于实例的绑定, 包括可调用的属性(方法)。对于一个方法,就像 for 任何其他属性(绑定到覆盖描述符的属性除外), 特定于实例的绑定隐藏了类级别的绑定: 属性查找在找到一个类时不考虑该类 直接在实例中绑定。一个特定于实例的绑定 可调用属性不执行任何转换 第 110 页的“绑定和未绑定方法”中详细介绍:属性 引用返回与之前完全相同的可调用对象 直接绑定到实例属性。

但是,这并不像您预期​​的那样工作 用于 Python 调用的特殊方法的每个实例绑定 隐含地作为各种操作的结果,如“特殊 方法”(第 123 页)。特殊方法的这种隐式使用总是 依赖于特殊方法的类级绑定(如果有)。 对于 示例:

def fake_get_item(idx): return idx
class MyClass(object): pass
n = MyClass()
n.__getitem__ = fake_get_item
print(n[23])                      # results in:
# Traceback (most recent call last):
#   File "<stdin>", line 1, in ?
# TypeError: unindexable object

具体是什么意思?

为什么是例子的错误?

谢谢。

【问题讨论】:

  • 看来你不能愚弄 python 来覆盖那个。查找必须要求更高,例如期望实例方法。你不提供一个。拍摄:用有效的实例方法覆盖 __getitem__ 也不起作用...

标签: python


【解决方案1】:

忽略所有细节,它基本上说特殊方法(如Pythons data model 中定义的 - 通常这些方法以两个下划线开头并以两个下划线结尾,并且很少,如果有的话,直接调用)将永远不要在实例中隐式使用,即使在那里定义:

n[whatever]  # will always call type(n).__getitem__(n, whatever)

这与首先检查实例的属性查找不同:

def fake_get_item(idx): 
    return idx

class MyClass(object): 
    pass

n = MyClass()
n.__getitem__ = fake_get_item
print(n.__getitem__(23))     # works because attribute lookup checks the instance first

文档中有一个完整的部分(包括基本原理):"Special method lookup":

3.3.9。特殊方法查找

对于自定义类,特殊方法的隐式调用只有在对象类型上定义时才能保证正常工作,而不是在对象的实例字典中。这种行为是以下代码引发异常的原因:

>>> class C:
...     pass
...
>>> c = C()
>>> c.__len__ = lambda: 5
>>> len(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'C' has no len()

这种行为背后的基本原理在于许多特殊方法,例如__hash__()__repr__(),它们由所有对象实现,包括类型对象。如果这些方法的隐式查找使用常规查找过程,则在类型对象本身上调用它们时会失败:

>>> 1 .__hash__() == hash(1)
True
>>> int.__hash__() == hash(int)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: descriptor '__hash__' of 'int' object needs an argument

[...]

以这种方式绕过__getattribute__() 机制为解释器中的速度优化提供了很大的空间,但代价是处理特殊方法时具有一定的灵活性(特殊方法必须在类对象本身上设置才能被解释器始终调用)。

【讨论】:

    【解决方案2】:

    说得更清楚一点,这意味着您不能即时重新定义 dunder 方法。因此,==+ 和其他运算符对于所有类型 T 的对象总是意味着相同的事情。

    【讨论】:

    • 你可以,只是没有调用它们。如果您想要“即时”调用,您还可以在类上重新定义它们(至少在类支持的情况下)。但这只是吹毛求疵!我认为这是一个很好且中肯的答案 +1 :)
    • 不错的细节工作。 :) 没有尝试过,但这表明说“运算符在某个时间 T 对 C 类的对象总是意味着同样的事情”会更准确。 (即,我可以在添加之间为 C 更改 + 的行为,但对于任何给定的添加,+c1 + c2 的两边都意味着相同的东西)
    • 动态语言不好玩吗?您甚至可以重新分配 __class__ 属性来临时(或永久)更改实例的类。但这仅适用于纯 Python 类,并不完全被认为是“好风格”。
    【解决方案3】:

    我将尝试总结摘录的内容,尤其是粗体部分。 一般来说,当 Python 尝试查找属性(包括方法)的值时,它首先检查实例(即您创建的实际对象),然后检查类。 下面的代码说明了一般行为。

    class MyClass(object):
        def a(self):
            print("howdy from the class")
    
    n = MyClass()
    #here the class method is called
    n.a()
    #'howdy from the class'
    
    def new_a():
        print("hello from new a")    
    n.a = new_a
    #the new instance binding hides the class binding
    n.a()
    #'hello from new a'
    

    粗体部分表示此行为适用于“特殊方法”,例如__getitem__。换句话说,在实例级别覆盖__getitem__(在您的示例中为n.__getitem__ = fake_get_item)什么都不做:当通过n[] 语法调用该方法时,由于该类未实现该方法而引发错误。 (如果在这种情况下也保持通用行为,print(n[23]) 的结果将是打印 23,即执行 fake_get_item 方法。

    同样行为的另一个例子:

    class MyClass(object):
        def __getitem__(self, idx):
            return idx
    n = MyClass()
    fake_get_item = lambda x: "fake"
    print(fake_get_item(23))
    #'fake'
    n.__getitem__ = fake_get_item
    print(n[23])
    #'23'
    

    在本例中,调用__getitem__class 方法(返回索引号)而不是实例绑定(返回'fake')。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-14
      • 2015-02-11
      • 1970-01-01
      • 1970-01-01
      • 2013-03-31
      • 1970-01-01
      相关资源
      最近更新 更多