1
# 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

I patched A with __iter__, so isinstance(a, Iterable) should returns True, since it hehaves like an iterable now for having __iter__ defined. From the source, Iterable determines only based on whether the class has __iter__ implemented.

So why does this monkey patch not working as I expected?

lyu.l
  • 282
  • 3
  • 8

2 Answers2

3

Dynamically implementing (or un-implementing) abstract methods isn't supported. The abc machinery does a lot of caching to speed up isinstance and issubclass checks, and there's no option to manually reset the cache. The fact that A isn't a subclass of Iterable is cached after the first isinstance call, causing a False result for the second call.

The closest the docs come to describing the caching behavior is the following line:

Dynamically adding abstract methods to a class, or attempting to modify the abstraction status of a method or class once it is created, are not supported.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • thanks, I just realize the instance check got returned in `__instancecheck__` because of the caching in `cls._abc_negative_cache`before jumping into `__subclasscheck__`. – lyu.l Mar 12 '19 at 07:54
0

you add variable __iter__ to a. you must add it as a method like this:

 class A:
     pass

 def foo(self):
     pass

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

UPDATE: this answer is accidentally returns True. this just returns True because I set iter and then call isinstance. If I first call isinstance and then set iter it always returns False because of python caching system (read the answer of user2357112)

Mojtaba Kamyabi
  • 3,440
  • 3
  • 29
  • 50
  • 1
    While `__iter__` does need to be a method, making it a method doesn't actually change the `isinstance` behavior. You're seeing a `True` result for other reasons - you removed the first `isinstance` check. – user2357112 Mar 12 '19 at 07:26