Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/guides/libraries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -568,10 +568,10 @@ type annotation would be redundant.
COLOR_FORMAT_RGB: Literal["rgb"] = "rgb"
LATEST_VERSION: tuple[int, int] = (4, 5)

# Final variable with inferred type
# Bare Final variable with inferred type
ColorFormatRgb: Final = "rgb"

# Final variable with explicit type
# Explicitly typed Final variable
ColorFormatRgb: Final[Literal["rgb"]] = "rgb"
LATEST_VERSION: Final[tuple[int, int]] = (4, 5)

Expand Down
4 changes: 2 additions & 2 deletions docs/guides/writing_stubs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,8 @@ still recommended to separately define known attributes.
Constants
---------

When the value of a constant is important, mark it as ``Final`` and assign it
to its value.
When the value of a constant is important, mark it as a :term:`bare Final` and
assign it to its value.

Yes::

Expand Down
5 changes: 3 additions & 2 deletions docs/spec/dataclasses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -526,8 +526,9 @@ This includes, but is not limited to, the following semantics:
* ClassVar attributes are not considered dataclass fields and are
`ignored by dataclass mechanisms <https://docs.python.org/3/library/dataclasses.html#class-variables>`_.

* A dataclass field may be annotated with ``Final[...]``. For example, ``x:
Final[int]`` in a dataclass body specifies a dataclass field ``x``, which
* A dataclass field may be annotated with an :term:`explicitly typed Final`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may have to say what "bare Final" does in dataclasses. I discovered ty handles this buggily: astral-sh/ty#4766.

There are two cases:

  • Bare Final with a default. The obvious interpretation is that that gives you a Final dataclass field with a literal type, which is coherent but kind of useless (you can only pass that one specific type).
  • Bare Final without a default. Ty and pyrefly implement this like Final[Any], which is sort of coherent but a little surprising to me; mypy disallows it; pyright interprets Final itself as a type which is definitely buggy.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, I failed to mention that: Bare Finals in dataclasses are a thing, but were not mentioned by the specs. I wanted to keep the status quo in this PR, because it's not about that. I think this should be a separate discussion.

:term:`type qualifier`. For example, ``x: Final[int]``
in a dataclass body specifies a dataclass field ``x``, which
will be initialized in the generated ``__init__`` and cannot be assigned to
thereafter. A ``Final`` dataclass field initialized in the class body is not
a class attribute unless explicitly annotated with ``ClassVar``. For example,
Expand Down
4 changes: 2 additions & 2 deletions docs/spec/distributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ assignments::
x = 0 # type: int
x = ... # type: int

The :ref:`Literal shortcut using Final <literal-final-interactions>` should be
supported::
The :ref:`Literal shortcut using a bare Final <literal-final-interactions>`
should be supported::

x: Final = 0 # type is Literal[0]

Expand Down
8 changes: 8 additions & 0 deletions docs/spec/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ This section defines a few terms that may be used elsewhere in the specification
``B``, respectively, such that ``B'`` is a subtype of ``A'``. See
:ref:`type-system-concepts`.

bare ``Final``
A :ref:`Final <uppercase-final>` :term:`type qualifier` without an
explicit type, e.g. ``x: Final``.

closed
A :ref:`TypedDict <typeddict>` type is closed if it may not contain any
additional :term:`items <item>` beyond those specified in the TypedDict definition.
Expand Down Expand Up @@ -59,6 +63,10 @@ This section defines a few terms that may be used elsewhere in the specification
also materializations of ``B``, and all materializations of ``B`` are
also materializations of ``A``.

explicitly typed ``Final``
A :ref:`Final <uppercase-final>` :term:`type qualifier` with an explicit
type, e.g. ``x: Final[int]``.

extra items
A :ref:`TypedDict <typeddict>` type with extra items may contain arbitrary
additional key-value pairs beyond those specified in the TypedDict definition, but those
Expand Down
10 changes: 5 additions & 5 deletions docs/spec/literal.rst
Original file line number Diff line number Diff line change
Expand Up @@ -504,8 +504,8 @@ involving Literal bools. For example, we can combine ``Literal[True]``,
Interactions with Final
"""""""""""""""""""""""

The ``Final`` qualifier can be used to declare that some variable or
attribute cannot be reassigned::
A :term:`bare Final` type qualifier can be used to declare that some variable
or attribute cannot be reassigned::

foo: Final = 3
foo = 4 # Error: 'foo' is declared to be Final
Expand All @@ -518,8 +518,8 @@ is valid to use in any context that expects a ``Literal[3]``::

expects_three(foo) # Type checks, since 'foo' is Final and equal to 3

The ``Final`` qualifier serves as a shorthand for declaring that a variable
is *effectively Literal*.
A :term:`bare Final` type qualifier serves as a shorthand for declaring that a
variable is *effectively Literal*.

Type checkers are expected to
support this shortcut. Specifically, given a variable or attribute assignment
Expand All @@ -530,7 +530,7 @@ any context that expects a ``Literal[value]``.
Type checkers are not obligated to understand any other uses of Final. For
example, whether or not the following program type checks is left unspecified::

# Note: The assignment does not exactly match the form 'var: Final = value'.
# Note: Literal inference is unspecified for explicitly typed Final.
bar1: Final[int] = 3
expects_three(bar1) # May or may not be accepted by type checkers

Expand Down
42 changes: 24 additions & 18 deletions docs/spec/qualifiers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,30 +81,27 @@ variable or attribute should not be reassigned, redefined, or overridden.
Syntax
^^^^^^

``Final`` may be used in one of several forms:
``Final`` may be used in one of two forms:

* With an explicit type, using the syntax ``Final[<type>]``. Example::
* As an :term:`explicitly typed Final`, using the syntax ``Final[<type>]``.
Example::

ID: Final[float] = 1

* With no type annotation. Example::
In class bodies and stub files the right hand side can be omitted::

ID: Final[float]

* As a :term:`bare Final`, without a type argument. Example::

ID: Final = 1

Bare ``Final`` annotations must have an assigned value.

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
^^^^^^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -156,8 +153,8 @@ be initialized in the ``__init__`` method (except in stub files)::
self.x = 1 # Good

The generated ``__init__`` method of :doc:`dataclasses` qualifies for this
requirement: a bare ``x: Final[int]`` is permitted in a dataclass body, because
the generated ``__init__`` will initialize ``x``.
requirement: an ``x: Final[int]`` without an initializer is permitted in a
dataclass body, because the generated ``__init__`` will initialize ``x``.

Type checkers should infer a final attribute that is initialized in a class
body as being a class variable, except in the case of :doc:`dataclasses`, where
Expand All @@ -167,6 +164,15 @@ necessary to create a final class variable with value ``3``. In
non-dataclasses, combining ``ClassVar`` and ``Final`` is redundant, and type
checkers may choose to warn or error on the redundancy.

``Final`` attributes can also be declared in ``__init__`` methods, as
annotations on an assignment to an attribute on ``self``. This is allowed
*only* in ``__init__``, not in any other method::

class ImmutableAttributes:
def __init__(self) -> None:
self.x: Final = 3
self.y: Final[str] = "knight"

``Final`` may only be used 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::
Expand All @@ -193,9 +199,9 @@ with ``Final`` to prevent mutating such values::
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::
Type checkers should treat uses of names declared with a :term:`bare Final`
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

Expand Down
4 changes: 2 additions & 2 deletions docs/spec/typeddict.rst
Original file line number Diff line number Diff line change
Expand Up @@ -732,8 +732,8 @@ certain items. For example, ``d['x'] = 1`` should generate a type check error
if ``'x'`` is not a valid key for ``d`` (which is assumed to be a TypedDict
type), unless ``d`` has mutable :term:`extra items` of a compatible type.

Type checkers should allow :ref:`final names <uppercase-final>` with
string values to be used instead of string literals in operations on
Type checkers should allow names declared with a :term:`bare Final` and a
string value to be used instead of string literals in operations on
TypedDict objects. For example, this is valid::

YEAR: Final = 'year'
Expand Down
Loading