1

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?

vanbastelaer
  • 368
  • 2
  • 15

0 Answers0