This is a follow-up question (related to this question I asked) but it is complicated enough that I think it is better to start a new question.
Previously, I discovered that for whatever reason, I have:
isinstance([], UserString) == True
But this is clearly not right. After a bunch of digging, I think my other code is causing this issue but I don't know how it is possible.
This is the minimal code that can reproduce the problematic result above:
if __name__ == "__main__":
def func_decorator(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
return res
return wrapper
def class_decorator(cls):
# set of callable function names already been decorated/inspected
handled_funcs = {'__new__', '__init__', '__del__', '__getattr__', '__getattribute__', '__class__'}
for key in cls.__dict__:
# Only callable functions are decorated
value = getattr(cls, key)
if not callable(value):
continue
handled_funcs.add(key)
setattr(cls, key, func_decorator(value))
# Handle the remaining base classes (skip Mixin)
for base in cls.__mro__[2:]:
for key in base.__dict__:
value = getattr(base, key)
if not callable(value) or key in handled_funcs:
continue
handled_funcs.add(key)
setattr(cls, key, func_decorator(value))
return cls
class Mixin():
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
class_decorator(cls)
class MyUserString(Mixin, UserString):
def __init__(self, seq, *, synthesized=False):
super().__init__(synthesized, seq)
print(isinstance([], UserString)) # this will print True
I removed most of my other code that actually does useful things I need.
Basically, I decorate MyUserString
using __init_subclass__
in Mixin
so that all methods in MyUserString
and its base classes (I skipped Mixin
) are decorated by func_decorator
. During the decoration process, I follow MRO and skip the methods that have already been handled according to handled_funcs
(i.e., handled_funcs has all the methods that should not been decorated, either because I don't want them to be decorated or because they have already been decorated, e.g., __new__
which I add at the very beginning).
Now, the interesting thing I found is that if I add __subclasshook__
in the set, I got the correct result, i.e.,
isinstance([], UserString) == False
. But why? The func_decorator
simply returns the same value as the original function, and more importantly, UserString
is not even decorated by class_decorator
(I think, because setattr
always sets the attribute for cls
, which is MyUserString
, but perhaps I am wrong)?
I know UserString
inherits from ABC
and ABC
somehow defines this __subclasshook__
for isinstance
...but I can't put two and two together! Can someone please explain what happened?