【问题标题】:monkey patching for duck typing鸭子打字的猴子补丁
【发布时间】:2019-03-12 06:55:45
【问题描述】:
# 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

我用__iter__ 修补了A,所以isinstance(a, Iterable) 应该返回True,因为它现在就像定义了__iter__ 的可迭代对象。从source来看,Iterable只根据类是否实现了__iter__来判断。

那么为什么这个猴子补丁没有像我预期的那样工作?

【问题讨论】:

    标签: python isinstance


    【解决方案1】:

    不支持动态实现(或不实现)抽象方法。 abc 机器做了很多缓存来加速isinstanceissubclass 检查,并且没有手动重置缓存的选项。 A 不是 Iterable 的子类这一事实在第一次 isinstance 调用之后被缓存,导致第二次调用的 False 结果。

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

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

    【讨论】:

    • 谢谢,我刚刚意识到在__instancecheck__ 中返回了实例检查,因为在进入__subclasscheck__ 之前缓存在cls._abc_negative_cache 中。
    【解决方案2】:

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

     class A:
         pass
    
     def foo(self):
         pass
    
     A.__iter__ = foo
     a = A()
     isinstance(a, Iterable)
     # True
    

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

    【讨论】:

    • 虽然__iter__ 确实需要成为一种方法,但使其成为一种方法实际上并不会改变isinstance 的行为。由于其他原因,您看到了 True 结果 - 您删除了第一个 isinstance 检查。
    猜你喜欢
    • 2010-09-29
    • 2016-09-01
    • 2012-09-16
    • 2012-12-18
    • 2020-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-04
    相关资源
    最近更新 更多