Reputation: 4514
One of the cons of using type hinting in Python is trading beauty of Python code.
Before type hinting my method signatures were concise:
def echo(items):
for i in items:
print(i)
Since my team is using type hinting, I've added type hints to my code as well:
def echo(items: Set[str]) -> None:
Still quite leggible. After some time other parts of my code that operate on set of sets required my items
to be hashable, while others did not. So I decided to support frozenset
as well and now my method looks like:
def echo(items: Union[Set[str],Frozenset[str]]) -> None:
It started to look like methods in Java, although in Java I could operate on interfaces, ignoring implementation details:
void echo(Set<String> items) {
Python does not support interface concept, i.e. I cannot state that Set
implements Frozenset
. The initial implementation would work both for Set
and Frozenset
thanks to duck typing: both behave as set. However, my impression is that explicit type hinting somehow does not play well with duck typing
How can I find a good balance between typing hinting and duck typing?
Upvotes: 4
Views: 1854
Reputation: 2556
As said @chpner it's a good option to use built-in types. All abstract types from the typing module are protocols starting from Python 3.7.
The conception of Protocols has some similarities with interfaces.
As said in the docs:
Structural subtyping can be seen as a static equivalent of duck typing, which is well known to Python programmers. Mypy provides support for structural subtyping via protocol classes described below. See PEP 544 for the detailed specification of protocols and structural subtyping in Python.
Also is possible to define custom protocols:
from typing import Iterable
from typing_extensions import Protocol
class SupportsClose(Protocol):
def close(self) -> None:
... # Empty method body (explicit '...')
class Resource: # No SupportsClose base class!
# ... some methods ...
def close(self) -> None:
self.resource.release()
def close_all(items: Iterable[SupportsClose]) -> None:
for item in items:
item.close()
close_all([Resource(), open('some/file')]) # Okay!
While the main purpose of protocols is static analysis they allow to check if the object follows some protocol in runtime as well:
from typing_extensions import Protocol, runtime_checkable
@runtime_checkable
class Portable(Protocol):
handles: int
class Mug:
def __init__(self) -> None:
self.handles = 1
mug = Mug()
if isinstance(mug, Portable):
use(mug.handles) # Works statically and at runtime
Upvotes: 3
Reputation: 531055
Use AbstractSet
:
from typing import AbstractSet
def echo(items: AbstractSet[str]) -> None:
...
Both Set
and FrozenSet
inherit (directly or indirectly) from AbstractSet
:
AbstractSet
|
|
+--------------+
| |
| |
MutableSet FrozenSet
|
|
Set
Upvotes: 7