Wie kann man ein Diktat „perfekt“ überschreiben?

217

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__ , dann get/ setfunktioniert 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 ?

  • Ich brauche repr, updateund__init__ ?

  • Sollte ich nur Mutablemapping verwenden (anscheinend sollte man es nicht verwenden UserDict oder DictMixin)? 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()
Paul Biggar
quelle
Ich denke, __keytransform __ () sollte statisch sein. Netter Ansatz. (vorangestellt @staticmethod)
Aiyion.Prime

Antworten:

229

Sie können ein Objekt, das sich dictmit ABCs (Abstract Base Classes) recht einfach verhält, aus dem collections.abcModul 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.

from collections.abc import MutableMapping


class TransformedDict(MutableMapping):
    """A dictionary that applies an arbitrary key-altering
       function before accessing the keys"""

    def __init__(self, *args, **kwargs):
        self.store = dict()
        self.update(dict(*args, **kwargs))  # use the free update to set keys

    def __getitem__(self, key):
        return self.store[self.__keytransform__(key)]

    def __setitem__(self, key, value):
        self.store[self.__keytransform__(key)] = value

    def __delitem__(self, key):
        del self.store[self.__keytransform__(key)]

    def __iter__(self):
        return iter(self.store)

    def __len__(self):
        return len(self.store)

    def __keytransform__(self, key):
        return key

Sie erhalten einige kostenlose Methoden aus dem ABC:

class MyTransformedDict(TransformedDict):

    def __keytransform__(self, key):
        return key.lower()


s = MyTransformedDict([('Test', 'test')])

assert s.get('TEST') is s['test']   # free get
assert 'TeSt' in s                  # free __contains__
                                    # free setdefault, __eq__, and so on

import pickle
# works too since we just use a normal dict
assert pickle.loads(pickle.dumps(s)) == s

Ich würde nicht dictdirekt (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.

Jochen Ritzel
quelle
46
Ich würde eine Umbenennung vorschlagen, __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 .
Martineau
1
Frage: Wird die Implementierung dieser Schnittstelle mit einem benutzerdefinierten Typ im Allgemeinen nicht zu langsameren diktähnlichen Operationen führen, die den integrierten Typ verwenden?
Twneale
2
Gibt es eine Möglichkeit, dies zu tun, damit isinstance (_, dict) == True ist? Oder verwenden Sie einfach Mutable Mapping, um dann eine Unterklasse zu erstellen?
Andy Hayden
5
@ AndyHayden: Du solltest schreiben if isinstance(t, collections.MutableMapping): print t, "can be used like a dict". Überprüfen Sie nicht den Typ eines Objekts, sondern die Schnittstelle.
Jochen Ritzel
2
@NeilG Dies beinhaltet leider den JSONEncoder in der Python-Standardbibliothek - github.com/python-git/python/blob/…
Andy Smith
97

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.

  • Wenn ich __getitem__/ überschreibe __setitem__, funktioniert get / set nicht. Wie bringe ich sie zum Arbeiten? Sicherlich muss ich sie nicht einzeln implementieren?

  • Verhindere ich, dass das Beizen funktioniert, und muss ich __setstate__usw. implementieren ?

  • Benötige ich Repr, Update und __init__?

  • Soll ich nur verwenden mutablemapping(es scheint, man sollte nicht verwenden UserDict oder DictMixin)? Wenn das so ist, wie? Die Dokumente sind nicht gerade aufschlussreich.

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:

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.

Die akzeptierte Antwort ist eigentlich keine Unterklasse dict, und ein Test dafür schlägt fehl:

>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False

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:

  • In der akzeptierten Antwort fehlt auch die Klassenmethode : fromkeys.
  • Die akzeptierte Antwort ist ebenfalls redundant __dict__und nimmt daher mehr Speicherplatz ein:

    >>> s.foo = 'bar'
    >>> s.__dict__
    {'foo': 'bar', 'store': {'test': 'test'}}

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.

Wenn ich __getitem__/ überschreibe __setitem__, funktioniert get / set nicht. Wie bringe ich sie zum Arbeiten? Sicherlich muss ich sie nicht einzeln implementieren?

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 erhalten dict.pop, und eine Funktion erstellen, um sicherzustellen, dass unsere Zeichenfolgenschlüssel in Kleinbuchstaben geschrieben sind:

from itertools import chain
try:              # Python 2
    str_base = basestring
    items = 'iteritems'
except NameError: # Python 3
    str_base = str, bytes, bytearray
    items = 'items'

_RaiseKeyError = object() # singleton for no-default behavior

def ensure_lower(maybe_str):
    """dict keys can be any hashable object - only call lower if str"""
    return maybe_str.lower() if isinstance(maybe_str, str_base) else maybe_str

Jetzt implementieren wir - ich verwende supermit den vollständigen Argumenten, damit dieser Code für Python 2 und 3 funktioniert:

class LowerDict(dict):  # dicts take a mapping or iterable as their optional first argument
    __slots__ = () # no __dict__ - that would be redundant
    @staticmethod # because this doesn't make sense as a global function.
    def _process_args(mapping=(), **kwargs):
        if hasattr(mapping, items):
            mapping = getattr(mapping, items)()
        return ((ensure_lower(k), v) for k, v in chain(mapping, getattr(kwargs, items)()))
    def __init__(self, mapping=(), **kwargs):
        super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
    def __getitem__(self, k):
        return super(LowerDict, self).__getitem__(ensure_lower(k))
    def __setitem__(self, k, v):
        return super(LowerDict, self).__setitem__(ensure_lower(k), v)
    def __delitem__(self, k):
        return super(LowerDict, self).__delitem__(ensure_lower(k))
    def get(self, k, default=None):
        return super(LowerDict, self).get(ensure_lower(k), default)
    def setdefault(self, k, default=None):
        return super(LowerDict, self).setdefault(ensure_lower(k), default)
    def pop(self, k, v=_RaiseKeyError):
        if v is _RaiseKeyError:
            return super(LowerDict, self).pop(ensure_lower(k))
        return super(LowerDict, self).pop(ensure_lower(k), v)
    def update(self, mapping=(), **kwargs):
        super(LowerDict, self).update(self._process_args(mapping, **kwargs))
    def __contains__(self, k):
        return super(LowerDict, self).__contains__(ensure_lower(k))
    def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
        return type(self)(self)
    @classmethod
    def fromkeys(cls, keys, v=None):
        return super(LowerDict, cls).fromkeys((ensure_lower(k) for k in keys), v)
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__, super(LowerDict, self).__repr__())

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, und valueskostenlos. Dies erforderte einige sorgfältige Überlegungen, um richtig zu sein, aber es ist trivial zu sehen, dass dies funktioniert.

(Beachten Sie, dass dies haskeyin Python 2 veraltet und in Python 3 entfernt wurde.)

Hier ist eine Verwendung:

>>> ld = LowerDict(dict(foo='bar'))
>>> ld['FOO']
'bar'
>>> ld['foo']
'bar'
>>> ld.pop('FoO')
'bar'
>>> ld.setdefault('Foo')
>>> ld
{'foo': None}
>>> ld.get('Bar')
>>> ld.setdefault('Bar')
>>> ld
{'bar': None, 'foo': None}
>>> ld.popitem()
('bar', None)

Verhindere ich, dass das Beizen funktioniert, und muss ich __setstate__usw. implementieren ?

Beizen

Und die dikt Unterklasse Gurken ganz gut:

>>> import pickle
>>> pickle.dumps(ld)
b'\x80\x03c__main__\nLowerDict\nq\x00)\x81q\x01X\x03\x00\x00\x00fooq\x02Ns.'
>>> pickle.loads(pickle.dumps(ld))
{'foo': None}
>>> type(pickle.loads(pickle.dumps(ld)))
<class '__main__.LowerDict'>

__repr__

Benötige ich Repr, Update und __init__?

Wir haben updateund definiert __init__, aber Sie haben __repr__standardmäßig eine schöne :

>>> ld # without __repr__ defined for the class, we get this
{'foo': None}

Es ist jedoch gut, eine zu schreiben __repr__, um die Debugbarkeit Ihres Codes zu verbessern. Der ideale Test ist eval(repr(obj)) == obj. Wenn es für Ihren Code einfach ist, empfehle ich es dringend:

>>> ld = LowerDict({})
>>> eval(repr(ld)) == ld
True
>>> ld = LowerDict(dict(a=1, b=2, c=3))
>>> eval(repr(ld)) == ld
True

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:

>>> ld
LowerDict({'a': 1, 'c': 3, 'b': 2})

Fazit

Soll ich nur verwenden mutablemapping(es scheint, man sollte nicht verwenden UserDict oder DictMixin)? Wenn das so ist, wie? Die Dokumente sind nicht gerade aufschlussreich.

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. MutableMappingist 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 collectionsModul aufzunehmen, aber es wurde abgelehnt . Sie sollten dies wahrscheinlich stattdessen einfach tun:

my_dict[transform(key)]

Es sollte viel einfacher zu debuggen sein.

Vergleichen und gegenüberstellen

Es gibt 6 Schnittstellenfunktionen, die mit der MutableMapping(was fehlt fromkeys) und 11 mit der dictUnterklasse implementiert sind . Ich weiß nicht implementieren müssen __iter__oder __len__, sondern ich habe zu implementieren get, setdefault, pop, update, copy, __contains__, und fromkeys- aber diese sind ziemlich trivial, da ich die Vererbung für die meisten dieser Implementierungen verwenden können.

Das MutableMappingimplementiert einige Dinge in Python, dictdie in C implementiert werden - daher würde ich erwarten, dass eine dictUnterklasse 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, die dictUnterklasse wird sich schneller vergleichen lassen.

Zusammenfassung:

  • Unterklassen MutableMappingsind einfacher mit weniger Möglichkeiten für Fehler, aber langsamer, benötigen mehr Speicher (siehe redundantes Diktat) und schlagen fehlisinstance(x, dict)
  • Unterklassen dictsind schneller, verbrauchen weniger Speicher und bestehen isinstance(x, dict), sind jedoch komplexer zu implementieren.

Welches ist perfekter? Das hängt von Ihrer Definition von perfekt ab.

Aaron Hall
quelle
Wie würde die akzeptierte Antwort das redundante Diktat entfernen?
Seanny123
1
Zwei Möglichkeiten, die sofort in den Sinn kommen, bestehen darin, entweder das Store-Attribut in zu deklarieren __slots__oder es möglicherweise __dict__als Store wiederzuverwenden. Dabei wird jedoch die Semantik gemischt, ein weiterer potenzieller Kritikpunkt.
Aaron Hall
1
Wäre es nicht einfacher gewesen, einen Dekorateur zu schreiben, der eine Methode verwendet und Ihre ensure_lowerbeim 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__).
Graipherie
1
Vielen Dank dafür - Sie erhalten Warnungen für Pop und Fromkeys, dass sie nicht mit der Signatur der Basisklassenmethode übereinstimmen.
Mr_and_Mrs_D
1
@Mr_and_Mrs_D Ich habe eine Implementierung von hinzugefügt 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.
Aaron Hall
4

Meine Anforderungen waren etwas strenger:

  • Ich musste die Fallinformationen beibehalten (die Zeichenfolgen sind Pfade zu Dateien, die dem Benutzer angezeigt werden, aber es handelt sich um eine Windows-App, sodass intern bei allen Vorgängen die Groß- und Kleinschreibung nicht berücksichtigt werden muss).
  • Ich brauchte Schlüssel, die so klein wie möglich waren (es machte einen Unterschied in der Speicherleistung, da 110 MB von 370 abgeschnitten wurden). Dies bedeutete, dass das Zwischenspeichern von Kleinbuchstaben nicht eine Option ist.
  • Ich musste die Datenstrukturen so schnell wie möglich erstellen (machte diesmal wieder einen Unterschied in der Leistung und Geschwindigkeit). Ich musste mit einem eingebauten gehen

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:

  • Es hat sich als schwierig erwiesen, das richtig zu machen - siehe: Eine String-Klasse ohne Berücksichtigung der Groß- und Kleinschreibung in Python
  • Es stellt sich heraus, dass die explizite Behandlung von Diktierschlüsseln den Code ausführlich und unübersichtlich macht - und fehleranfällig (Strukturen werden hin und her übergeben, und es ist nicht klar, ob sie CIStr-Instanzen als Schlüssel / Elemente haben, leicht zu vergessen und some_dict[CIstr(path)]hässlich).

Also musste ich endlich dieses fallunabhängige Diktat aufschreiben. Dank des Codes von @AaronHall wurde dies zehnmal einfacher.

class CIstr(unicode):
    """See https://stackoverflow.com/a/43122305/281545, especially for inlines"""
    __slots__ = () # does make a difference in memory performance

    #--Hash/Compare
    def __hash__(self):
        return hash(self.lower())
    def __eq__(self, other):
        if isinstance(other, CIstr):
            return self.lower() == other.lower()
        return NotImplemented
    def __ne__(self, other):
        if isinstance(other, CIstr):
            return self.lower() != other.lower()
        return NotImplemented
    def __lt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() < other.lower()
        return NotImplemented
    def __ge__(self, other):
        if isinstance(other, CIstr):
            return self.lower() >= other.lower()
        return NotImplemented
    def __gt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() > other.lower()
        return NotImplemented
    def __le__(self, other):
        if isinstance(other, CIstr):
            return self.lower() <= other.lower()
        return NotImplemented
    #--repr
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(CIstr, self).__repr__())

def _ci_str(maybe_str):
    """dict keys can be any hashable object - only call CIstr if str"""
    return CIstr(maybe_str) if isinstance(maybe_str, basestring) else maybe_str

class LowerDict(dict):
    """Dictionary that transforms its keys to CIstr instances.
    Adapted from: https://stackoverflow.com/a/39375731/281545
    """
    __slots__ = () # no __dict__ - that would be redundant

    @staticmethod # because this doesn't make sense as a global function.
    def _process_args(mapping=(), **kwargs):
        if hasattr(mapping, 'iteritems'):
            mapping = getattr(mapping, 'iteritems')()
        return ((_ci_str(k), v) for k, v in
                chain(mapping, getattr(kwargs, 'iteritems')()))
    def __init__(self, mapping=(), **kwargs):
        # dicts take a mapping or iterable as their optional first argument
        super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
    def __getitem__(self, k):
        return super(LowerDict, self).__getitem__(_ci_str(k))
    def __setitem__(self, k, v):
        return super(LowerDict, self).__setitem__(_ci_str(k), v)
    def __delitem__(self, k):
        return super(LowerDict, self).__delitem__(_ci_str(k))
    def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
        return type(self)(self)
    def get(self, k, default=None):
        return super(LowerDict, self).get(_ci_str(k), default)
    def setdefault(self, k, default=None):
        return super(LowerDict, self).setdefault(_ci_str(k), default)
    __no_default = object()
    def pop(self, k, v=__no_default):
        if v is LowerDict.__no_default:
            # super will raise KeyError if no default and key does not exist
            return super(LowerDict, self).pop(_ci_str(k))
        return super(LowerDict, self).pop(_ci_str(k), v)
    def update(self, mapping=(), **kwargs):
        super(LowerDict, self).update(self._process_args(mapping, **kwargs))
    def __contains__(self, k):
        return super(LowerDict, self).__contains__(_ci_str(k))
    @classmethod
    def fromkeys(cls, keys, v=None):
        return super(LowerDict, cls).fromkeys((_ci_str(k) for k in keys), v)
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(LowerDict, self).__repr__())

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 :)

Mr_and_Mrs_D
quelle
CIstrs __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__.
Aaron Hall
Schauen Sie sich auch den total_orderingKlassendekorator an , der 4 Methoden aus Ihrer Unicode-Unterklasse entfernt. Aber die Diktat-Unterklasse sieht sehr geschickt implementiert aus. : P
Aaron Hall
Vielen Dank an @AaronHall - Sie haben Folgendes implementiert: P Re: Gesamtbestellung - Ich habe absichtlich die von Raymond Hettinger empfohlenen Methoden hier geschrieben: stackoverflow.com/a/43122305/281545 . Re: repr: Ich erinnere mich, dass ich einen Kommentar (von einem Kernentwickler IIRC) so gut gelesen habe, dass es sich nicht wirklich lohnt, zu versuchen, repr zu machen, um diesen Test zu bestehen (es ist ein Ärger) - konzentrieren Sie sich besser darauf, dass er so informativ wie möglich ist ( aber nicht mehr)
Mr_and_Mrs_D
Ich erlaube Ihnen Ihre redundanten Vergleichsmethoden (Sie sollten dies in Ihrer Antwort vermerken), aber die CIstr.__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.
Aaron Hall
@AaronHall: Ich habe __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). '"
Ich bin
4

Alles was Sie tun müssen, ist

class BatchCollection(dict):
    def __init__(self, *args, **kwargs):
        dict.__init__(*args, **kwargs)

ODER

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

Ein Beispiel für die Verwendung für meinen persönlichen Gebrauch

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        dict.__init__(*args, **kwargs)

    def __setitem__(self, key, item):
        if (isinstance(key, tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a tuple (database_name, table_name) "
                "and value should be iterable")

Hinweis : Nur in Python3 getestet

ravi404
quelle
3

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:

class MyDict(MutableMapping):
   # ... the few __methods__ that mutablemapping requires
   # and then this monstrosity
   @property
   def __class__(self):
       return dict

was ich wirklich hasse, aber meinen Bedürfnissen zu entsprechen scheint, die sind:

  • kann überschreiben **my_dict
    • Wenn Sie von erben dict, wird Ihr Code umgangen . Versuch es.
    • Dies macht # 2 für mich zu jeder Zeit inakzeptabel , da dies im Python-Code durchaus üblich ist
  • Maskeraden als isinstance(my_dict, dict)
    • schließt MutableMapping alleine aus, daher reicht # 1 nicht aus
    • Ich empfehle # 1 von Herzen, wenn Sie dies nicht brauchen, es ist einfach und vorhersehbar
  • voll kontrollierbares Verhalten
    • also kann ich nicht erben von dict

Wenn Sie sich von anderen unterscheiden müssen, verwende ich persönlich so etwas (obwohl ich bessere Namen empfehlen würde):

def __am_i_me(self):
  return True

@classmethod
def __is_it_me(cls, other):
  try:
    return other.__am_i_me()
  except Exception:
    return False

Solange Sie sich nur intern erkennen müssen, ist es __am_i_meaufgrund von Pythons Namensmunging schwieriger, versehentlich anzurufen (dies wird _MyDict__am_i_mevon allen Anrufen außerhalb dieser Klasse umbenannt). Etwas privater als _methods, 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:

d = LowerDict()  # prints "init", or whatever your print statement said
print '------'
splatted = dict(**d)  # note that there are no prints here

Sie werden ein ähnliches Verhalten für andere Szenarien sehen. Angenommen, Ihre Fälschung dictist ein Wrapper um einen anderen Datentyp. Es gibt also keine vernünftige Möglichkeit, die Daten im Backing-Dikt zu speichern. **your_dictwird leer sein, unabhängig davon, was jede andere Methode tut.

Dies funktioniert korrekt für MutableMapping, aber sobald Sie davon erben dict, 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ür isinstancePrüfungen - @property __class__funktioniert: https://repl.it/repls/UnitedScientificSequence

Groxx
quelle
Was genau meinst du mit " **your_dictwird leer sein" (wenn du eine Unterklasse von hast dict)? Ich habe keine Probleme mit dem Auspacken von Diktaten gesehen ...
Matt P
Wenn Sie tatsächlich Daten in das übergeordnete Diktat einfügen (wie dies bei LowerDict der Fall ist), funktioniert dies - Sie erhalten diese diktgespeicherten Daten. Wenn Sie dies nicht tun (sagen Sie, Sie wollten Daten im laufenden Betrieb generieren, z. B. {access_count: "Stapelverfolgung des Zugriffs"}, die bei jedem Lesen ausgefüllt werden), werden Sie feststellen, dass **your_dictIhr 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.
Groxx
Ok, ich verstehe, was du jetzt meinst. Ich nehme an, ich habe keine Codeausführung mit erwartet **your_dict, aber ich finde es sehr interessant, dass MutableMappingdas funktioniert.
Matt P
Ja. Es ist für eine Reihe von Dingen notwendig (z. B. habe ich RPC-Anrufe in ein früheres lokales Diktat umgewandelt und musste es auf Anfrage für Reasons ™ tun), und es scheint, dass nur sehr wenige Menschen davon Kenntnis haben, auch wenn **some_dictist 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.
Groxx
Vielleicht fehlt mir etwas, aber der 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öchte isinstance(SpreadSheet(), dict)zurückkehren True.
Martineau