Python 3.10 |
(pipe, binary or) Union
type hint syntax sugar
Once you get access, this will be the way to go, it is sweet:
def f(i: int|str) -> int|str:
if type(i) is str:
return int(i) + 1
else:
return str(i)
The PEP: https://peps.python.org/pep-0604/
Documented at: https://docs.python.org/3.11/library/typing.html#typing.Union
Union type;
Union[X, Y]
is equivalent toX | Y
and means eitherX
orY
.
Python 3.5 Union
type hints
https://docs.python.org/3/library/typing.html#typing.Union
from typing import Union
def f(i: Union[int,str]) -> Union[int,str]:
if type(i) is str:
return int(i) + 1
else:
return str(i)
What to do before you get access to typing
For the poor souls stuck in older Pythons, I recommend using the exact same syntax as that Python 3 module, which will:
- make porting easier, and possibly automatable, later on
- specifies a unique well defined canonical way to do things
Example:
def f(i: Union[int,str]) -> Union[int,str]:
"""
:param i: Description of the parameter
:type i: Union[int,str]
:rtype: Union[int,str]
"""
if type(i) is str:
return int(i) + 1
else:
return str(i)
or
syntax
While reading through the docs I found another recommendation, now likely fully obsoleted by Union
which also just works, but which might work on even older sphinx https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#info-field-lists
Multiple types in a type field will be linked automatically if separated by the word “or”:
:type an_arg: int or None :vartype a_var: str or int :rtype: float or str
typing.Optional
: optional arguments
As mentioned in this comment, Optional
is a synonym to Union[SomeType,None]
, e.g.:
from typing import Optional
def maybe_i(i: Optional[int] = None) -> int:
if i is None:
return 0
return i + 1
assert maybe_i() == 0
assert maybe_i(1) == 2
However, with the introduction of |
, perhaps the scales have shifted in favor of SomeType|None
which both golfs better (5 chars vs 11 chars, if you don’t spaces around |
) and is more explicit:
def maybe_i(i: int|None = None) -> int:
if i is None:
return 0
return i + 1
assert maybe_i() == 0
assert maybe_i(1) == 2
Sphinx support
Sphinx supports both typing
and :type x: Union[int,str]
well now. Example:
requirements.txt
Sphinx==4.5.0
main.py
from typing import Optional, Union
class C:
'''
My doc for C!
'''
pass
class D:
'''
My doc for D!
'''
pass
def main(i: Union[C, D]) -> Union[C, D]:
'''
My doc for main!
:param i: My doc for i!
'''
return C()
def main_docstring(i):
'''
My doc for main_docstring!
:param i: My doc for i!
:type i: Union[C, D]
:rtype: Union[C, D]
'''
return C()
def main_optional(i: Optional[C]) -> Optional[C]:
'''
My doc for main_optional!
'''
return None
def main_optional_docstring(i):
'''
My doc for main_optional_docstring!
:param i: My doc for i!
:type i: Optional[C]
:rtype: Optional[C]
'''
return None
conf.py
import os
import sys
sys.path.insert(0, os.path.abspath('.'))
extensions = [ 'sphinx.ext.autodoc' ]
#autodoc_typehints = "description"
index.rst
.. automodule:: main
:members:
Build with:
sphinx-build . out
Now:
xdg-open out/index.html
contains:
and all type links work just fine. Also note how it automatically uses the nicer pipe notation even if we wrote Union[]
.
One thing to note is that the types set with typing
syntax show next to the argument, while those set with :type:
show on the description.
We can make everything show on the description by uncommenting on conf.py
as mentioned at Python 3: Sphinx doesn’t show type hints correctly
autodoc_typehints = "description"
which gives:
but it would be even better if we could instead do it the other way around and show :type:
next to the arguments. Anyways, both are acceptable.
typing.Protocol
: enter polymorphism
Union
is usually a code smell. For small stuff it is OK. But saner APIs will instead use polymorphism when possible: How to implement virtual methods in Python?
And now typing
also offers static polymorphism check with Protocol
, e.g.:
from typing import Protocol
class CanFly(Protocol):
def fly(self) -> str:
raise NotImplementedError()
class Bird(CanFly):
def fly(self):
return 'Bird.fly'
class Bat(CanFly):
def fly(self):
return 'Bat.fly'
def send_mail(flyer: CanFly):
print(flyer.fly())
send_mail(Bird())
send_mail(Bat())
So here send_mail
can take any type that implements CanFly
, e.g. either Bird()
or Bat()
, and we don’t need any ugly if
type checks.