PEP 591 – Hinzufügen eines final-Qualifikators zu typing
- Autor:
- Michael J. Sullivan <sully at msully.net>, Ivan Levkivskyi <levkivskyi at gmail.com>
- BDFL-Delegate:
- Guido van Rossum <guido at python.org>
- Discussions-To:
- Typing-SIG list
- Status:
- Final
- Typ:
- Standards Track
- Thema:
- Typisierung
- Erstellt:
- 15. Mrz. 2019
- Python-Version:
- 3.8
- Post-History:
- Resolution:
- Typing-SIG Nachricht
Zusammenfassung
Dieses PEP schlägt einen „final“-Qualifikator vor, der dem typing Modul hinzugefügt werden soll – in Form eines final Dekorators und einer Final Typannotation – um drei zusammenhängende Zwecke zu erfüllen
- Deklarieren, dass eine Methode nicht überschrieben werden sollte
- Deklarieren, dass eine Klasse nicht unterklassifiziert werden sollte
- Deklarieren, dass eine Variable oder ein Attribut nicht neu zugewiesen werden sollte
Motivation
Der final Dekorator
Das aktuelle typing Modul hat keine Möglichkeit, die Verwendung von Vererbung oder Überschreibung auf Typüberprüfungsebene einzuschränken. Dies ist eine übliche Funktion in anderen objektorientierten Sprachen (wie Java) und nützlich, um den potenziellen Verhaltensraum einer Klasse zu reduzieren und die Argumentation zu erleichtern.
Einige Situationen, in denen eine finale Klasse oder Methode nützlich sein kann, umfassen
- Eine Klasse wurde nicht zum Unterklassifizieren entworfen, oder eine Methode wurde nicht zum Überschreiben entworfen. Vielleicht würde sie nicht wie erwartet funktionieren oder fehleranfällig sein.
- Unterklassifizieren oder Überschreiben würde den Code schwieriger zu verstehen oder zu warten machen. Zum Beispiel möchten Sie möglicherweise unnötig enge Kopplungen zwischen Basisklassen und Unterklassen verhindern.
- Sie möchten sich die Freiheit behalten, die Klassenimplementierung zukünftig beliebig zu ändern, und diese Änderungen könnten Unterklassen brechen.
Die Final Annotation
Dem aktuellen typing Modul fehlt eine Möglichkeit anzuzeigen, dass einer Variablen kein Wert zugewiesen wird. Dies ist in mehreren Situationen eine nützliche Funktion
- Verhindern der unbeabsichtigten Änderung von Modul- und Klassenebene-Konstanten und deren überprüfbare Dokumentation als Konstanten.
- Erstellen eines schreibgeschützten Attributs, das von Unterklassen nicht überschrieben werden kann. (
@propertykann ein Attribut schreibgeschützt machen, verhindert aber kein Überschreiben) - Ermöglichen der Verwendung eines Namens in Situationen, in denen normalerweise ein Literal erwartet wird (z. B. als Feldname für
NamedTuple, ein Tupel von Typen, die anisinstanceübergeben werden, oder ein Argument für eine Funktion mit Argumenten vom TypLiteral(PEP 586)).
Spezifikation
Der final Dekorator
Der typing.final Dekorator wird verwendet, um die Verwendung von Vererbung und Überschreibung einzuschränken.
Ein Typüberprüfer sollte jede Klasse, die mit @final dekoriert ist, vom Unterklassifizieren und jede Methode, die mit @final dekoriert ist, vom Überschreiben in einer Unterklasse verbieten. Die Methoden-Dekoratorversion kann mit Instanzmethoden, Klassenmethoden, statischen Methoden und Eigenschaften verwendet werden.
Zum Beispiel:
from typing import final
@final
class Base:
...
class Derived(Base): # Error: Cannot inherit from final class "Base"
...
und
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")
...
Für überladene Methoden sollte @final auf der Implementierung platziert werden (oder auf der ersten Überladung für 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):
...
Es ist ein Fehler, @final auf eine Nicht-Methodenfunktion anzuwenden.
Die Final Annotation
Der typing.Final Typqualifikator wird verwendet, um anzuzeigen, dass eine Variable oder ein Attribut nicht neu zugewiesen, neu definiert oder überschrieben werden sollte.
Syntax
Final kann in einer von mehreren Formen verwendet werden
- Mit einem expliziten Typ unter Verwendung der Syntax
Final[<Typ>]. BeispielID: Final[float] = 1
- Ohne Typannotation. Beispiel
ID: Final = 1
Der Typüberprüfer sollte seine üblichen Typinferenzmechanismen anwenden, um den Typ von
IDzu bestimmen (hier wahrscheinlichint). Beachten Sie, dass dies im Gegensatz zu generischen Klassen *nicht* dasselbe ist wieFinal[Any]. - In Klassenkörpern und Stub-Dateien können Sie die rechte Seite weglassen und einfach
ID: Final[float]schreiben. Wenn die rechte Seite weggelassen wird, muss ein explizites Typargument fürFinalvorhanden sein. - Schließlich, wie
self.id: Final = 1(optional auch mit einem Typ in eckigen Klammern). Dies ist *nur* in__init__Methoden erlaubt, damit das finale Instanzattribut nur einmal bei der Erstellung einer Instanz zugewiesen wird.
Semantik und Beispiele
Die beiden Hauptregeln für die Definition eines finalen Namens sind
- Es kann *höchstens eine* finale Deklaration pro Modul oder Klasse für ein gegebenes Attribut geben. Es kann keine separaten Konstanten auf Klassen- und Instanzebene mit demselben Namen geben.
- Es muss *genau eine* Zuweisung an einen finalen Namen geben.
Das bedeutet, ein Typüberprüfer sollte weitere Zuweisungen an finale Namen in typüberprüftem Code verhindern
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
Beachten Sie, dass ein Typüberprüfer Final-Deklarationen nicht innerhalb von Schleifen zulassen muss, da die Laufzeit in nachfolgenden Iterationen mehrere Zuweisungen an dieselbe Variable sehen würde.
Zusätzlich sollte ein Typüberprüfer verhindern, dass finale Attribute in einer Unterklasse überschrieben werden
from typing import Final
class Window:
BORDER_WIDTH: Final = 2.5
...
class ListView(Window):
BORDER_WIDTH = 3 # Error: can't override a final attribute
Ein finales Attribut, das in einem Klassenkörper ohne Initialisierer deklariert wurde, muss in der __init__ Methode initialisiert werden (außer in Stub-Dateien)
class ImmutablePoint:
x: Final[int]
y: Final[int] # Error: final attribute without an initializer
def __init__(self) -> None:
self.x = 1 # Good
Typüberprüfer sollten ein finales Attribut, das in einem Klassenkörper initialisiert wird, als Klassenvariable inferieren. Variablen sollten nicht sowohl mit ClassVar als auch mit Final annotiert werden.
Final kann nur als äußerster Typ bei Zuweisungen oder Variablenannotationen verwendet werden. Die Verwendung an einer anderen Stelle ist ein Fehler. Insbesondere kann Final nicht in Annotationen für Funktionsargumente verwendet werden
x: List[Final[int]] = [] # Error!
def fun(x: Final[List[int]]) -> None: # Error!
...
Beachten Sie, dass die Deklaration eines Namens als final nur garantiert, dass der Name nicht an einen anderen Wert gebunden wird, aber den Wert nicht unveränderlich macht. Unveränderliche ABCs und Container können in Kombination mit Final verwendet werden, um die Änderung solcher Werte zu verhindern
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
Typüberprüfer sollten Verwendungen eines finalen Namens, der mit einem Literal initialisiert wurde, so behandeln, als ob er durch das Literal ersetzt worden wäre. Zum Beispiel sollte Folgendes erlaubt sein
from typing import NamedTuple, Final
X: Final = "x"
Y: Final = "y"
N = NamedTuple("N", [(X, int), (Y, int)])
Referenzimplementierung
Der mypy [1] Typüberprüfer unterstützt Final und final. Eine Referenzimplementierung der Laufzeitkomponente wird im Modul typing_extensions [2] bereitgestellt.
Abgelehnte/verschobene Ideen
Der Name Const wurde auch als Name für die Final Typannotation in Betracht gezogen. Stattdessen wurde der Name Final gewählt, da die Konzepte verwandt sind und es am besten schien, zwischen ihnen konsistent zu sein.
Wir haben erwogen, einen einzigen Namen Final anstelle der Einführung von final zu verwenden, aber @Final sah für uns einfach zu seltsam aus.
Eine verwandte Funktion zu finalen Klassen wären Scala-ähnliche versiegelte Klassen, bei denen eine Klasse nur von Klassen geerbt werden darf, die im selben Modul definiert sind. Versiegelte Klassen scheinen am nützlichsten in Kombination mit Pattern Matching zu sein, so dass es in unserem Fall die Komplexität nicht rechtfertigt. Dies könnte in Zukunft noch einmal überdacht werden.
Es wäre möglich, dass der @final Dekorator auf Klassen zur Laufzeit dynamisch das Unterklassifizieren verhindert. Nichts anderes in typing erzwingt Laufzeitprüfungen, daher wird final dies auch nicht tun. Ein Workaround, wenn sowohl Laufzeit- als auch statische Überprüfung gewünscht sind, ist die Verwendung dieses Idioms (möglicherweise in einem Support-Modul)
if typing.TYPE_CHECKING:
from typing import final
else:
from runtime_final import final
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0591.rst
Zuletzt geändert: 2024-06-11 22:12:09 GMT