Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 455 – Hinzufügen eines schlüsseltransformierenden Dictionaries zu collections

Autor:
Antoine Pitrou <solipsis at pitrou.net>
BDFL-Delegate:
Raymond Hettinger
Status:
Abgelehnt
Typ:
Standards Track
Erstellt:
13. Sep. 2013
Python-Version:
3.5
Post-History:


Inhaltsverzeichnis

Zusammenfassung

Dieser PEP schlägt eine neue Datenstruktur für das Modul collections vor, die in diesem PEP als „TransformDict“ bezeichnet wird. Diese Struktur ist eine mutable Zuordnung, die den Schlüssel bei der Suche mithilfe einer gegebenen Funktion transformiert, aber den ursprünglichen Schlüssel beim Lesen beibehält.

Ablehnung

Siehe die Begründung unter https://mail.python.org/pipermail/python-dev/2015-May/140003.html und für eine frühere Teilprüfung siehe https://mail.python.org/pipermail/python-dev/2013-October/129937.html .

Begründung

Zahlreiche spezialisierte Versionen dieses Musters existieren. Die häufigste ist ein fall-unempfindliches, aber Groß-/Kleinschreibung-erhaltendes Dictionary, d.h. ein Dictionary-ähnlicher Container, der Schlüssel fall-unempfindlich abgleicht, aber die ursprüngliche Groß-/Kleinschreibung beibehält. Dies ist ein sehr häufiger Bedarf in der Netzwerkprogrammierung, da viele Protokolle Arrays von „Schlüssel/Wert“-Eigenschaften in ihren Nachrichten aufweisen, bei denen die Schlüssel Textzeichenfolgen sind, deren Groß-/Kleinschreibung beim Empfang ignoriert werden soll, aber gemäß Spezifikation oder Konvention bei Wiederübertragung beibehalten oder nicht trivial kanonisiert werden muss.

Eine weitere häufige Anforderung ist ein Identitäts-Dictionary, bei dem Schlüssel gemäß ihren jeweiligen id()s anstelle des normalen Abgleichs abgeglichen werden.

Beides sind Instanzen eines allgemeineren Musters, bei dem eine gegebene Transformationsfunktion auf Schlüssel angewendet wird, wenn sie nachgeschlagen werden: Diese Funktion ist str.lower oder str.casefold im ersteren Beispiel und die eingebaute Funktion id im letzteren.

(Man könnte sagen, dass das Muster Schlüssel aus der benutzerseitig sichtbaren Menge auf die interne Nachschagesmenge *projiziert*.)

Semantik

TransformDict ist eine Implementierung von MutableMapping: Sie implementiert getreu die bekannte API von mutable Mappings wie dict selbst und andere dictionary-ähnliche Klassen in der Standardbibliothek. Daher wird dieser PEP die Semantik der meisten TransformDict-Methoden nicht wiederholen.

Die Transformationsfunktion muss nicht bijektiv sein, sie kann streng surjektiv sein wie im fall-unempfindlichen Beispiel (mit anderen Worten, verschiedene Schlüssel können auf denselben Wert nachgeschlagen werden)

>>> d = TransformDict(str.casefold)
>>> d['SomeKey'] = 5
>>> d['somekey']
5
>>> d['SOMEKEY']
5

TransformDict behält den ersten Schlüssel bei, der beim Erstellen eines Eintrags verwendet wurde.

>>> d = TransformDict(str.casefold)
>>> d['SomeKey'] = 1
>>> d['somekey'] = 2
>>> list(d.items())
[('SomeKey', 2)]

Die ursprünglichen Schlüssel müssen nicht hashbar sein, solange die Transformationsfunktion eine hashbare zurückgibt.

>>> d = TransformDict(id)
>>> l = [None]
>>> d[l] = 5
>>> l in d
True

Konstruktor

Wie in den obigen Beispielen gezeigt, erfordert die Erstellung eines TransformDicts die Übergabe der Schlüsseltransformationsfunktion als erstes Argument (ähnlich wie die Erstellung eines defaultdict die Übergabe der Factory-Funktion als erstes Argument erfordert).

Der Konstruktor nimmt auch andere optionale Argumente entgegen, die verwendet werden können, um das TransformDict mit bestimmten Schlüssel-Wert-Paaren zu initialisieren. Diese optionalen Argumente sind die gleichen wie in den Konstruktoren von dict und defaultdict.

>>> d = TransformDict(str.casefold, [('Foo', 1)], Bar=2)
>>> sorted(d.items())
[('Bar', 2), ('Foo', 1)]

Abrufen des ursprünglichen Schlüssels

TransformDict bietet auch eine Lookup-Methode, die den gespeicherten Schlüssel zusammen mit dem entsprechenden Wert zurückgibt.

>>> d = TransformDict(str.casefold, {'Foo': 1})
>>> d.getitem('FOO')
('Foo', 1)
>>> d.getitem('bar')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'bar'

Der Methodenname getitem() folgt der Standardmethode popitem() bei mutable Mappings.

Abrufen der Transformationsfunktion

TransformDict hat eine einfache schreibgeschützte Eigenschaft transform_func, die die Transformationsfunktion zurückgibt.

Alternative Vorschläge und Fragen

Beibehalten des letzten ursprünglichen Schlüssels

Die meisten python-dev-Antwortenden fanden das Beibehalten des ersten vom Benutzer bereitgestellten Schlüssels intuitiver als das Beibehalten des letzten. Außerdem passt es zum Verhalten des Dictionary-Objekts selbst, wenn unterschiedliche, aber gleiche Schlüssel verwendet werden.

>>> d = {}
>>> d[1] = 'hello'
>>> d[1.0] = 'world'
>>> d
{1: 'world'}

Darüber hinaus ist das explizite Beibehalten des letzten Schlüssels in einem System, das den ersten Schlüssel beibehält, mit dem folgenden Ansatz immer noch möglich.

d.pop(key, None)
d[key] = value

während das Gegenteil (das Beibehalten des ersten Schlüssels in einem System, das den letzten Schlüssel beibehält) ohne Umschreiben von Teilen des Codes des Containers nicht möglich zu sein scheint.

Verwendung eines Encoder-/Decoder-Paares

Die Verwendung eines Funktionspaares ist nicht notwendig, da der ursprüngliche Schlüssel vom Container beibehalten wird. Darüber hinaus würde ein Encoder-/Decoder-Paar erfordern, dass die Transformation bijektiv ist, was wichtige Anwendungsfälle wie fall-unempfindliches Abgleichen verhindert.

Bereitstellen einer Transformationsfunktion für Werte

Dictionary-Werte werden nicht für die Suche verwendet, ihre Semantik ist für die Funktionsweise des Containers völlig irrelevant. Daher hat es keinen Sinn, sowohl einen "ursprünglichen" als auch einen "transformierten" Wert zu haben: der transformierte Wert würde für nichts verwendet werden.

Bereitstellen eines spezialisierten Containers, nicht generisch

Es wurde gefragt, warum wir die generische TransformDict-Konstruktion und nicht eine spezialisierte fall-unempfindliche Dictionary-Variante bereitstellen würden. Die Antwort ist, dass es (code- und leistungsmäßig) fast genauso günstig ist, die generische Konstruktion bereitzustellen, und sie kann mehr Anwendungsfälle abdecken.

Selbst fall-unempfindliche Dictionaries können tatsächlich unterschiedliche Transformationsfunktionen hervorrufen: str.lower, str.casefold oder in einigen Fällen bytes.lower, wenn mit Text gearbeitet wird, der in einer ASCII-kompatiblen Kodierung codiert ist.

Andere Konstruktormuster

Zwei weitere Konstruktormuster wurden von Serhiy Storchaka vorgeschlagen

  • Ein Typ-Factory-Schema
    d = TransformDict(str.casefold)(Foo=1)
    
  • Ein Unterklassen-Schema
    class CaseInsensitiveDict(TransformDict):
        __transform__ = str.casefold
    
    d = CaseInsensitiveDict(Foo=1)
    

Obwohl beide Ansätze verteidigt werden können, folgen sie nicht den etablierten Praktiken in der Standardbibliothek und wurden daher abgelehnt.

Implementierung

Ein Patch für das collections-Modul wird im Bug-Tracker unter http://bugs.python.org/issue18986 verfolgt.

Bestehende Arbeiten

Fall-unempfindliche Dictionaries sind eine beliebte Anfrage

Identitäts-Dictionaries wurden ebenfalls angefordert

Mehrere Module in der Standardbibliothek verwenden Identitäts-Lookups für die Memoisation von Objekten, zum Beispiel pickle, json, copy, cProfile, doctest und _threading_local.

Andere Sprachen

C# / .Net

.Net hat eine generische Dictionary-Klasse, bei der Sie einen benutzerdefinierten IEqualityComparer angeben können: http://msdn.microsoft.com/en-us/library/xfhwa508.aspx

Die Verwendung ist der empfohlene Weg, um fall-unempfindliche Dictionaries zu schreiben: http://stackoverflow.com/questions/13230414/case-insensitive-access-for-generic-dictionary

Java

Java hat eine spezialisierte CaseInsensitiveMap: http://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/CaseInsensitiveMap.html

Es hat auch eine separate IdentityHashMap: http://docs.oracle.com/javase/6/docs/api/java/util/IdentityHashMap.html

C++

Die C++ Standard Template Library verfügt über eine unordered_map mit anpassbaren Hash- und Gleichheitsfunktionen: http://www.cplusplus.com/reference/unordered_map/unordered_map/


Quelle: https://github.com/python/peps/blob/main/peps/pep-0455.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT