Type qualifiers¶
This section describes constructs that do not affect types of variables and methods, but affect how they can be accessed, assigned, and overridden.
Class and instance variables¶
By default mypy assumes that a variable declared in the class body is
an instance variable. One can mark names intended to be used as class variables
with a special type qualifier ClassVar
. For example:
from typing import ClassVar
class Base:
attr: int # This is an instance variable
num_subclasses: ClassVar[int] # This is a class variable
def foo(self) -> None:
self.attr = 0 # OK
self.num_subclasses = 0 # Error: Cannot assign to class variable via instance
Base.num_subclasses = 0 # OK
Base.attr = 0 # Also OK, sets default value for an instance variable
Note that ClassVar
is not valid as a nested type, and in any position
other than assignment in class body. For example:
x: ClassVar[int] # Error: ClassVar can't be used at module scope
class C:
y: List[ClassVar[int]] # Error: can't use ClassVar as nested type
Final attributes of classes and modules¶
Note
This is an experimental feature. Some details might change in later
versions of mypy. The final qualifiers are available in typing_extensions
module. When the semantics is stable, they will be added to typing
.
There are several situations where static guarantees about non-redefinition of certain names (or references in general) can be useful. One such example is module or class level constants, user might want to guard them against unintentional modifications:
RATE = 3000
class Base:
DEFAULT_ID = 0
# 1000 lines later
class Derived(Base):
DEFAULT_ID = 1 # this may be unintentional
RATE = 300 # this too
Another example is where a user might want to protect certain instance attributes from overriding in a subclass:
import uuid
class Snowflake:
"""An absolutely unique object in the database"""
def __init__(self) -> None:
self.id = uuid.uuid4()
# 1000 lines later
class User(Snowflake):
id = uuid.uuid4() # This has valid type, but the meaning
# may be wrong
Some other use cases might be solved by using @property
, but note that both
above use cases can’t be solved this way. For such situations, one might want
to use typing.Final
.
Definition syntax¶
The typing.Final
type qualifier indicates that a given name or attribute
should never be re-assigned, re-defined, nor overridden. It can be used in
one of these forms:
- The simplest one is
ID: Final = 1
. Note that unlike gor generic classes this is not the same asFinal[Any]
. Here mypy will infer typeint
. - An explicit type
ID: Final[float] = 1
can be used as in any normal assignment. - In stub files one can omit the right hand side and just write
ID: Final[float]
. - Finally, one can define
self.id: Final = 1
(also with a type argument), but this is allowed only in__init__
methods.
Definition rules¶
The are two rules that should be always followed when defining a final name:
There can be at most one final declaration per module or class for a given attribute:
ID: Final = 1 ID: Final = 2 # Error! class SomeCls: id: Final = 1 def __init__(self, x: int) -> None: self.id: Final = x # Error!
Note that mypy has a single namespace for a class. So there can’t be two class-level and instance-level constants with the same name.
There must be exactly one assignment to a final attribute:
ID = 1 ID: Final = 2 # Error! class SomeCls: ID = 1 ID: Final = 2 # Error!
Note
Conditional final declarations and final declarations within loops are not supported.
Using final attributes¶
As a result of a final declaration mypy strives to provide the two following guarantees:
A final attribute can’t be re-assigned (or otherwise re-defined), both internally and externally:
# file mod.py from typing import Final ID: Final = 1 # file main.py from typing import Final import mod mod.ID = 2 # Error, can't assign to constant. class SomeCls: ID: Final = 1 def meth(self) -> None: self.ID = 2 # Error, can't assign to final attribute class DerivedCls(SomeCls): ... DerivedCls.ID = 2 # Error! obj: DerivedCls obj.ID = 2 # Error!
A final attribute can’t be overridden by a subclass (even with another explicit final declaration). Note however, that final attributes can override normal attributes. This also applies to multiple inheritance:
class Base: ID = 0 class One(Base): ID: Final = 1 # OK class Other(Base): ID: Final = 2 # OK class Combo(One, Other): # Error, cannot override final attribute. pass
Note
Mypy treats re-exported final names as final. In other words, once declared, the final status can’t be “stripped”. Such behaviour is typically desired for larger libraries where constants are defined in a separate module and then re-exported.
Final methods¶
Like with attributes, sometimes it is useful to protect a method from
overriding. In such situations one can use a typing.final
decorator:
from typing import final
class Base:
@final
def common_name(self) -> None: # common signature
...
# 1000 lines later
class Derived(Base):
def common_name(self) -> None: # Error, this overriding might break
# invariants in the base class.
...
This @final
decorator can be used with instance methods, class methods,
static methods, and properties (this includes overloaded methods).
Final classes¶
As a bonus, applying a typing.final
decorator to a class indicates to mypy
that it can’t be subclassed. Mypy doesn’t provide any additional features for
final classes, but some other tools may use this information for their benefits.
Plus it serves a verifiable documentation purpose:
# file lib.pyi
from typing import final
@final
class Leaf:
...
# file main.py
from lib import Leaf
class MyLeaf(Leaf): # Error, library author believes this is unsafe
...