Reputation: 9593
Python's typing
module defines a number of duck types, e.g., typing.SupportsAbs
to represent any type that implements the __abs__
special method.
Is it possible to define custom duck types in a way such that I can use them as valid type annotations?
For example, I would like to be able to annotate that an argument should be a duck-type equivalent of a threading.Lock
, i.e., any object that implements acquire
and release
methods. Ideally, I could annotate such an argument as SupportsAcquireAndRequire
or DuckLock
, rather than object
.
Upvotes: 10
Views: 2954
Reputation: 7210
typing
has updated to support use-cases like this one - you may use typing.Protocol
for this.
from typing import Protocol, runtime_checkable
@runtime_checkable
class LockLike(Protocol):
def acquire(self) -> None:
...
def release(self) -> None:
...
You need @runtimecheckable
if you want to be able to use isinstance
as in Martijn's answer.
>>> from threading import Lock
>>> isinstance(Lock(), RuntimeCheckable)
True
Upvotes: 8
Reputation: 1121644
You can define an abstract base class (ABC) to specify the interface:
from abc import ABCMeta, abstractmethod
class SupportsAcquireAndRequire(metaclass=ABCMeta):
@abstractmethod
def acquire(self):
pass
@abstractmethod
def release(self):
pass
@classmethod
def __subclasshook__(cls, C):
for method in ('release', 'acquire'):
for B in C.__mro__:
if method in B.__dict__:
if B.__dict__[method] is None:
return NotImplemented
break
else:
return NotImplemented
return True
This is basically how the protocols (like typing.SupportsAbs
) are implemented, albeit without directly using ABCMeta
.
By giving the ABC a __subclasshook__
method, you can use it in isinstance()
and issubclass()
tests, which is more than good enough for tools like mypy
:
>>> from threading import Lock
>>> isinstance(Lock(), SupportsAcquireAndRequire)
True
Upvotes: 11