Skip to content

Commit f3b44fb

Browse files
msullivangvanrossum
authored andcommitted
PEP 591: Adding a final qualifier to typing (#990)
1 parent 6966646 commit f3b44fb

File tree

1 file changed

+293
-0
lines changed

1 file changed

+293
-0
lines changed

pep-0591.rst

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
PEP: 591
2+
Title: Adding a final qualifier to typing
3+
Author: Michael J. Sullivan <[email protected]>, Ivan Levkivskyi <[email protected]>
4+
Discussions-To: [email protected]
5+
Status: Draft
6+
Type: Standards Track
7+
Content-Type: text/x-rst
8+
Created: 15-Mar-2019
9+
Post-History:
10+
11+
12+
Abstract
13+
========
14+
15+
This PEP proposes a "final" qualifier to be added to the ``typing``
16+
module---in the form of a ``final`` decorator and a ``Final`` type
17+
annotation---to serve three related purposes:
18+
19+
* Declaring that a method should not be overridden
20+
* Declaring that a class should not be subclassed
21+
* Declaring that a variable or attribute should not be reassigned
22+
23+
24+
Motivation
25+
==========
26+
27+
The ``final`` decorator
28+
-----------------------
29+
The current ``typing`` module lacks a way to restrict the use of
30+
inheritance or overriding at a typechecker level. This is a common
31+
feature in other object-oriented languages (such as Java), and is
32+
useful for reducing the potential space of behaviors of a class,
33+
easing reasoning.
34+
35+
Some situations where a final class or method may be useful include:
36+
37+
* A class wasn’t designed to be subclassed or a method wasn't designed
38+
to be overridden. Perhaps it would not work as expected, or be
39+
error-prone.
40+
* Subclassing or overriding would make code harder to understand or
41+
maintain. For example, you may want to prevent unnecessarily tight
42+
coupling between base classes and subclasses.
43+
* You want to retain the freedom to arbitrarily change the class
44+
implementation in the future, and these changes might break
45+
subclasses.
46+
47+
The ``Final`` annotation
48+
------------------------
49+
50+
The current ``typing`` module lacks a way to indicate that a variable
51+
will not be assigned to. This is a useful feature in several
52+
situations:
53+
54+
* Preventing unintended modification of module and class level
55+
constants and documenting them as constants in a checkable way.
56+
* Creating a read-only attribute that may not be overridden by
57+
subclasses. (``@property`` can make an attribute read-only but
58+
does not prevent overriding)
59+
* Allowing a name to be used in situations where ordinarily a literal
60+
is expected (for example as a file name for ``NamedTuple``, a tuple
61+
of types passed to ``isinstance``, or an argument to a function
62+
with arguments of ``Literal`` type [#PEP-586]_).
63+
64+
Specification
65+
=============
66+
67+
The ``final`` decorator
68+
-----------------------
69+
70+
The ``typing.final`` decorator is used to restrict the use of
71+
inheritance and overriding.
72+
73+
A type checker should prohibit any class decorated with ``@final``
74+
from being subclassed and any method decorated with ``@final`` from
75+
being overridden in a subclass. The method decorator version may be
76+
used with all of instance methods, class methods, static methods, and properties.
77+
78+
For example::
79+
80+
from typing import final
81+
82+
@final
83+
class Base:
84+
...
85+
86+
class Derived(Base): # Error: Cannot inherit from final class "Base"
87+
...
88+
89+
and::
90+
91+
from typing import final
92+
93+
class Base:
94+
@final
95+
def foo(self) -> None:
96+
...
97+
98+
class Derived(Base):
99+
def foo(self) -> None: # Error: Cannot override final attribute "foo"
100+
# (previously declared in base class "Base")
101+
...
102+
103+
104+
For overloaded methods, ``@final`` should be placed on the
105+
implementation (or on the first overload, for stubs)::
106+
107+
from typing import Any, overload
108+
109+
class Base:
110+
@overload
111+
def method(self) -> None: ...
112+
@overload
113+
def method(self, arg: int) -> int: ...
114+
@final
115+
def method(self, x=None):
116+
...
117+
118+
The ``Final`` annotation
119+
------------------------
120+
121+
The ``typing.Final`` type qualifier is used to indicate that a
122+
variable or attribute should not be reassigned, redefined, or overridden.
123+
124+
Syntax
125+
~~~~~~
126+
127+
``Final`` may be used in in one of several forms:
128+
129+
* With an explicit type, using the syntax ``Final[<type>]``. Example::
130+
131+
ID: Final[float] = 1
132+
133+
* With no type annotation. Example::
134+
135+
ID: Final = 1
136+
137+
The typechecker should apply its usual type inference mechanisms to
138+
determine the type of ``ID`` (here, likely, ``int``). Note that unlike for
139+
generic classes this is *not* the same as ``Final[Any]``.
140+
141+
* In class bodies and stub files you can omit the right hand side and just write
142+
``ID: Final[float]``. If the right hand side is omitted, there must
143+
be an explicit type argument to ``Final``.
144+
145+
* Finally, as ``self.id: Final = 1`` (also optionally with a type in
146+
square brackets). This is allowed *only* in ``__init__`` methods, so
147+
that the final instance attribute is assigned only once when an
148+
instance is created.
149+
150+
151+
Semantics and examples
152+
~~~~~~~~~~~~~~~~~~~~~~
153+
154+
The two main rules for defining a final name are:
155+
156+
* There can be *at most one* final declaration per module or class for
157+
a given attribute. There can't be separate class-level and instance-level
158+
constants with the same name.
159+
160+
* There must be *exactly one* assignment to a final name.
161+
162+
This means a type checker should prevent further assignments to final
163+
names in type-checked code::
164+
165+
from typing import Final
166+
167+
RATE: Final = 3000
168+
169+
class Base:
170+
DEFAULT_ID: Final = 0
171+
172+
RATE = 300 # Error: can't assign to final attribute
173+
Base.DEFAULT_ID = 1 # Error: can't override a final attribute
174+
175+
for x in [1, 2, 3]:
176+
FOO: Final = x # Error: Cannot use Final inside a loop
177+
178+
error: Cannot use Final inside a loop
179+
180+
Additionally, a type checker should prevent final attributes from
181+
being overridden in a subclass::
182+
183+
from typing import Final
184+
185+
class Window:
186+
BORDER_WIDTH: Final = 2.5
187+
...
188+
189+
class ListView(Window):
190+
BORDER_WIDTH = 3 # Error: can't override a final attribute
191+
192+
A final attribute declared in a class body without an initializer must
193+
be initialized in the ``__init__`` method (except in stub files)::
194+
195+
class ImmutablePoint:
196+
x: Final[int]
197+
y: Final[int] # Error: final attribute without an initializer
198+
199+
def __init__(self) -> None:
200+
self.x = 1 # Good
201+
202+
Type checkers should infer a final attribute that is initialized in
203+
a class body as being a class variable. Variables should not be annotated
204+
with both ``ClassVar`` and ``Final``.
205+
206+
``Final`` may only be used as the outermost type in assignments or variable
207+
annotations. Using it in any other position is an error. In particular,
208+
``Final`` can't be used in annotations for function arguments::
209+
210+
x: List[Final[int]] = [] # Error!
211+
212+
def fun(x: Final[List[int]]) -> None: # Error!
213+
...
214+
215+
Note that declaring a name as final only guarantees that the name will
216+
not be re-bound to another value, but does not make the value
217+
immutable. Immutable ABCs and containers may be used in combination
218+
with ``Final`` to prevent mutating such values::
219+
220+
x: Final = ['a', 'b']
221+
x.append('c') # OK
222+
223+
y: Final[Sequence[str]] = ['a', 'b']
224+
y.append('x') # Error: "Sequence[str]" has no attribute "append"
225+
z: Final = ('a', 'b') # Also works
226+
227+
228+
Type checkers should treat uses of a final name that was initialized
229+
with a literal as if it was replaced by the literal. For example, the
230+
following should be allowed::
231+
232+
from typing import NamedTuple, Final
233+
234+
X: Final = "x"
235+
Y: Final = "y"
236+
N = NamedTuple("N", [(X, int), (Y, int)])
237+
238+
239+
Reference Implementation
240+
========================
241+
242+
The mypy [#mypy]_ type checker supports `Final` and `final`. A
243+
reference implementation of the runtime component is provided in the
244+
``typing_extensions`` [#typing_extensions]_ module.
245+
246+
247+
Rejected/deferred Ideas
248+
=======================
249+
250+
The name ``Const`` was also considered as the name for the ``Final``
251+
type annotation. The name ``Final`` was chosen instead because the
252+
concepts are related and it seemed best to be consistent between them.
253+
254+
We considered using a single name ``Final`` instead of introducing
255+
``final`` as well, but ``@Final`` just looked too weird to us.
256+
257+
A related feature to final classes would be Scala-style sealed
258+
classes, where a class is allowed to be inherited only by classes
259+
defined in the same module. Sealed classes seem most useful in
260+
combination with pattern matching, so it does not seem to justify the
261+
complexity in our case. This could be revisisted in the future.
262+
263+
264+
References
265+
==========
266+
267+
.. [#PEP-484] PEP 484, Type Hints, van Rossum, Lehtosalo, Langa
268+
(http://www.python.org/dev/peps/pep-0484)
269+
270+
.. [#PEP-526] PEP 526, Syntax for Variable Annotations, Gonzalez,
271+
House, Levkivskyi, Roach, van Rossum
272+
(http://www.python.org/dev/peps/pep-0526)
273+
274+
.. [#PEP-586] PEP 486, Literal Types, Lee, Levkivskyi, Lehtosalo
275+
(http://www.python.org/dev/peps/pep-0586)
276+
277+
.. [#mypy] http://www.mypy-lang.org/
278+
279+
.. [#typing_extensions] https://github.com/python/typing/typing_extensions
280+
281+
Copyright
282+
=========
283+
284+
This document has been placed in the public domain.
285+
286+
..
287+
Local Variables:
288+
mode: indented-text
289+
indent-tabs-mode: nil
290+
sentence-end-double-space: t
291+
fill-column: 70
292+
coding: utf-8
293+
End:

0 commit comments

Comments
 (0)