Come sovrascrivere "perfettamente" un dict?


223

Come posso rendere il più "perfetto" possibile una sottoclasse di dict ? L'obiettivo finale è avere un semplice dict in cui i tasti sono minuscoli.

Sembrerebbe che ci dovrebbe essere un piccolo insieme di primitive che posso ignorare per farlo funzionare, ma secondo tutte le mie ricerche e tentativi sembra che non sia così:

  • Se sovrascrivo __getitem__/__setitem__ , allora get/ setnon funziona. Come posso farli funzionare? Sicuramente non ho bisogno di implementarli individualmente?

  • Sto impedendo il funzionamento del decapaggio e devo implementare __setstate__ecc.?

  • Ho bisogno repr, updatee__init__ ?

  • Devo solo usare il mutablemapping (sembra che non si dovrebbe usare UserDict o DictMixin)? Se é cosi, come? I documenti non sono esattamente illuminanti.

Ecco il mio primo tentativo, get()non funziona e senza dubbio ci sono molti altri problemi minori:

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

Penso che __keytransform __ () dovrebbe essere statico. Bel approccio però. (anteponendo @staticmethod)
Aiyion.Prime

Risposte:


236

Puoi scrivere un oggetto che si comporta come un dictabbastanza facilmente con ABC s (Abstract Base Classes) dal collections.abcmodulo. Ti dice anche se ti sei perso un metodo, quindi di seguito è la versione minima che spegne l'ABC.

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

Ottieni alcuni metodi gratuiti dall'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

Non sottoclasserei dict(o altri incorporati) direttamente. Spesso non ha senso, perché ciò che si desidera effettivamente fare è implementare l'interfaccia di un filedict . Ed è esattamente a questo che servono gli ABC.


1
Domanda però: l'implementazione di questa interfaccia con un tipo definito dall'utente generalmente non si tradurrà in operazioni simili a dict più lente rispetto all'utilizzo del tipo integrato?
twneale

2
C'è un modo per farlo in modo che isinstance (_, dict) == True? O usi semplicemente Mutable Mapping per costruire poi una sottoclasse?
Andy Hayden

1
@ NeilG Allora qual è il vantaggio di questo approccio, a parte 20 linee extra, in più MyClass = type('MyClass', (dict,), {})?
twneale

5
@ AnddyHayden: dovresti scrivere if isinstance(t, collections.MutableMapping): print t, "can be used like a dict". Non controllare il tipo di oggetto, controlla l'interfaccia.
Jochen Ritzel

2
@NeilG Questo purtroppo include JSONEncoder nella libreria standard python - github.com/python-git/python/blob/…
Andy Smith

105

Come posso rendere il più "perfetto" possibile una sottoclasse di dict?

L'obiettivo finale è avere un semplice dict in cui i tasti sono minuscoli.

  • Se sovrascrivo __getitem__/ __setitem__, allora get / set non funziona. Come li faccio funzionare? Sicuramente non ho bisogno di implementarli individualmente?

  • Sto impedendo il funzionamento del decapaggio e devo implementare __setstate__ecc.?

  • Ho bisogno di ristampa, aggiornamento e __init__?

  • Dovrei solo usare mutablemapping(sembra che uno non dovrebbe usare UserDict o DictMixin)? Se é cosi, come? I documenti non sono esattamente illuminanti.

La risposta accettata sarebbe il mio primo approccio, ma poiché ha alcuni problemi e poiché nessuno ha affrontato l'alternativa, in realtà sottoclasse a dict, lo farò qui.

Cosa c'è di sbagliato nella risposta accettata?

Questa mi sembra una richiesta piuttosto semplice:

Come posso rendere il più "perfetto" possibile una sottoclasse di dict? L'obiettivo finale è avere un semplice dict in cui i tasti sono minuscoli.

La risposta accettata in realtà non è una sottoclasse dicte un test per questo fallisce:

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

Idealmente, qualsiasi codice di controllo del tipo verrebbe testato per l'interfaccia che ci aspettiamo o una classe base astratta, ma se i nostri oggetti dati vengono passati a funzioni che stanno testando dict- e non possiamo "aggiustare" quelle funzioni, questo codice avrà esito negativo.

Altri cavilli che si potrebbero fare:

  • La risposta accettata manca anche il classmethod: fromkeys.
  • La risposta accettata ha anche una ridondanza __dict__, quindi occupa più spazio in memoria:

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

In realtà sottoclasse dict

Possiamo riutilizzare i metodi dict attraverso l'ereditarietà. Tutto quello che dobbiamo fare è creare un livello di interfaccia che assicuri che le chiavi vengano passate nel dict in forma minuscola se sono stringhe.

Se sovrascrivo __getitem__/ __setitem__, allora get / set non funziona. Come li faccio funzionare? Sicuramente non ho bisogno di implementarli individualmente?

Bene, implementarli singolarmente è lo svantaggio di questo approccio e il lato positivo dell'utilizzo MutableMapping(vedi la risposta accettata), ma in realtà non è molto più lavoro.

Innanzitutto, escludiamo la differenza tra Python 2 e 3, creiamo un singleton ( _RaiseKeyError) per assicurarci di sapere se otteniamo effettivamente un argomento dict.pope creiamo una funzione per assicurarci che le nostre chiavi di stringa siano minuscole:

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

Ora implementiamo: sto usando supercon gli argomenti completi in modo che questo codice funzioni per Python 2 e 3:

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__())

Usiamo un approccio quasi caldaia-piastra per qualsiasi metodo o metodo speciale che i riferimenti di una chiave, ma per il resto, per eredità, otteniamo metodi: len, clear, items, keys, popitem, e valuesgratuitamente. Anche se questo ha richiesto un'attenta riflessione per avere ragione, è banale vedere che funziona.

(Nota che haskeyera deprecato in Python 2, rimosso in Python 3.)

Ecco alcuni utilizzi:

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

Sto impedendo il funzionamento del decapaggio e devo implementare __setstate__ecc.?

decapaggio

E la sottoclasse dict va benissimo:

>>> 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__

Ho bisogno di ristampa, aggiornamento e __init__?

Abbiamo definito updatee __init__, ma hai una bella __repr__di default:

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

Tuttavia, è bene scrivere a __repr__per migliorare il debug del codice. Il test ideale è eval(repr(obj)) == obj. Se è facile da fare per il tuo codice, lo consiglio vivamente:

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

Vedi, è esattamente ciò di cui abbiamo bisogno per ricreare un oggetto equivalente - questo è qualcosa che potrebbe apparire nei nostri log o nei backtrace:

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

Conclusione

Dovrei solo usare mutablemapping(sembra che uno non dovrebbe usare UserDict o DictMixin)? Se é cosi, come? I documenti non sono esattamente illuminanti.

Sì, queste sono alcune righe di codice in più, ma intendono essere complete. La mia prima inclinazione sarebbe quella di utilizzare la risposta accettata, e se ci fossero problemi con essa, allora guarderei la mia risposta - poiché è un po 'più complicata e non c'è ABC che mi aiuti a ottenere la mia interfaccia corretta.

L'ottimizzazione prematura sta andando per una maggiore complessità alla ricerca delle prestazioni. MutableMappingè più semplice, quindi ottiene un vantaggio immediato, a parità di tutto il resto. Tuttavia, per mettere in evidenza tutte le differenze, confrontiamo e confrontiamo.

Dovrei aggiungere che c'è stata una spinta per inserire un dizionario simile nel collectionsmodulo, ma è stato rifiutato . Probabilmente dovresti farlo invece:

my_dict[transform(key)]

Dovrebbe essere molto più facilmente eseguibile il debug.

Confrontare e contrapporre

Ci sono 6 funzioni di interfaccia implementate con MutableMapping(che manca fromkeys) e 11 con la dictsottoclasse. Non ho bisogno di implementare __iter__o __len__, ma invece devo implementare get, setdefault, pop, update, copy, __contains__, e fromkeys- ma questi sono abbastanza banale, dato che posso utilizzare l'ereditarietà per la maggior parte di queste implementazioni.

L' MutableMappingimplementazione di alcune cose in Python che dictimplementa in C, quindi mi aspetto che una dictsottoclasse sia più performante in alcuni casi.

Otteniamo una libertà __eq__in entrambi gli approcci - entrambi assumono l'uguaglianza solo se un altro dict è tutto minuscolo - ma ancora una volta, penso che la dictsottoclasse si confronterà più rapidamente.

Sommario:

  • la sottoclasse MutableMappingè più semplice con meno possibilità di bug, ma più lenta, richiede più memoria (vedi dict ridondante) e fallisceisinstance(x, dict)
  • la sottoclasse dictè più veloce, utilizza meno memoria e passa isinstance(x, dict), ma ha una maggiore complessità da implementare.

Quale è più perfetto? Dipende dalla tua definizione di perfetto.


In che modo la risposta accettata rimuoverebbe il dict ridondante?
Seanny123

1
Due modi che vengono immediatamente in mente sono o dichiarare l'attributo store in __slots__o forse riutilizzare __dict__come store, ma questo mescola la semantica, un altro potenziale punto di critica.
Aaron Hall

1
Non sarebbe stato più facile scrivere un decoratore che prende un metodo e usa il tuo ensure_lowersul primo argomento (che è sempre la chiave)? Quindi sarebbe lo stesso numero di sostituzioni, ma sarebbero tutte nella forma __getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__).
Graipher

1
Grazie per questo - ricevere avvisi per pop e fromkeys che non corrispondono alla firma del metodo della classe base.
Mr_and_Mrs_D

1
@Mr_and_Mrs_D Ho aggiunto un'implementazione di copy- Penso che dovrebbe farlo, no? Penso che dovrebbe testare l'interfaccia, ad esempio l'oggetto DataFrame di panda non è un'istanza di mappatura (all'ultimo controllo) ma ha elementi / iteritem.
Aaron Hall

5

I miei requisiti erano un po 'più rigidi:

  • Ho dovuto conservare le informazioni sul caso (le stringhe sono percorsi dei file visualizzati dall'utente, ma è un'app per Windows quindi internamente tutte le operazioni devono essere senza distinzione tra maiuscole e minuscole)
  • Avevo bisogno che le chiavi fossero il più piccole possibile (ha fatto la differenza nelle prestazioni della memoria, tagliata 110 mb su 370). Ciò significa che la memorizzazione nella cache della versione minuscola delle chiavi non è un'opzione.
  • Avevo bisogno che la creazione delle strutture dati fosse il più veloce possibile (ancora una volta ha fatto la differenza in termini di prestazioni, velocità questa volta). Ho dovuto usare un builtin

Il mio pensiero iniziale era di sostituire la nostra goffa classe Path con una sottoclasse Unicode senza distinzione tra maiuscole e minuscole, ma:

  • si è rivelato difficile da capire - vedi: Una classe stringa insensibile al maiuscolo / minuscolo in python
  • si scopre che la gestione esplicita delle chiavi dict rende il codice prolisso e disordinato - e soggetto a errori (le strutture vengono passate qua e là, e non è chiaro se hanno istanze CIStr come chiavi / elementi, facile da dimenticare e inoltre some_dict[CIstr(path)]è brutto)

Quindi ho dovuto finalmente scrivere quel dict insensibile al maiuscolo / minuscolo. Grazie al codice di @AaronHall che è stato reso 10 volte più semplice.

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__())

Implicito vs esplicito è ancora un problema, ma una volta che la polvere si è depositata, rinominare attributi / variabili per iniziare con ci (e un grosso commento di un documento che spiega che ci sta per case insensitive) penso che sia una soluzione perfetta - come devono fare i lettori del codice essere pienamente consapevoli che abbiamo a che fare con strutture di dati sottostanti senza distinzione tra maiuscole e minuscole. Si spera che questo risolva alcuni bug difficili da riprodurre, che sospetto si riducano alla distinzione tra maiuscole e minuscole.

Commenti / correzioni sono benvenuti :)


I CIstr __repr__dovrebbero usare la classe genitore __repr__per superare il test eval (repr (obj)) == obj (non credo che lo faccia adesso) e non fare affidamento su __str__.
Aaron Hall

Controlla anche il total_orderingdecoratore di classi : eliminerà 4 metodi dalla tua sottoclasse unicode. Ma la sottoclasse dict sembra implementata in modo molto intelligente. : P
Aaron Hall

Grazie @AaronHall - sei tu che lo hai implementato: P Re: total ordering - Ho scritto intenzionalmente i metodi inline come consigliato da Raymond Hettinger qui: stackoverflow.com/a/43122305/281545 . Re: repr: Ricordo di aver letto un commento (di alcuni core dev IIRC) che bene, non vale davvero la pena provare a fare una replica per superare quel test (è una seccatura) - meglio concentrarsi sul fatto che sia il più informativo possibile ( ma non di più)
Mr_and_Mrs_D

Ti permetterò i tuoi metodi di confronto ridondanti (dovresti prenderne nota nella risposta), ma CIstr.__repr__, nel tuo caso, può superare il test di ripetizione con pochissimi problemi e dovrebbe rendere il debug molto più piacevole. Aggiungerei anche un __repr__per il tuo dict. Lo farò nella mia risposta per dimostrare.
Aaron Hall

@AaronHall: ho aggiunto __slots__in CIstr - fa la differenza in termini di prestazioni (CIstr non è pensato per essere sottoclasse o effettivamente utilizzato al di fuori di LowerDict, dovrebbe essere una classe finale annidata statica). Ancora non sono sicuro di come risolvere elegantemente il problema della riproduzione (la puntura potrebbe contenere una combinazione di 'e "virgolette)
Mr_and_Mrs_D

5

Tutto quello che dovrai fare è

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

O

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

Un esempio di utilizzo per uso personale

### 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")

Nota : testato solo in python3


3

Dopo aver provato entrambi i primi due suggerimenti, ho optato per una via di mezzo dall'aspetto ombroso per Python 2.7. Forse 3 è più sano, ma per me:

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

che odio davvero, ma sembra soddisfare le mie esigenze, che sono:

  • può sovrascrivere **my_dict
    • se erediti da dict, questo ignora il tuo codice . Provalo.
    • questo rende il # 2 inaccettabile per me in ogni momento , poiché questo è abbastanza comune nel codice Python
  • si traveste da isinstance(my_dict, dict)
    • esclude MutableMapping da solo, quindi # 1 non è sufficiente
    • Consiglio vivamente il n. 1 se non ne hai bisogno, è semplice e prevedibile
  • comportamento completamente controllabile
    • quindi non posso ereditare da dict

Se hai bisogno di distinguerti dagli altri, personalmente uso qualcosa del genere (anche se consiglierei nomi migliori):

def __am_i_me(self):
  return True

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

Finché hai solo bisogno di riconoscerti internamente, in questo modo è più difficile chiamare accidentalmente a __am_i_mecausa del munging dei nomi di python (questo viene rinominato _MyDict__am_i_meda qualsiasi cosa che chiami al di fuori di questa classe). Un po 'più privato di _methods, sia nella pratica che culturalmente.

Finora non ho lamentele, a parte l' __class__override dall'aspetto seriamente ombroso . Sarei entusiasta di sapere di eventuali problemi che altri incontrano con questo, però, non capisco appieno le conseguenze. Ma finora non ho avuto problemi di sorta e questo mi ha permesso di migrare un sacco di codice di qualità mediocre in molte posizioni senza bisogno di modifiche.


Come prova: https://repl.it/repls/TraumaticToughCockatoo

Fondamentalmente: copia l'attuale opzione n. 2 , aggiungi print 'method_name'linee a ogni metodo, quindi prova questo e guarda l'output:

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

Vedrai un comportamento simile per altri scenari. Supponiamo che il tuo fake- dictsia un wrapper attorno a qualche altro tipo di dati, quindi non esiste un modo ragionevole per memorizzare i dati nel dict di supporto; **your_dictsarà vuoto, indipendentemente da ciò che fa ogni altro metodo.

Funziona correttamente per MutableMapping, ma non appena si eredita da dictesso diventa incontrollabile.


Modifica: come aggiornamento, questo è in esecuzione senza un singolo problema da quasi due anni, su diverse centinaia di migliaia (eh, potrebbero essere un paio di milioni) linee di python complicati e legacy. Quindi ne sono abbastanza soddisfatto :)

Modifica 2: a quanto pare ho copiato erroneamente questo o qualcosa molto tempo fa. @classmethod __class__non funziona per i isinstancecontrolli - @property __class__funziona: https://repl.it/repls/UnitedScientificSequence


Cosa intendi esattamente con " **your_dictsarà vuoto" (se fai una sottoclasse da dict)? Non ho riscontrato alcun problema con il disimballaggio dei dict ...
Matt P

Se metti effettivamente i dati nel dict genitore (come fa LowerDict), funziona: otterrai i dati memorizzati nel dict. Se non lo fai (supponi di voler generare dati al volo, come {access_count: "stack trace of access"} che si riempie ogni volta che viene letto), noterai che **your_dictnon esegue il tuo codice, quindi non può produrre nulla di "speciale". Ad esempio, non puoi contare le "letture" perché non esegue il codice di conteggio delle letture. MutableMapping fa il lavoro per questo (usarlo se potete!), Ma non riesce isinstance(..., dict)quindi non ho potuto usarlo. yay software legacy.
Groxx

Ok, capisco cosa intendi ora. Suppongo che non mi aspettassi l'esecuzione di codice con **your_dict, ma trovo molto interessante che MutableMappinglo farà.
Matt P

Sì. È necessario per un certo numero di cose (ad esempio, stavo inserendo chiamate RPC in quella che era una lettura di dettami locali e dovevo farlo su richiesta per Reasons ™), e sembra che pochissime persone ne siano consapevoli, anche se **some_dictè abbastanza comune. Per lo meno accade molto spesso nei decoratori, quindi se ne hai qualcuno , sei immediatamente a rischio di comportamenti scorretti apparentemente impossibili se non ne rendi conto.
Groxx

Forse mi manca qualcosa, ma il def __class__()trucco non sembra funzionare né con Python 2 né con 3, almeno per il codice di esempio nella domanda Come registrare l'implementazione di abc.MutableMapping come una sottoclasse dict? (modificato per funzionare diversamente nelle due versioni). Voglio isinstance(SpreadSheet(), dict)tornare True.
martineau
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.