2019-04-12 20:17:52 -04:00
|
|
|
|
PEP: 591
|
|
|
|
|
Title: Adding a final qualifier to typing
|
|
|
|
|
Author: Michael J. Sullivan <sully@msully.net>, Ivan Levkivskyi <levkivskyi@gmail.com>
|
2019-04-17 16:45:01 -04:00
|
|
|
|
BDFL-Delegate: Guido van Rossum <guido@python.org>
|
2019-04-12 20:17:52 -04:00
|
|
|
|
Discussions-To: typing-sig@python.org
|
2019-05-26 05:58:57 -04:00
|
|
|
|
Status: Accepted
|
2019-04-12 20:17:52 -04:00
|
|
|
|
Type: Standards Track
|
|
|
|
|
Content-Type: text/x-rst
|
|
|
|
|
Created: 15-Mar-2019
|
2019-05-26 05:58:57 -04:00
|
|
|
|
Python-Version: 3.8
|
2019-04-12 20:17:52 -04:00
|
|
|
|
Post-History:
|
2019-05-26 05:58:57 -04:00
|
|
|
|
Resolution: https://mail.python.org/archives/list/typing-sig@python.org/message/FDO4KFYWYQEP3U2HVVBEBR3SXPHQSHYR/
|
2019-04-12 20:17:52 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
|
========
|
|
|
|
|
|
|
|
|
|
This PEP proposes a "final" qualifier to be added to the ``typing``
|
|
|
|
|
module---in the form of a ``final`` decorator and a ``Final`` type
|
|
|
|
|
annotation---to serve three related purposes:
|
|
|
|
|
|
|
|
|
|
* Declaring that a method should not be overridden
|
|
|
|
|
* Declaring that a class should not be subclassed
|
|
|
|
|
* Declaring that a variable or attribute should not be reassigned
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Motivation
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
|
|
The ``final`` decorator
|
|
|
|
|
-----------------------
|
|
|
|
|
The current ``typing`` module lacks a way to restrict the use of
|
|
|
|
|
inheritance or overriding at a typechecker level. This is a common
|
|
|
|
|
feature in other object-oriented languages (such as Java), and is
|
|
|
|
|
useful for reducing the potential space of behaviors of a class,
|
|
|
|
|
easing reasoning.
|
|
|
|
|
|
|
|
|
|
Some situations where a final class or method may be useful include:
|
|
|
|
|
|
|
|
|
|
* A class wasn’t designed to be subclassed or a method wasn't designed
|
|
|
|
|
to be overridden. Perhaps it would not work as expected, or be
|
|
|
|
|
error-prone.
|
|
|
|
|
* Subclassing or overriding would make code harder to understand or
|
|
|
|
|
maintain. For example, you may want to prevent unnecessarily tight
|
|
|
|
|
coupling between base classes and subclasses.
|
|
|
|
|
* You want to retain the freedom to arbitrarily change the class
|
|
|
|
|
implementation in the future, and these changes might break
|
|
|
|
|
subclasses.
|
|
|
|
|
|
|
|
|
|
The ``Final`` annotation
|
|
|
|
|
------------------------
|
|
|
|
|
|
|
|
|
|
The current ``typing`` module lacks a way to indicate that a variable
|
|
|
|
|
will not be assigned to. This is a useful feature in several
|
|
|
|
|
situations:
|
|
|
|
|
|
|
|
|
|
* Preventing unintended modification of module and class level
|
|
|
|
|
constants and documenting them as constants in a checkable way.
|
|
|
|
|
* Creating a read-only attribute that may not be overridden by
|
|
|
|
|
subclasses. (``@property`` can make an attribute read-only but
|
|
|
|
|
does not prevent overriding)
|
|
|
|
|
* Allowing a name to be used in situations where ordinarily a literal
|
2019-04-15 19:57:52 -04:00
|
|
|
|
is expected (for example as a field name for ``NamedTuple``, a tuple
|
2019-04-12 20:17:52 -04:00
|
|
|
|
of types passed to ``isinstance``, or an argument to a function
|
|
|
|
|
with arguments of ``Literal`` type [#PEP-586]_).
|
|
|
|
|
|
|
|
|
|
Specification
|
|
|
|
|
=============
|
|
|
|
|
|
|
|
|
|
The ``final`` decorator
|
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
|
|
The ``typing.final`` decorator is used to restrict the use of
|
|
|
|
|
inheritance and overriding.
|
|
|
|
|
|
|
|
|
|
A type checker should prohibit any class decorated with ``@final``
|
|
|
|
|
from being subclassed and any method decorated with ``@final`` from
|
|
|
|
|
being overridden in a subclass. The method decorator version may be
|
|
|
|
|
used with all of instance methods, class methods, static methods, and properties.
|
|
|
|
|
|
|
|
|
|
For example::
|
|
|
|
|
|
|
|
|
|
from typing import final
|
|
|
|
|
|
|
|
|
|
@final
|
|
|
|
|
class Base:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
class Derived(Base): # Error: Cannot inherit from final class "Base"
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
and::
|
|
|
|
|
|
|
|
|
|
from typing import final
|
|
|
|
|
|
|
|
|
|
class Base:
|
|
|
|
|
@final
|
|
|
|
|
def foo(self) -> None:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
class Derived(Base):
|
|
|
|
|
def foo(self) -> None: # Error: Cannot override final attribute "foo"
|
|
|
|
|
# (previously declared in base class "Base")
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For overloaded methods, ``@final`` should be placed on the
|
|
|
|
|
implementation (or on the first overload, for stubs)::
|
|
|
|
|
|
|
|
|
|
from typing import Any, overload
|
|
|
|
|
|
|
|
|
|
class Base:
|
|
|
|
|
@overload
|
|
|
|
|
def method(self) -> None: ...
|
|
|
|
|
@overload
|
|
|
|
|
def method(self, arg: int) -> int: ...
|
|
|
|
|
@final
|
|
|
|
|
def method(self, x=None):
|
|
|
|
|
...
|
|
|
|
|
|
2019-04-15 19:57:52 -04:00
|
|
|
|
It is an error to use ``@final`` on a non-method function.
|
|
|
|
|
|
2019-04-12 20:17:52 -04:00
|
|
|
|
The ``Final`` annotation
|
|
|
|
|
------------------------
|
|
|
|
|
|
|
|
|
|
The ``typing.Final`` type qualifier is used to indicate that a
|
|
|
|
|
variable or attribute should not be reassigned, redefined, or overridden.
|
|
|
|
|
|
|
|
|
|
Syntax
|
|
|
|
|
~~~~~~
|
|
|
|
|
|
|
|
|
|
``Final`` may be used in in one of several forms:
|
|
|
|
|
|
|
|
|
|
* With an explicit type, using the syntax ``Final[<type>]``. Example::
|
|
|
|
|
|
|
|
|
|
ID: Final[float] = 1
|
|
|
|
|
|
|
|
|
|
* With no type annotation. Example::
|
|
|
|
|
|
|
|
|
|
ID: Final = 1
|
|
|
|
|
|
|
|
|
|
The typechecker should apply its usual type inference mechanisms to
|
|
|
|
|
determine the type of ``ID`` (here, likely, ``int``). Note that unlike for
|
|
|
|
|
generic classes this is *not* the same as ``Final[Any]``.
|
|
|
|
|
|
|
|
|
|
* In class bodies and stub files you can omit the right hand side and just write
|
|
|
|
|
``ID: Final[float]``. If the right hand side is omitted, there must
|
|
|
|
|
be an explicit type argument to ``Final``.
|
|
|
|
|
|
|
|
|
|
* Finally, as ``self.id: Final = 1`` (also optionally with a type in
|
|
|
|
|
square brackets). This is allowed *only* in ``__init__`` methods, so
|
|
|
|
|
that the final instance attribute is assigned only once when an
|
|
|
|
|
instance is created.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Semantics and examples
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
The two main rules for defining a final name are:
|
|
|
|
|
|
|
|
|
|
* There can be *at most one* final declaration per module or class for
|
|
|
|
|
a given attribute. There can't be separate class-level and instance-level
|
|
|
|
|
constants with the same name.
|
|
|
|
|
|
|
|
|
|
* There must be *exactly one* assignment to a final name.
|
|
|
|
|
|
|
|
|
|
This means a type checker should prevent further assignments to final
|
|
|
|
|
names in type-checked code::
|
|
|
|
|
|
|
|
|
|
from typing import Final
|
|
|
|
|
|
|
|
|
|
RATE: Final = 3000
|
|
|
|
|
|
|
|
|
|
class Base:
|
|
|
|
|
DEFAULT_ID: Final = 0
|
|
|
|
|
|
|
|
|
|
RATE = 300 # Error: can't assign to final attribute
|
|
|
|
|
Base.DEFAULT_ID = 1 # Error: can't override a final attribute
|
|
|
|
|
|
2019-05-08 17:36:02 -04:00
|
|
|
|
Note that a type checker need not allow ``Final`` declarations inside loops
|
|
|
|
|
since the runtime will see multiple assignments to the same variable in
|
|
|
|
|
subsequent iterations.
|
2019-04-12 20:17:52 -04:00
|
|
|
|
|
|
|
|
|
Additionally, a type checker should prevent final attributes from
|
|
|
|
|
being overridden in a subclass::
|
|
|
|
|
|
|
|
|
|
from typing import Final
|
|
|
|
|
|
|
|
|
|
class Window:
|
|
|
|
|
BORDER_WIDTH: Final = 2.5
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
class ListView(Window):
|
|
|
|
|
BORDER_WIDTH = 3 # Error: can't override a final attribute
|
|
|
|
|
|
|
|
|
|
A final attribute declared in a class body without an initializer must
|
|
|
|
|
be initialized in the ``__init__`` method (except in stub files)::
|
|
|
|
|
|
|
|
|
|
class ImmutablePoint:
|
|
|
|
|
x: Final[int]
|
|
|
|
|
y: Final[int] # Error: final attribute without an initializer
|
|
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
self.x = 1 # Good
|
|
|
|
|
|
|
|
|
|
Type checkers should infer a final attribute that is initialized in
|
|
|
|
|
a class body as being a class variable. Variables should not be annotated
|
|
|
|
|
with both ``ClassVar`` and ``Final``.
|
|
|
|
|
|
|
|
|
|
``Final`` may only be used as the outermost type in assignments or variable
|
|
|
|
|
annotations. Using it in any other position is an error. In particular,
|
|
|
|
|
``Final`` can't be used in annotations for function arguments::
|
|
|
|
|
|
|
|
|
|
x: List[Final[int]] = [] # Error!
|
|
|
|
|
|
|
|
|
|
def fun(x: Final[List[int]]) -> None: # Error!
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
Note that declaring a name as final only guarantees that the name will
|
|
|
|
|
not be re-bound to another value, but does not make the value
|
|
|
|
|
immutable. Immutable ABCs and containers may be used in combination
|
|
|
|
|
with ``Final`` to prevent mutating such values::
|
|
|
|
|
|
|
|
|
|
x: Final = ['a', 'b']
|
|
|
|
|
x.append('c') # OK
|
|
|
|
|
|
|
|
|
|
y: Final[Sequence[str]] = ['a', 'b']
|
|
|
|
|
y.append('x') # Error: "Sequence[str]" has no attribute "append"
|
|
|
|
|
z: Final = ('a', 'b') # Also works
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Type checkers should treat uses of a final name that was initialized
|
|
|
|
|
with a literal as if it was replaced by the literal. For example, the
|
|
|
|
|
following should be allowed::
|
|
|
|
|
|
|
|
|
|
from typing import NamedTuple, Final
|
|
|
|
|
|
|
|
|
|
X: Final = "x"
|
|
|
|
|
Y: Final = "y"
|
|
|
|
|
N = NamedTuple("N", [(X, int), (Y, int)])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Reference Implementation
|
|
|
|
|
========================
|
|
|
|
|
|
|
|
|
|
The mypy [#mypy]_ type checker supports `Final` and `final`. A
|
|
|
|
|
reference implementation of the runtime component is provided in the
|
|
|
|
|
``typing_extensions`` [#typing_extensions]_ module.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rejected/deferred Ideas
|
|
|
|
|
=======================
|
|
|
|
|
|
|
|
|
|
The name ``Const`` was also considered as the name for the ``Final``
|
|
|
|
|
type annotation. The name ``Final`` was chosen instead because the
|
|
|
|
|
concepts are related and it seemed best to be consistent between them.
|
|
|
|
|
|
|
|
|
|
We considered using a single name ``Final`` instead of introducing
|
|
|
|
|
``final`` as well, but ``@Final`` just looked too weird to us.
|
|
|
|
|
|
|
|
|
|
A related feature to final classes would be Scala-style sealed
|
|
|
|
|
classes, where a class is allowed to be inherited only by classes
|
|
|
|
|
defined in the same module. Sealed classes seem most useful in
|
|
|
|
|
combination with pattern matching, so it does not seem to justify the
|
|
|
|
|
complexity in our case. This could be revisisted in the future.
|
|
|
|
|
|
2019-05-10 21:40:46 -04:00
|
|
|
|
It would be possible to have the ``@final`` decorator on classes
|
|
|
|
|
dynamically prevent subclassing at runtime. Nothing else in ``typing``
|
|
|
|
|
does any runtime enforcement, though, so ``final`` will not either.
|
|
|
|
|
A workaround for when both runtime enforcement and static checking is
|
|
|
|
|
desired is to use this idiom (possibly in a support module)::
|
|
|
|
|
|
|
|
|
|
if typing.TYPE_CHECKING:
|
|
|
|
|
from typing import final
|
|
|
|
|
else:
|
|
|
|
|
from runtime_final import final
|
|
|
|
|
|
2019-04-12 20:17:52 -04:00
|
|
|
|
|
|
|
|
|
References
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
|
|
.. [#PEP-484] PEP 484, Type Hints, van Rossum, Lehtosalo, Langa
|
|
|
|
|
(http://www.python.org/dev/peps/pep-0484)
|
|
|
|
|
|
|
|
|
|
.. [#PEP-526] PEP 526, Syntax for Variable Annotations, Gonzalez,
|
|
|
|
|
House, Levkivskyi, Roach, van Rossum
|
|
|
|
|
(http://www.python.org/dev/peps/pep-0526)
|
|
|
|
|
|
2019-06-11 10:45:16 -04:00
|
|
|
|
.. [#PEP-586] PEP 586, Literal Types, Lee, Levkivskyi, Lehtosalo
|
2019-04-12 20:17:52 -04:00
|
|
|
|
(http://www.python.org/dev/peps/pep-0586)
|
|
|
|
|
|
|
|
|
|
.. [#mypy] http://www.mypy-lang.org/
|
|
|
|
|
|
|
|
|
|
.. [#typing_extensions] https://github.com/python/typing/typing_extensions
|
|
|
|
|
|
|
|
|
|
Copyright
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
|
|
|
|
..
|
|
|
|
|
Local Variables:
|
|
|
|
|
mode: indented-text
|
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
|
sentence-end-double-space: t
|
|
|
|
|
fill-column: 70
|
|
|
|
|
coding: utf-8
|
|
|
|
|
End:
|