PEP 661 – Sentinel-Werte, nach 5 Jahren genehmigt
(peps.python.org)- PEP 661 schlägt das Python-Builtin-Callable
sentinel()und die C-APIPySentinel_New()vor, um einen separat unterscheidbaren Sentinel-Wert zu erzeugen, wennNoneein gültiger Wert ist - Das bisherige Idiom
_sentinel = object()hat in Funktionssignaturen ein langes und unklar wirkendesrepr, und es kann Probleme mit klaren Typsignaturen, Kopieren und Pickling geben - Der Aufruf
sentinel('MISSING')erzeugt ein neues eindeutiges Objekt mit kurzemrepr; wenn derselbe Sentinel gemeinsam genutzt werden soll, muss er explizit wiederverwendet werden, etwa durch Zuweisung an eine Variable wieMISSING = sentinel('MISSING') - Für Sentinel-Werte wird ein Vergleich per
isempfohlen, sie werden als wahr ausgewertet,copy.copy()undcopy.deepcopy()geben dasselbe Objekt zurück, und wenn sie aus einem Modul per Namen importierbar sind, bleibt ihre Identität auch nach dem Pickling erhalten - Das Typsystem erlaubt, den Sentinel selbst in Typsausdrücken wie
int | MISSINGzu verwenden; die aktuelle offizielle Dokumentation findet sich in der Python-3.15-Dokumentation zu [sentinel](<https://docs.python.org/3.15/library/functions.html#sentinel "(in Python v3.15>)")
Hintergrund der Einführung
- Ein Sentinel-Wert (sentinel value) als eindeutiger Platzhalterwert wird etwa als Standardwert verwendet, wenn ein Funktionsargument nicht übergeben wurde, als Rückgabewert zur Kennzeichnung einer erfolglosen Suche oder als Wert für fehlende Daten
- In Python gibt es dafür meist den speziellen Wert
None, aber in Kontexten, in denenNoneselbst ein gültiger Wert ist, wird ein separater Sentinel-Wert benötigt, der sich vonNoneunterscheiden lässt - Im Mai 2021 wurde auf der Mailingliste python-dev diskutiert, wie sich der in
traceback.print_exceptionverwendete Sentinel-Wert besser implementieren ließe - Die bestehende Implementierung verwendete das verbreitete Idiom
_sentinel = object(), aber dessenreprwar zu lang und wenig aussagekräftig, wodurch Funktionssignaturen schwer lesbar wurden>>> help(traceback.print_exception) Help on function print_exception in module traceback: print_exception(exc, /, value=<object object at 0x000002825DF09650>, tb=<object object at 0x000002825DF09650>, limit=None, file=None, chain=True) - Im Verlauf der Diskussion wurden weitere Probleme bestehender Sentinel-Implementierungen festgestellt
- Einige Sentinel-Werte haben keinen eigenen Typ, wodurch sich für Funktionen, die einen Sentinel als Standardwert verwenden, nur schwer eine klare Typsignatur definieren lässt
- Nach dem Kopieren entsteht teils eine separate Instanz, sodass Vergleiche mit
isfehlschlagen und sich das Verhalten anders als erwartet zeigt - Einige verbreitete Idiome haben nach dem Pickling und anschließenden Unpickling ähnliche Probleme
- Victor Stinner stellte eine Liste der in der Python-Standardbibliothek verwendeten Sentinel-Werte bereit; dabei zeigte sich, dass selbst innerhalb der Standardbibliothek verschiedene Implementierungsansätze verwendet werden und viele Implementierungen mindestens eines der genannten Probleme haben
- Die Abstimmung auf discuss.python.org brachte bei 39 Stimmen kein klares Ergebnis
- 40 % wählten „der aktuelle Zustand ist in Ordnung und Konsistenz ist nicht nötig“
- Die Mehrheit entschied sich für eine oder mehrere standardisierte Lösungen
- 37 % wählten die Option, „eine neue dedizierte Sentinel-Factory/Klasse/Metaklasse konsistent zu verwenden und in der Standardbibliothek öffentlich bereitzustellen“
- Wegen dieses gemischten Ergebnisses wurde ein PEP verfasst, was zu der Schlussfolgerung führte, dass eine einfache und gute Implementierung in der Standardbibliothek sowohl innerhalb als auch außerhalb der Standardbibliothek nützlich ist
- Es ist nicht zwingend erforderlich, alle bestehenden Sentinel-Werte der Standardbibliothek auf diese Weise umzustellen; das liegt im Ermessen der jeweiligen Maintainer
- Das PEP-Dokument ist ein historisches Dokument; die aktuelle offizielle Dokumentation findet sich in der Python-3.15-Dokumentation zu [
sentinel](<https://docs.python.org/3.15/library/functions.html#sentinel "(in Python v3.15>)")
Designkriterien
- Ein Sentinel-Objekt muss bei einem Vergleich mit dem
is-Operator immer mit sich selbst identisch sein und mit keinem anderen Objekt identisch sein - Die Erzeugung eines Sentinel-Objekts soll eine einfache und intuitive einzeilige Anweisung sein
- Es soll leicht möglich sein, beliebig viele unterschiedliche Sentinel-Werte zu definieren
- Sentinel-Objekte sollen ein kurzes und klares
reprhaben - Für Sentinel-Werte sollen klare Typsignaturen verwendbar sein
- Sie sollen auch nach dem Kopieren korrekt funktionieren und beim Pickling und Unpickling ein vorhersehbares Verhalten zeigen
- Sie sollen mit CPython 3.x und PyPy3 funktionieren und möglichst auch mit anderen Python-Implementierungen
- Sowohl Implementierung als auch Nutzung sollen so einfach und intuitiv wie möglich sein und beim Erlernen von Python nicht noch ein weiteres besonderes Konzept als Belastung hinzufügen
- Da die Standardbibliothek nicht von PyPI-Paketimplementierungen wie
sentinelsodersentinelabhängen kann, wird eine Implementierung benötigt, die innerhalb der Standardbibliothek verwendet werden kann
sentinel()-Spezifikation
- Ein neues eingebautes aufrufbares Objekt
sentinelwird hinzugefügt>>> MISSING = sentinel('MISSING') >>> MISSING MISSING sentinel()akzeptiert genau ein positionsgebundenes Argumentname, undnamemuss einstrsein- Wird ein Nicht-String-Wert übergeben, wird ein
TypeErrorausgelöst namedient als Name undreprdes Sentinel-Werts- Sentinel-Objekte haben zwei öffentliche Attribute
__name__: Name des Sentinel-Werts__module__: Name des Moduls, in demsentinel()aufgerufen wurde
sentinelkann nicht als Basisklasse verwendet werden- Jeder Aufruf von
sentinel(name)gibt ein neues Sentinel-Objekt zurück - Wenn dasselbe Sentinel an mehreren Stellen verwendet werden soll, muss man es – wie beim bisherigen Idiom
MISSING = object()– einer Variablen zuweisen und dasselbe Objekt anschließend explizit wiederverwendenMISSING = sentinel('MISSING') def read_value(default=MISSING): ... - Um zu prüfen, ob ein bestimmter Wert ein Sentinel ist, wird – wie bei
None– die Verwendung desis-Operators empfohlen - Auch ein
==-Vergleich verhält sich wie erwartet und gibt nur beim Vergleich mit sich selbstTruezurück - Eine Identitätsprüfung wie
if value is MISSING:ist in der Regel passender als ein Bool-Test wieif value:oderif not value: - Sentinel-Objekte sind truthy, ihre Bool-Auswertung ist also
True- Das entspricht dem Standardverhalten beliebiger Klassen und dem Bool-Wert von
Ellipsis - Anders als bei
None, das falsy ist
- Das entspricht dem Standardverhalten beliebiger Klassen und dem Bool-Wert von
- Wenn ein Sentinel-Objekt mit
copy.copy()odercopy.deepcopy()kopiert wird, wird dasselbe Objekt zurückgegeben - Sentinel-Werte, die aus dem definierenden Modul per Name importierbar sind, behalten gemäß dem Standard-Pickle-Mechanismus ihre Identität auch nach Pickling und Unpickling bei
MISSING = sentinel('MISSING') assert pickle.loads(pickle.dumps(MISSING)) is MISSING sentinel()speichert beim Erzeugen eines Sentinel-Werts das aufrufende Modul im Attribut__module__- Beim Pickling werden Modul und Name des Sentinel-Werts gespeichert, beim Unpickling wird das Modul importiert und der Sentinel-Wert anhand seines Namens geholt
- Sentinel-Werte, die nicht per Modul und Name importierbar sind – etwa solche, die in einem lokalen Scope erzeugt und nicht einem passenden Namen auf Modulebene oder als Klassenattribut zugewiesen wurden – können nicht gepickelt werden
- Das
repreines Sentinel-Objekts ist das ansentinel()übergebenename; ein impliziter Modulqualifizierer wird nicht angehängt - Wenn ein qualifiziertes
reprbenötigt wird, muss es explizit im Namen enthalten sein>>> MyClass_NotGiven = sentinel('MyClass.NotGiven') >>> MyClass_NotGiven MyClass.NotGiven - Ordnungsvergleiche für Sentinel-Objekte sind nicht definiert
- Sentinel-Werte unterstützen keine weakrefs
Typisierung
- Damit die Verwendung von Sentinel-Werten in typisiertem Python-Code klar und einfach wird, wird dem Typsystem eine Sonderbehandlung für Sentinel-Objekte hinzugefügt
- Sentinel-Objekte können in Typausdrücken") als Werte verwendet werden, die sich selbst repräsentieren
- Das ist vergleichbar mit der bestehenden Behandlung von
Noneim TypsystemMISSING = sentinel('MISSING') def foo(value: int | MISSING = MISSING) -> int: ... - Type-Checker sollen eine Sentinel-Erzeugung der Form
NAME = sentinel('NAME')als Erzeugung eines neuen Sentinel-Objekts erkennen - Wenn der an
sentinel()übergebene Name nicht mit dem Namen des Zuweisungsziels übereinstimmt, soll der Type-Checker einen Fehler melden - Mit dieser Syntax definierte Sentinel-Werte können in Typausdrücken") verwendet werden
- Der entsprechende Sentinel-Typ stellt einen vollständig statischen Typ") dar, dessen einziges Element das Sentinel-Objekt selbst ist
- Type-Checker sollen die Einengung von Union-Typen mit Sentinel-Werten über die Operatoren
isundis notunterstützenfrom typing import assert_type MISSING = sentinel('MISSING') def foo(value: int | MISSING) -> None: if value is MISSING: assert_type(value, MISSING) else: assert_type(value, int) - Die Runtime-Implementierung muss die Methoden
__or__und__ror__bereitstellen, um die Verwendung in Typausdrücken zu unterstützen; diese Methoden geben ein Objekt vom Typtyping.Union") zurück - Der Typing Council unterstützt die typbezogenen Teile dieses Vorschlags
C-API
- Da Sentinel-Werte auch in C-Erweiterungen nützlich sein können, werden zwei neue C-API-Funktionen vorgeschlagen
PyObject *PySentinel_New(const char *name, const char *module_name)erzeugt ein neues Sentinel-Objektbool PySentinel_Check(PyObject *obj)prüft, ob ein Objekt ein Sentinel ist- C-Code kann zur Prüfung auf ein bestimmtes Sentinel den Operator
==verwenden
Kompatibilität und Sicherheit
- Das Hinzufügen eines neuen eingebauten Namens bedeutet, dass Code, der derzeit annimmt, dass der Bare Name
sentineleinenNameErrorauslöst, nicht mehr dasselbe Ergebnis sieht - Das ist ein üblicher Kompatibilitätsaspekt beim Hinzufügen neuer eingebauter Namen
- Bereits vorhandene lokale, globale oder importierte Namen
sentinelsind davon nicht betroffen - Bereits existierender Code, der den Namen
sentinelverwendet, muss möglicherweise angepasst werden, um das neue eingebaute Objekt zu verwenden, und könnte neue Warnungen von Lintern erhalten, die vor Kollisionen mit eingebauten Namen warnen - Die übliche Dokumentation neuer Built-ins über Docstrings, Bibliotheksdokumentation und den Abschnitt „What’s New“ wird als ausreichend angesehen
- Es wird davon ausgegangen, dass dieser Vorschlag keine Sicherheitsauswirkungen hat
Referenzimplementierung und Backport
- Die Referenzimplementierung wird als CPython-Pull-Request [10] bereitgestellt
- Eine frühere Referenzimplementierung befindet sich in einem separaten GitHub-Repository [7]
- Eine Skizze des beabsichtigten Verhaltens sieht wie folgt aus
class sentinel: """Unique sentinel values.""" __slots__ = ("__name__", "_module_name") def __init_subclass__(cls): raise TypeError("type 'sentinel' is not an acceptable base type") def __init__(self, name, /): if not isinstance(name, str): raise TypeError("sentinel name must be a string") self.__name__ = name self._module_name = sys._getframemodulename(1) @property def __module__(self): return self._module_name def __repr__(self): return self.__name__ def __reduce__(self): return self.__name__ def __copy__(self): return self def __deepcopy__(self, memo): return self def __or__(self, other): return typing.Union[self, other] def __ror__(self, other): return typing.Union[other, self]- Das Modul typing-extensions enthält zwar einen Backport, dieser entspricht derzeit jedoch nicht exakt dem Verhalten der aktuellen PEP-Iteration.
Abgelehnte Alternativen
-
Verwendung von
NotGiven = object()- Dieser Ansatz hat alle Nachteile, die in den Entwurfskriterien des PEP behandelt werden
- Das
reprist lang und nicht eindeutig, Typsignaturen lassen sich nur schwer klar ausdrücken, und es kann Probleme beim Kopieren oder Pickling geben
-
Hinzufügen eines einzelnen neuen Sentinel-Werts wie
MISSINGoderSentinel- Wenn ein einzelner Wert an mehreren Stellen für verschiedene Zwecke verwendet wird, ist es in manchen Anwendungsfällen schwer, immer sicher zu sein, dass dieser Wert selbst kein gültiger Wert ist
- Dedizierte, unterschiedliche Sentinel-Werte lassen sich selbstbewusster verwenden, ohne potenzielle Edge Cases berücksichtigen zu müssen
- Sentinel-Werte sollten aussagekräftige Namen und ein passendes
reprfür ihren Nutzungskontext bereitstellen können - Diese Option war sehr unbeliebt und wurde in der Abstimmung nur von 12 % gewählt
-
Verwendung des bestehenden Sentinel-Werts
EllipsisEllipsisist ursprünglich nicht für diesen Zweck gedacht- Es wird zwar zunehmend verwendet, um leere Klassen- oder Funktionsblöcke anstelle von
passzu definieren, lässt sich aber nicht in allen Fällen so zuverlässig einsetzen wie dedizierte, unterschiedliche Sentinel-Werte
-
Verwendung eines Ein-Wert-
Enum- Das vorgeschlagene Idiom lautet wie folgt
class NotGivenType(Enum): NotGiven = 'NotGiven' NotGiven = NotGivenType.NotGiven - Es enthält zu viele Wiederholungen, und das
reprist mit etwas wie<NotGivenType.NotGiven: 'NotGiven'>zu lang - Man könnte ein kürzeres
reprdefinieren, aber dadurch würden Code und Wiederholungen noch zunehmen - Unter den 9 Optionen der Abstimmung war dies die einzige, die keine Stimme erhielt, und damit die unpopulärste
-
Sentinel-Klassendekorator
- Das vorgeschlagene Idiom lautet wie folgt
@sentinel class NotGivenType: pass NotGiven = NotGivenType() - Die Implementierung des Dekorators selbst könnte einfach und klar sein, aber das Idiom ist zu wortreich, repetitiv und schwer zu merken
- Das vorgeschlagene Idiom lautet wie folgt
-
Verwendung von Klassenobjekten
- Klassen sind ihrem Wesen nach Singletons, daher ist die Idee denkbar, sie als Sentinel-Werte zu verwenden
- Die einfachste Form sieht so aus
class NotGiven: pass- Um ein eindeutiges
reprzu erhalten, ist eine Metaklasse oder ein Klassendekorator erforderlich
class NotGiven(metaclass=SentinelMeta): pass@Sentinel class NotGiven: pass - Um ein eindeutiges
- Klassen auf diese Weise zu verwenden ist ungewöhnlich und kann verwirrend sein
- Ohne Kommentare ist die Absicht des Codes schwer zu verstehen, und es entstehen unerwartete, unerwünschte Verhaltensweisen, etwa dass das Sentinel aufrufbar wird
-
Nur ein empfohlenes Standard-Idiom definieren, ohne Implementierung
- Die meisten gängigen bestehenden Idiome haben erhebliche Nachteile
- Bisher wurde kein klares und knappes Idiom gefunden, das diese Nachteile vermeidet
- In der zugehörigen Abstimmung waren Optionen, die nur ein Idiom empfahlen, unbeliebt, und selbst die Option mit den meisten Stimmen kam nur auf 25 %
-
Verwendung eines neuen Standardbibliotheksmoduls
- Der erste Entwurf schlug vor, die Klasse
Sentinelin ein neues Modulsentinelsodersentinellibaufzunehmen - Für ein einzelnes öffentliches aufrufbares Objekt ein neues Modul hinzuzufügen ist unnötig
- Mit einem Modul wäre die Nutzung weniger bequem als beim bestehenden
object()-Idiom - Auch der Steering Council empfahl ausdrücklich, daraus eine eingebaute Funktionalität zu machen, damit sie sich mindestens so einfach wie
object()verwenden lässt - Der Name
sentinelskollidiert zudem mit einem bereits aktiv genutzten PyPI-Paket; als Built-in lässt sich dieses Namensproblem vermeiden
- Der erste Entwurf schlug vor, die Klasse
-
Verwendung eines modulweiten Namensregisters für Sentinel-Namen
- Der erste Entwurf schlug vor, Sentinel-Namen innerhalb eines Moduls eindeutig zu machen
- In diesem Entwurf würde ein wiederholter Aufruf von
sentinel("MISSING")im selben Modul dasselbe Objekt zurückgeben, über ein prozessweites globales Register mit Modulname und Sentinel-Name als Schlüssel - Dieses Verhalten wurde als zu implizit angesehen und abgelehnt
- Wenn ein gemeinsam genutztes Sentinel erforderlich ist, kann man wie beim bestehenden
MISSING = object()explizit eines definieren und es per Namen wiederverwenden - In lokalem Scope möchte man bei jedem Aufruf oder jeder Wiederholung möglicherweise ein neues Sentinel, daher sollte ein wiederholter Aufruf von
sentinel(name)wie ein wiederholter Aufruf vonobject()unterschiedliche Objekte erzeugen - Durch das Entfernen des Registers werden Implementierung und Denkmodell einfacher, und es bleibt nur noch die Regel:
sentinel(name)erzeugt ein neues eindeutiges Objekt mitreprgleichname
-
Automatische Erkennung oder Übergabe des Modulnamens
- Der erste Entwurf schlug ein optionales Argument
module_namevor, um den registerbasierten Entwurf zu unterstützen - Mit dem Wegfall des Registers wird das öffentliche Argument
module_namefür den Kernvorschlag nicht mehr benötigt - Die Implementierung zeichnet das aufrufende Modul intern auf, damit Pickle importierbare Sentinel-Werte ähnlich wie
TypeVarüber Modul und Name serialisieren kann - Der interne Modulname hat keinen Einfluss auf das
reprdes Sentinels - Wenn ein
reprmit Modul- oder Klassenname gewünscht ist, kann dies explizit in das einzelne Argumentnameaufgenommen werden, etwa mitsentinel("mymodule.MISSING")
- Der erste Entwurf schlug ein optionales Argument
-
Benutzerdefiniertes
reprerlauben- Ein Vorteil wäre gewesen, dass sich bestehende Sentinel-Werte ohne Änderung ihres
reprauf diese Weise migrieren ließen - Dies wurde jedoch ausgeschlossen, weil der zusätzliche Aufwand die Komplexität nicht rechtfertigt
- Ein Vorteil wäre gewesen, dass sich bestehende Sentinel-Werte ohne Änderung ihres
-
Benutzerdefinierte Bool-Auswertung erlauben
- In der Diskussion wurde geprüft, Sentinel-Werte explizit als wahr, falsch oder nicht in
boolkonvertierbar gestalten zu können - Einige Drittanbieter-Sentinels bieten falsches Verhalten als Teil ihrer öffentlichen API an
- Mehrere Beteiligte hielten es für besser, in booleschen Kontexten eine Ausnahme auszulösen, um Identitätsprüfungen stärker zu erzwingen
- Das PEP hält den ursprünglichen Vorschlag bewusst einfach, indem es das Standardverhalten normaler Objekte als wahr beibehält und Identitätsprüfungen empfiehlt
- Benutzerdefiniertes boolesches Verhalten könnte später geprüft werden, wenn der zusätzliche API- und Typisierungsaufwand als lohnend angesehen wird
- In der Diskussion wurde geprüft, Sentinel-Werte explizit als wahr, falsch oder nicht in
-
Verwendung von
typing.Literalin Typannotationen- Dies wurde in der Diskussion von mehreren Personen vorgeschlagen, und das PEP übernahm diesen Ansatz anfangs auch
- Es kann jedoch verwirrend sein, weil
Literal["MISSING"]nicht eine Vorwärtsreferenz auf den Sentinel-WertMISSING, sondern auf den String-Wert"MISSING"verweist - Auch die Verwendung eines bare name wurde in der Diskussion häufig vorgeschlagen
- Der Ansatz mit bare name folgt dem von
Nonegeschaffenen Präzedenzfall und einem bekannten Muster, benötigt keinen Import und ist deutlich kürzer
Zusätzliche Nutzungshinweise
- Wenn ein Sentinel im Klassen-Scope definiert wird, Namenskonflikte vermieden werden sollen oder ein qualifiziertes
reprklarer ist, sollte der gewünschte qualifizierte Name explizit übergeben werden>>> class MyClass: ... NotGiven = sentinel('MyClass.NotGiven') >>> MyClass.NotGiven MyClass.NotGiven - Es ist erlaubt, Sentinel-Werte innerhalb von Funktionen oder Methoden zu erzeugen
- Da jeder Aufruf von
sentinel()ein anderes Objekt erzeugt, verhalten sich Sentinel-Werte aus lokalem Scope wie Werte, die dort durch Aufrufe vonobject()erzeugt wurden - Der boolesche Wert von
NotImplementedistTrue, aber seine Verwendung ist seit Python 3.9 veraltet und löst eine Deprecation Warning aus - Diese Veraltung liegt an Problemen, die speziell
NotImplementedbetreffen und in bpo-35712 [8] beschrieben sind - Wenn mehrere zusammengehörige Sentinel-Werte definiert werden müssen oder eine Reihenfolge zwischen ihnen festgelegt werden soll, sollte
Enumoder ein ähnlicher Ansatz verwendet werden - Zur Typisierung solcher Sentinel-Werte wurden in der Mailingliste typing-sig [9] mehrere Optionen diskutiert
1 Kommentare
Lobste.rs-Meinungen
Der gewählte Name wirkt seltsam, weil seine Bedeutung zu eng erscheint
Dem Namen nach hätte etwas in Richtung eindeutiges Symbol wie ein flexibleres Grundelement gewirkt. In der Praxis würde es sich ohnehin fast wie ein Symbol verhalten, also könnte man es so verwenden, aber es „Sentinels“ zu nennen, fühlt sich merkwürdig an. Vielleicht liegt das auch daran, dass ich an Lisp gewöhnt bin
SENTINEL_Aein anderer Typ alsSENTINEL_Bist, damit man fragen kann, ob ein Wertis_a SENTINEL_AistRubys Symbole verhalten sich nicht so:
:beef.is_a? :droog.class #=> trueLiteralund Literal-StringsDass diese benannte Sentinels sind, liegt daran, dass sentinel values in Python ein verbreitetes Konzept und Muster sind und Sentinels einige Probleme gezielt lösen sollen, die bei der Nutzung dieses Musters entstehen. Genau so wird es in den Abschnitten „Motivation“ und „Rationale“ erklärt
Außerdem haben Sentinels keine Wertsemantik, daher sind auch zwei Sentinels mit demselben Namen unterschiedliche Werte und einander nicht gleich. Sie verhalten sich also nicht wie Symbole und sollten auch nicht so verwendet werden
Beim Problem mit Standardwerten für benannte Argumente könnte Typst mit
noneplus einemauto-Wert fast alle gewünschten benannten Argument-Interfaces ausdrückenMit
noneallein lässt sich die Bedeutung der meisten Standardwerte benannter Argumente nicht gut ausdrücken.noneeignet sich als Standardrückgabewert, aber als Funktionsargument transportiert es oft nicht die richtige Bedeutung als Substantiv. Beimatrix(axes=None)ist unklar, ob das bedeutet, die Achsen zu entfernen, oder sie wie üblich beizubehalten. Auch ist nicht klar, ob es einen Unterschied macht,nonezu übergeben oder gar nichts zu übergeben. Wenn man zur Unterscheidung der Präsenz eines Parameters zu Multiple Dispatch greift, verliert man die zentrale Stelle, an der das Verhalten dieses Parameters dokumentiert werden sollteautoist ein hervorragender Standardwert, weil es direkt bedeutet: „Verarbeite es passend anhand der vorhandenen Informationen.“ Eine Signaturauto | nonekann wie ein expliziteres Boolean verwendet werden, undT | auto | noneverrät schon ziemlich viel darüber, wie eine Funktion den Wert nutzen wird. WennTzum Beispielcolorist, wirdautowahrscheinlich einen Standardwert wie Weiß/Schwarz wählen oder vom Elternkontext erben,Tsetzt die Farbe explizit, undnonekann je nach Kontext bedeuten, die Farbe gar nicht zu setzen oder sie als transparent zu behandelnInteressant, und ich frage mich, wie sich dadurch die Semantik einiger Pakete ändern wird. Statt zum Beispiel
Item | Nonezurückzugeben, könnte man Folgendes schreibenNatürlich könnte man auch mit mehreren Sentinels zusätzliche Bedeutung transportieren. Das war vorher schon möglich, aber es gab in der Dokumentation keinen „offiziell empfohlenen“ Weg dafür. Das könnte Paketautoren in eine andere Richtung lenken
Etwas konstruiertes Beispiel, aber hier könnte man unterscheiden zwischen dem Fall, dass eine bestehende ID keinen verknüpften Wert hat, und dem Fall, dass es diese ID gar nicht gibt und deshalb ein Fehler auftritt. Die „Pythonische“ Variante wäre vermutlich eher, eine Exception zu verwenden, aber das wirkt mehr nach einem funktionalen Ansatz, als man Python sonst typischerweise schreibt
Wahrscheinlich wäre es besser gewesen, einfach die
Symbol-API aus JavaScript zu übernehmen. Sie ist allgemein nützlich und würde auch das hier zu lösende Problem mit abdecken