PiotrB
PiotrB

Reputation: 143

python abc subclasshook has no effect when class is derived

There is no way to return False from issubclass when class is derived from class with __subclashook__ implementation. I modified code from: python subclasscheck & subclasshook I only added '(Sized)' to both class definitions:

from abc import ABCMeta

class Sized(metaclass=ABCMeta):
    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            if any("__len__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

class A(Sized):
    pass

class B(Sized):
    def __len__(self):
        return 0

print(issubclass(A, Sized))  # True - should be False
print(issubclass(B, Sized))  # True

Is there any way to return False in this case? Or maybe I'm doing something wrong?

Upvotes: 0

Views: 898

Answers (2)

PiotrB
PiotrB

Reputation: 143

I think the good way to implement this:

from abc import ABCMeta

class Sized(metaclass=ABCMeta):
    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            if any("__len__" in B.__dict__ for B in C.__mro__):
                return True
            else:
                return False
        return NotImplemented


class A(Sized):
    pass


class B(Sized):
    def __len__(self):
        return 0


print(issubclass(A, Sized))  # False
print(issubclass(B, Sized))  # True

I think that when we assume that abc is mechanism similar to compilation (or reflection) in other languages we should return False. If there is some doubt the class is correct subbclass then code shouldn't run or even compile (not in python).

Upvotes: 1

MSeifert
MSeifert

Reputation: 152677

The problem is that you return NotImplemented when the __subclasshook__ doesn't exit early. And as stated in the documentation:

If it returns NotImplemented, the subclass check is continued with the usual mechanism.

So it uses the normal subclass check and finds that you do, in fact, inherit from Sized so returns True.

There are two solutions:

  1. return False instead of return NotImplemented. However, do you really want/need issubclass to return False for direct subclasses?

  2. If you inherit from object for classes A and B it works as expected:

    class A(object):
        pass
    
    class B(object):
        def __len__(self):
            return 0
    
    print(issubclass(A, Sized))  # False
    print(issubclass(B, Sized))  # True
    

Upvotes: 3

Related Questions