Skip to content

Commit d34b43f

Browse files
committed
Add the typing spec
Copied over from https://github.com/JelleZijlstra/typing-spec The text largely derives from the typing PEPs, though I added some material. The organization is based on an outline by @erictraut. @rchen152 and @Daverball made contributions to the text.
1 parent 0c89ed5 commit d34b43f

25 files changed

+7757
-0
lines changed

docs/index.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ Reference
3333
the docs -- since the Python typing system is standardised via PEPs, this
3434
information should apply to most Python type checkers.
3535

36+
Specification
37+
=============
38+
39+
.. toctree::
40+
:maxdepth: 2
41+
42+
spec/index
43+
3644
Indices and tables
3745
==================
3846

docs/spec/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
_build/

docs/spec/Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Minimal makefile for Sphinx documentation
2+
#
3+
4+
# You can set these variables from the command line, and also
5+
# from the environment for the first two.
6+
SPHINXOPTS ?=
7+
SPHINXBUILD ?= sphinx-build
8+
SOURCEDIR = .
9+
BUILDDIR = _build
10+
11+
# Put it first so that "make" without argument is like "make help".
12+
help:
13+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14+
15+
.PHONY: help Makefile
16+
17+
# Catch-all target: route all unknown targets to Sphinx using the new
18+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19+
%: Makefile
20+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

docs/spec/aliases.rst

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
Type aliases
2+
============
3+
4+
(See :pep:`613` for the introduction of ``TypeAlias``, and
5+
:pep:`695` for the ``type`` statement.)
6+
7+
Type aliases may be defined by simple variable assignments::
8+
9+
Url = str
10+
11+
def retry(url: Url, retry_count: int) -> None: ...
12+
13+
Or by using ``typing.TypeAlias``::
14+
15+
from typing import TypeAlias
16+
17+
Url: TypeAlias = str
18+
19+
def retry(url: Url, retry_count: int) -> None: ...
20+
21+
Or by using the ``type`` statement (Python 3.12 and higher)::
22+
23+
type Url = str
24+
25+
def retry(url: Url, retry_count: int) -> None: ...
26+
27+
Note that we recommend capitalizing alias names, since they represent
28+
user-defined types, which (like user-defined classes) are typically
29+
spelled that way.
30+
31+
Type aliases may be as complex as type hints in annotations --
32+
anything that is acceptable as a type hint is acceptable in a type
33+
alias::
34+
35+
from typing import TypeVar
36+
from collections.abc import Iterable
37+
38+
T = TypeVar('T', bound=float)
39+
Vector = Iterable[tuple[T, T]]
40+
41+
def inproduct(v: Vector[T]) -> T:
42+
return sum(x*y for x, y in v)
43+
def dilate(v: Vector[T], scale: T) -> Vector[T]:
44+
return ((x * scale, y * scale) for x, y in v)
45+
vec: Vector[float] = []
46+
47+
48+
This is equivalent to::
49+
50+
from typing import TypeVar
51+
from collections.abc import Iterable
52+
53+
T = TypeVar('T', bound=float)
54+
55+
def inproduct(v: Iterable[tuple[T, T]]) -> T:
56+
return sum(x*y for x, y in v)
57+
def dilate(v: Iterable[tuple[T, T]], scale: T) -> Iterable[tuple[T, T]]:
58+
return ((x * scale, y * scale) for x, y in v)
59+
vec: Iterable[tuple[float, float]] = []
60+
61+
``TypeAlias``
62+
-------------
63+
64+
The explicit alias declaration syntax with ``TypeAlias`` clearly differentiates between the three
65+
possible kinds of assignments: typed global expressions, untyped global
66+
expressions, and type aliases. This avoids the existence of assignments that
67+
break type checking when an annotation is added, and avoids classifying the
68+
nature of the assignment based on the type of the value.
69+
70+
Implicit syntax (pre-existing):
71+
72+
::
73+
74+
x = 1 # untyped global expression
75+
x: int = 1 # typed global expression
76+
77+
x = int # type alias
78+
x: type[int] = int # typed global expression
79+
80+
81+
Explicit syntax:
82+
83+
::
84+
85+
x = 1 # untyped global expression
86+
x: int = 1 # typed global expression
87+
88+
x = int # untyped global expression (see note below)
89+
x: type[int] = int # typed global expression
90+
91+
x: TypeAlias = int # type alias
92+
x: TypeAlias = "MyClass" # type alias
93+
94+
95+
Note: The examples above illustrate implicit and explicit alias declarations in
96+
isolation. For the sake of backwards compatibility, type checkers should support
97+
both simultaneously, meaning an untyped global expression ``x = int`` will
98+
still be considered a valid type alias.
99+
100+
``type`` statement
101+
------------------
102+
103+
Type aliases may also be defined using the ``type`` statement (Python 3.12 and
104+
higher).
105+
106+
The ``type`` statement allows the creation of explicitly generic
107+
type aliases::
108+
109+
type ListOrSet[T] = list[T] | set[T]
110+
111+
Type parameters declared as part of a generic type alias are valid only
112+
when evaluating the right-hand side of the type alias.
113+
114+
As with ``typing.TypeAlias``, type checkers should restrict the right-hand
115+
expression to expression forms that are allowed within type annotations.
116+
The use of more complex expression forms (call expressions, ternary operators,
117+
arithmetic operators, comparison operators, etc.) should be flagged as an
118+
error.
119+
120+
Type alias expressions are not allowed to use traditional type variables (i.e.
121+
those allocated with an explicit ``TypeVar`` constructor call). Type checkers
122+
should generate an error in this case.
123+
124+
::
125+
126+
T = TypeVar("T")
127+
type MyList = list[T] # Type checker error: traditional type variable usage
128+
129+
``NewType``
130+
-----------
131+
132+
There are also situations where a programmer might want to avoid logical
133+
errors by creating simple classes. For example::
134+
135+
class UserId(int):
136+
pass
137+
138+
def get_by_user_id(user_id: UserId):
139+
...
140+
141+
However, this approach introduces a runtime overhead. To avoid this,
142+
``typing.py`` provides a helper function ``NewType`` that creates
143+
simple unique types with almost zero runtime overhead. For a static type
144+
checker ``Derived = NewType('Derived', Base)`` is roughly equivalent
145+
to a definition::
146+
147+
class Derived(Base):
148+
def __init__(self, _x: Base) -> None:
149+
...
150+
151+
While at runtime, ``NewType('Derived', Base)`` returns a dummy function
152+
that simply returns its argument. Type checkers require explicit casts
153+
from ``int`` where ``UserId`` is expected, while implicitly casting
154+
from ``UserId`` where ``int`` is expected. Examples::
155+
156+
UserId = NewType('UserId', int)
157+
158+
def name_by_id(user_id: UserId) -> str:
159+
...
160+
161+
UserId('user') # Fails type check
162+
163+
name_by_id(42) # Fails type check
164+
name_by_id(UserId(42)) # OK
165+
166+
num = UserId(5) + 1 # type: int
167+
168+
``NewType`` accepts exactly two arguments: a name for the new unique type,
169+
and a base class. The latter should be a proper class (i.e.,
170+
not a type construct like ``Union``, etc.), or another unique type created
171+
by calling ``NewType``. The function returned by ``NewType``
172+
accepts only one argument; this is equivalent to supporting only one
173+
constructor accepting an instance of the base class (see above). Example::
174+
175+
class PacketId:
176+
def __init__(self, major: int, minor: int) -> None:
177+
self._major = major
178+
self._minor = minor
179+
180+
TcpPacketId = NewType('TcpPacketId', PacketId)
181+
182+
packet = PacketId(100, 100)
183+
tcp_packet = TcpPacketId(packet) # OK
184+
185+
tcp_packet = TcpPacketId(127, 0) # Fails in type checker and at runtime
186+
187+
Both ``isinstance`` and ``issubclass``, as well as subclassing will fail
188+
for ``NewType('Derived', Base)`` since function objects don't support
189+
these operations.

0 commit comments

Comments
 (0)