Wie kann ich eine Unterklasse von Diktaten so "perfekt" wie möglich machen? Das Endziel ist ein einfaches Diktat, bei dem die Tasten in Kleinbuchstaben geschrieben sind.
Es scheint, dass es einige winzige Primitive geben sollte, die ich überschreiben kann, damit dies funktioniert, aber nach all meinen Forschungen und Versuchen scheint dies nicht der Fall zu sein:
Wenn ich / überschreibe
__getitem__
__setitem__
, dannget
/set
funktioniert es nicht. Wie kann ich sie zum Laufen bringen? Sicherlich muss ich sie nicht einzeln implementieren?Verhindere ich, dass das Beizen funktioniert, und muss ich
__setstate__
usw. implementieren ?Sollte ich nur Mutablemapping verwenden (anscheinend sollte man es nicht verwenden
UserDict
oderDictMixin
)? Wenn das so ist, wie? Die Dokumente sind nicht gerade aufschlussreich.
Hier ist mein erster Versuch, get()
funktioniert nicht und ohne Zweifel gibt es viele andere kleinere Probleme:
class arbitrary_dict(dict):
"""A dictionary that applies an arbitrary key-altering function
before accessing the keys."""
def __keytransform__(self, key):
return key
# Overridden methods. List from
# /programming/2390827/how-to-properly-subclass-dict
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
# Note: I'm using dict directly, since super(dict, self) doesn't work.
# I'm not sure why, perhaps dict is not a new-style class.
def __getitem__(self, key):
return dict.__getitem__(self, self.__keytransform__(key))
def __setitem__(self, key, value):
return dict.__setitem__(self, self.__keytransform__(key), value)
def __delitem__(self, key):
return dict.__delitem__(self, self.__keytransform__(key))
def __contains__(self, key):
return dict.__contains__(self, self.__keytransform__(key))
class lcdict(arbitrary_dict):
def __keytransform__(self, key):
return str(key).lower()
quelle
Antworten:
Sie können ein Objekt, das sich
dict
mit ABCs (Abstract Base Classes) recht einfach verhält, aus demcollections.abc
Modul schreiben . Es zeigt Ihnen sogar an, ob Sie eine Methode verpasst haben. Im Folgenden finden Sie die minimale Version, mit der das ABC geschlossen wird.Sie erhalten einige kostenlose Methoden aus dem ABC:
Ich würde nicht
dict
direkt (oder andere eingebaute) Unterklassen . Es macht oft keinen Sinn, weil Sie eigentlich die Schnittstelle von a implementierendict
möchten . Und genau dafür sind ABCs da.quelle
__keytransform__()
da dies gegen den PEP 8-Styleguide verstößt, in dem am Ende des Abschnitts Beschreibende: Benennen von Stilen empfohlen wird, solche Namen niemals zu erfinden, sondern nur wie dokumentiert zu verwenden .if isinstance(t, collections.MutableMapping): print t, "can be used like a dict"
. Überprüfen Sie nicht den Typ eines Objekts, sondern die Schnittstelle.Die akzeptierte Antwort wäre mein erster Ansatz, aber da es einige Probleme gibt und niemand die Alternative angesprochen hat, die tatsächlich eine Unterklasse ist
dict
, werde ich das hier tun.Was ist falsch an der akzeptierten Antwort?
Dies scheint mir eine ziemlich einfache Bitte zu sein:
Die akzeptierte Antwort ist eigentlich keine Unterklasse
dict
, und ein Test dafür schlägt fehl:Im Idealfall testet jeder Code zur Typprüfung die von uns erwartete Schnittstelle oder eine abstrakte Basisklasse. Wenn unsere Datenobjekte jedoch an Funktionen übergeben werden, auf die getestet wird
dict
- und wir diese Funktionen nicht "reparieren" können, diesen Code wird versagen.Andere Probleme, die man machen könnte:
fromkeys
.Die akzeptierte Antwort ist ebenfalls redundant
__dict__
und nimmt daher mehr Speicherplatz ein:Eigentlich Unterklasse
dict
Wir können die Diktiermethoden durch Vererbung wiederverwenden. Wir müssen lediglich eine Schnittstellenebene erstellen, die sicherstellt, dass Schlüssel in Kleinbuchstaben an das Diktat übergeben werden, wenn es sich um Zeichenfolgen handelt.
Die individuelle Implementierung ist der Nachteil dieses Ansatzes und der Vorteil der Verwendung
MutableMapping
(siehe die akzeptierte Antwort), aber es ist wirklich nicht viel mehr Arbeit.Lassen Sie uns zunächst den Unterschied zwischen Python 2 und 3 herausrechnen, ein Singleton (
_RaiseKeyError
) erstellen , um sicherzustellen, dass wir wissen, ob wir tatsächlich ein Argument erhaltendict.pop
, und eine Funktion erstellen, um sicherzustellen, dass unsere Zeichenfolgenschlüssel in Kleinbuchstaben geschrieben sind:Jetzt implementieren wir - ich verwende
super
mit den vollständigen Argumenten, damit dieser Code für Python 2 und 3 funktioniert:Wir verwenden einen fast Kesselblech Ansatz für jedes Verfahren oder spezielle Methode , die Referenzen eines Schlüssel, aber ansonsten durch Vererbung erhalten wir Methoden:
len
,clear
,items
,keys
,popitem
, undvalues
kostenlos. Dies erforderte einige sorgfältige Überlegungen, um richtig zu sein, aber es ist trivial zu sehen, dass dies funktioniert.(Beachten Sie, dass dies
haskey
in Python 2 veraltet und in Python 3 entfernt wurde.)Hier ist eine Verwendung:
Beizen
Und die dikt Unterklasse Gurken ganz gut:
__repr__
Wir haben
update
und definiert__init__
, aber Sie haben__repr__
standardmäßig eine schöne :Es ist jedoch gut, eine zu schreiben
__repr__
, um die Debugbarkeit Ihres Codes zu verbessern. Der ideale Test isteval(repr(obj)) == obj
. Wenn es für Ihren Code einfach ist, empfehle ich es dringend:Sie sehen, es ist genau das, was wir brauchen, um ein äquivalentes Objekt neu zu erstellen - dies kann in unseren Protokollen oder in Rückverfolgungen angezeigt werden:
Fazit
Ja, dies sind noch ein paar Codezeilen, aber sie sollen umfassend sein. Meine erste Neigung wäre, die akzeptierte Antwort zu verwenden, und wenn es Probleme damit gäbe, würde ich mir meine Antwort ansehen - da sie etwas komplizierter ist und es kein ABC gibt, das mir hilft, meine Benutzeroberfläche richtig zu machen.
Eine vorzeitige Optimierung führt zu einer höheren Komplexität bei der Suche nach Leistung.
MutableMapping
ist einfacher - so bekommt es einen sofortigen Vorteil, alles andere ist gleich. Um jedoch alle Unterschiede aufzuzeigen, vergleichen und kontrastieren wir sie.Ich sollte hinzufügen, dass es einen Druck gab, ein ähnliches Wörterbuch in das
collections
Modul aufzunehmen, aber es wurde abgelehnt . Sie sollten dies wahrscheinlich stattdessen einfach tun:Es sollte viel einfacher zu debuggen sein.
Vergleichen und gegenüberstellen
Es gibt 6 Schnittstellenfunktionen, die mit der
MutableMapping
(was fehltfromkeys
) und 11 mit derdict
Unterklasse implementiert sind . Ich weiß nicht implementieren müssen__iter__
oder__len__
, sondern ich habe zu implementierenget
,setdefault
,pop
,update
,copy
,__contains__
, undfromkeys
- aber diese sind ziemlich trivial, da ich die Vererbung für die meisten dieser Implementierungen verwenden können.Das
MutableMapping
implementiert einige Dinge in Python,dict
die in C implementiert werden - daher würde ich erwarten, dass einedict
Unterklasse in einigen Fällen leistungsfähiger ist.Wir erhalten
__eq__
in beiden Ansätzen ein freies - beide gehen nur dann von Gleichheit aus, wenn ein anderes Diktat nur in Kleinbuchstaben geschrieben ist -, aber ich denke, diedict
Unterklasse wird sich schneller vergleichen lassen.Zusammenfassung:
MutableMapping
sind einfacher mit weniger Möglichkeiten für Fehler, aber langsamer, benötigen mehr Speicher (siehe redundantes Diktat) und schlagen fehlisinstance(x, dict)
dict
sind schneller, verbrauchen weniger Speicher und bestehenisinstance(x, dict)
, sind jedoch komplexer zu implementieren.Welches ist perfekter? Das hängt von Ihrer Definition von perfekt ab.
quelle
__slots__
oder es möglicherweise__dict__
als Store wiederzuverwenden. Dabei wird jedoch die Semantik gemischt, ein weiterer potenzieller Kritikpunkt.ensure_lower
beim ersten Argument verwendet (was immer der Schlüssel ist)? Dann wäre es die gleiche Anzahl von Überschreibungen, aber alle hätten die Form__getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__)
.copy
- ich denke, das sollte es tun, nein? Ich denke, es sollte auf die Schnittstelle getestet werden - z. B. ist das pandas DataFrame-Objekt keine Mapping-Instanz (bei der letzten Überprüfung), aber es enthält Elemente / Iteritems.Meine Anforderungen waren etwas strenger:
Mein erster Gedanke war, unsere klobige Path-Klasse durch eine Unicode-Unterklasse zu ersetzen, bei der die Groß- und Kleinschreibung nicht berücksichtigt wird - aber:
some_dict[CIstr(path)]
hässlich).Also musste ich endlich dieses fallunabhängige Diktat aufschreiben. Dank des Codes von @AaronHall wurde dies zehnmal einfacher.
Implizit oder explizit ist immer noch ein Problem, aber sobald sich der Staub gelegt hat, ist das Umbenennen von Attributen / Variablen, um mit ci zu beginnen (und ein großer fetter Dokumentkommentar, der erklärt, dass ci für Groß- und Kleinschreibung steht) eine perfekte Lösung - wie es die Leser des Codes tun müssen Seien Sie sich bewusst, dass es sich um zugrunde liegende Datenstrukturen handelt, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird. Dies wird hoffentlich einige schwer zu reproduzierende Fehler beheben, die meiner Meinung nach auf die Groß- und Kleinschreibung zurückzuführen sind.
Kommentare / Korrekturen willkommen :)
quelle
__repr__
sollten die übergeordneten Klassen verwenden__repr__
, um den Test eval (repr (obj)) == obj zu bestehen (ich glaube nicht, dass dies derzeit der Fall ist) und sich nicht darauf verlassen__str__
.total_ordering
Klassendekorator an , der 4 Methoden aus Ihrer Unicode-Unterklasse entfernt. Aber die Diktat-Unterklasse sieht sehr geschickt implementiert aus. : PCIstr.__repr__
können in Ihrem Fall den Repr-Test mit sehr geringem Aufwand bestehen, und das Debuggen sollte dadurch viel angenehmer werden. Ich würde auch ein__repr__
für Ihr Diktat hinzufügen . Ich werde es in meiner Antwort tun, um zu demonstrieren.__slots__
in CIstr hinzugefügt - macht einen Unterschied in der Leistung (CIstr ist nicht dazu gedacht, untergeordnet oder außerhalb von LowerDict verwendet zu werden, sollte eine statisch verschachtelte Endklasse sein).'
"
Alles was Sie tun müssen, ist
ODER
Ein Beispiel für die Verwendung für meinen persönlichen Gebrauch
Hinweis : Nur in Python3 getestet
quelle
Nach dem Versuch , beide der aus oben zwei Vorschläge habe ich auf einer schattige aussehende Mittel Route für Python 2.7 angesiedelt. Vielleicht ist 3 vernünftiger, aber für mich:
was ich wirklich hasse, aber meinen Bedürfnissen zu entsprechen scheint, die sind:
**my_dict
dict
, wird Ihr Code umgangen . Versuch es.isinstance(my_dict, dict)
dict
Wenn Sie sich von anderen unterscheiden müssen, verwende ich persönlich so etwas (obwohl ich bessere Namen empfehlen würde):
Solange Sie sich nur intern erkennen müssen, ist es
__am_i_me
aufgrund von Pythons Namensmunging schwieriger, versehentlich anzurufen (dies wird_MyDict__am_i_me
von allen Anrufen außerhalb dieser Klasse umbenannt). Etwas privater als_method
s, sowohl in der Praxis als auch kulturell.Bisher habe ich keine Beschwerden, abgesehen von der ernsthaft zwielichtig aussehenden
__class__
Außerkraftsetzung. Ich würde mich freuen , von Problemen zu hören, auf die andere stoßen, aber ich verstehe die Konsequenzen nicht ganz. Bisher hatte ich jedoch keinerlei Probleme, und dies ermöglichte es mir, viel Code mittlerer Qualität an vielen Orten zu migrieren, ohne dass Änderungen erforderlich waren.Als Beweis: https://repl.it/repls/TraumaticToughCockatoo
Grundsätzlich gilt: Kopieren Sie die aktuelle Option Nr. 2 , fügen Sie
print 'method_name'
jeder Methode Zeilen hinzu, versuchen Sie dies und beobachten Sie die Ausgabe:Sie werden ein ähnliches Verhalten für andere Szenarien sehen. Angenommen, Ihre Fälschung
dict
ist ein Wrapper um einen anderen Datentyp. Es gibt also keine vernünftige Möglichkeit, die Daten im Backing-Dikt zu speichern.**your_dict
wird leer sein, unabhängig davon, was jede andere Methode tut.Dies funktioniert korrekt für
MutableMapping
, aber sobald Sie davon erbendict
, wird es unkontrollierbar.Bearbeiten: Als Update läuft dies seit fast zwei Jahren ohne ein einziges Problem auf mehreren hunderttausend (eh, vielleicht ein paar Millionen) Zeilen komplizierter Python-Legacy-Python. Also bin ich ziemlich zufrieden damit :)
Edit 2: Anscheinend habe ich dies oder etwas vor langer Zeit falsch kopiert.
@classmethod __class__
funktioniert nicht fürisinstance
Prüfungen -@property __class__
funktioniert: https://repl.it/repls/UnitedScientificSequencequelle
**your_dict
wird leer sein" (wenn du eine Unterklasse von hastdict
)? Ich habe keine Probleme mit dem Auspacken von Diktaten gesehen ...**your_dict
Ihr Code nicht ausgeführt wird kann nichts "Besonderes" ausgeben. Beispielsweise können Sie "Lesevorgänge" nicht zählen, da Ihr Lesezählcode nicht ausgeführt wird. MutableMapping funktioniert zwar (verwenden Sie es, wenn Sie können!), Aber es schlägt fehl,isinstance(..., dict)
sodass ich es nicht verwenden konnte. yay Legacy-Software.**your_dict
, aber ich finde es sehr interessant, dassMutableMapping
das funktioniert.**some_dict
ist ziemlich häufig. Zumindest kommt es sehr oft in Dekorateure, also , wenn Sie irgendwelche , sind Sie sofort in Gefahr scheinbar unmöglich Fehlverhaltens , wenn Sie nicht für sie ausmachen.def __class__()
Trick scheint weder mit Python 2 noch mit Python 3 zu funktionieren, zumindest für den Beispielcode in der Frage Wie registriere ich die Implementierung von abc.MutableMapping als diktierte Unterklasse? (geändert, um ansonsten in den beiden Versionen zu funktionieren). Ich möchteisinstance(SpreadSheet(), dict)
zurückkehrenTrue
.