猴子打鸭打字

时间:2019-03-12 06:55:45

标签: python isinstance

# python3.7
Python 3.7.2 (default, Feb 15 2019, 16:54:46) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from collections.abc import *
>>> from _collections_abc import _check_methods
>>> class A:
...     pass
... 
>>> a = A()
>>> isinstance(a, Iterable)
False
>>> A.__iter__ = 100
>>> isinstance(a, Iterable)             # why this not working?
False
>>> _check_methods(A, "__iter__")
True
>>> class B:
...     def __iter__(self):
...             pass
... 
>>> isinstance(B(), Iterable)
True

我用A修补了__iter__,所以isinstance(a, Iterable)应该返回True,因为它现在已经很容易定义__iter__了。从source中,Iterable仅基于该类是否已实现__iter__来确定。

那么为什么这个猴子补丁不能按我预期的那样工作?

2 个答案:

答案 0 :(得分:3)

不支持动态实现(或取消实现)抽象方法。 abc机制进行了大量的缓存,以加快isinstanceissubclass的检查速度,并且没有手动重置缓存的选项。 A不是Iterable的子类这一事实在第一次isinstance调用之后被缓存,导致第二次调用产生False的结果。

最接近docs来描述缓存行为的是以下行:

  

不动态地向类添加抽象方法,或在创建方法或类后尝试修改其抽象状态。

答案 1 :(得分:1)

您将变量__iter__添加到a。您必须将其添加为这样的方法

 class A:
     pass

 def foo(self):
     pass

 A.__iter__ = foo
 a = A()
 isinstance(a, Iterable)
 # True

更新:此答案意外返回True。这只是返回True,因为我设置了 iter ,然后调用isinstance。如果我先调用isinstance然后设置 iter ,由于python缓存系统,它总是返回False(请阅读user2357112的答案)