Protocols can be used. I'm documenting it here because I found this to be a difficult topic to search for; especially checking for the existence of attributes.
For ensuring the presence of an attribute:
from typing import Protocol
class HasFoo(Protocol): # Define what's required
foo: int
class Foo: # This class fulfills the protocol implicitly
def __init__(self):
self.foo = 1
class Bar:
def __init__(self): # This class fails to implicitly fulfill the protocol
self.bar = 2
def foo_func(f: HasFoo):
pass
foo_func(Foo()) # Type check succeeds
foo_func(Bar()) # Type check fails
Note the type hint after foo. It's required for that line to be syntactically valid, and the type must match the inferred type of the checked attributes. typing.Any can be used as a placeholder if you care about the existence of foo, but not its type.
Similarly, the same can be done for checking methods:
class HasFoo(Protocol):
def foo(self):
pass
class Foo:
def foo(self):
pass
class Bar:
def bar(self):
pass
def func(f: HasFoo):
pass
func(Foo()) # Succeeds
func(Bar()) # Fails
Type checking was done via Pycharm 2020.2.2.