-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
PEP 591: Adding a final qualifier to typing #990
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,293 @@ | ||
PEP: 591 | ||
Title: Adding a final qualifier to typing | ||
Author: Michael J. Sullivan <sully@msully.net>, Ivan Levkivskyi <levkivskyi@gmail.com> | ||
Discussions-To: typing-sig@python.org | ||
Status: Draft | ||
Type: Standards Track | ||
Content-Type: text/x-rst | ||
Created: 15-Mar-2019 | ||
Post-History: | ||
|
||
|
||
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 | ||
is expected (for example as a file name for ``NamedTuple``, a tuple | ||
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): | ||
... | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps say that use of final on a non-method function is an error. |
||
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 | ||
|
||
for x in [1, 2, 3]: | ||
FOO: Final = x # Error: Cannot use Final inside a loop | ||
|
||
error: Cannot use Final inside a loop | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stray line |
||
|
||
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. | ||
|
||
|
||
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) | ||
|
||
.. [#PEP-586] PEP 486, Literal Types, Lee, Levkivskyi, Lehtosalo | ||
(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: |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I'm late to the party, but I'm guessing "file name" is a typo for "field name".