|
| 1 | +PEP: 591 |
| 2 | +Title: Adding a final qualifier to typing |
| 3 | +Author: Michael J. Sullivan < [email protected]>, Ivan Levkivskyi < [email protected]> |
| 4 | + |
| 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