Why do explicit protocols in python not require attribute implementation for @runtimechecks, but implicit protocols do?

Viewed 261

If a protocol is implemented with a required attribute i.e. name

@runtime_checkable
class DuckProtocol(Protocol):
    """Protocol for a duck"""
    name: str
    @abstractmethod
    def quack(self):
        raise NotImplementedError

a runtime check of isinstance on an implicit subtype will fail if there is no name attribute, but an explicit one will not:

class Duck1(DuckProtocol):
    def quack(self):
        print('quack')
        
class Duck2:
    def quack(self):
        print('quack')

class Duck3:
    def __init__(self):
        self.name = 'duck3'
    def quack(self):
        print('quack')

print(isinstance(Duck1(), DuckProtocol))
print(isinstance(Duck2(), DuckProtocol))
print(isinstance(Duck3(), DuckProtocol))

Output:

True
False
True

Why does this attribute check not extend to explicit subtypes?

1 Answers

Duck1() does not follow the spec defined by the DuckProtocol protocol, but it is still an instance of the protocol class in the usual sense of objects being instances of classes: its type is Duck1, which is a subclass of the DuckProtocol class. By the default rules of isinstance, that makes Duck1() an instance of DuckProtocol.

Runtime-checkable protocols define their own isinstance logic (by implementing __instancecheck__), but if that logic doesn't decide the object is an instance of the protocol, it ends by delegating to super().__instancecheck__ instead of returning False. After a whole bunch more indirection and delegation, we eventually end up in _abc._abc_subclasscheck, which checks for concrete subclasses as step 4 of its handling, and this check returns True.

Related