Dovrei usare una classe o un dizionario?


99

Ho una classe che contiene solo campi e nessun metodo, come questa:

class Request(object):

    def __init__(self, environ):
        self.environ = environ
        self.request_method = environ.get('REQUEST_METHOD', None)
        self.url_scheme = environ.get('wsgi.url_scheme', None)
        self.request_uri = wsgiref.util.request_uri(environ)
        self.path = environ.get('PATH_INFO', None)
        # ...

Questo potrebbe essere facilmente tradotto in un dict. La classe è più flessibile per future aggiunte e potrebbe essere veloce con __slots__. Quindi ci sarebbe un vantaggio nell'usare invece un dict? Un dict sarebbe più veloce di una lezione? E più veloce di una classe con slot?


2
Uso sempre i dizionari per conservare i dati e questo mi sembra un caso d'uso. in alcuni casi dictpuò avere senso derivare una classe da , a. netto vantaggio: quando esegui il debug, dì print(request)e vedrai subito tutte le informazioni sullo stato. con l'approccio più classico dovrai scrivere i tuoi __str__metodi personalizzati , il che fa schifo se devi farlo tutto il tempo.
flusso


Se la lezione ha perfettamente senso ed è chiara agli altri, perché no? Inoltre, se ad esempio definisci molte classi con interfacce comuni, perché no? Ma Python non supporta potenti concetti orientati agli oggetti come C ++.
MasterControlProgram

3
@Ralf cosa OOP non supporta python?
qwr

Risposte:


32

Perché dovresti renderlo un dizionario? Qual è il vantaggio? Cosa succede se in seguito vuoi aggiungere del codice? Dove andrebbe il tuo __init__codice?

Le classi servono per raggruppare i dati correlati (e solitamente il codice).

I dizionari servono a memorizzare le relazioni valore-chiave, dove di solito le chiavi sono tutte dello stesso tipo e anche tutti i valori sono dello stesso tipo. Occasionalmente possono essere utili per raggruppare i dati quando i nomi di chiave / attributo non sono tutti noti in anticipo, ma spesso questo è un segno che qualcosa non va nel tuo design.

Mantieni questa una lezione.


Creerei una sorta di metodo factory creando un dict invece del __init__metodo della classe . Ma hai ragione: mi separerei dalle cose che stanno insieme.
demone

88
Non potrei essere più in disaccordo con te: dizionari, set, elenchi e tuple sono tutti lì per raggruppare i dati correlati. in nessun modo si suppone che i valori di un dizionario debbano o debbano essere dello stesso tipo di dati, al contrario. in molti elenchi e insiemi, i valori avranno lo stesso tipo, ma principalmente perché è quello che ci piace enumerare insieme. in realtà penso che l'uso diffuso di classi per conservare i dati sia un abuso di oop; quando pensi ai problemi di serializzazione, puoi facilmente capire perché.
flusso

4
È una programmazione orientata agli OGGETTI e non alle classi per un motivo: ci occupiamo di oggetti. Un oggetto è caratterizzato da 2 (3) proprietà: 1. stato (membri) 2. comportamento (metodi) e 3. istanza possono essere descritti in più parole. Pertanto le classi servono per raggruppare stato e comportamento.
friendzis

14
L'ho contrassegnato perché dovresti sempre impostare la struttura dati più semplice, ove possibile. In questo caso, un dizionario è sufficiente per lo scopo previsto. La domanda where would your __init__ code go?è preoccupante. Potrebbe persuadere uno sviluppatore meno esperto che devono essere utilizzate solo le classi poiché un metodo init non viene utilizzato in un dizionario. Assurdo.
Lloyd Moore

1
@Ralf Una classe Foo è semplicemente un tipo, come int e string. Memorizzi il valore in un tipo intero o in una variabile foo di tipo intero? Sottile, ma importante differenza semantica. In lingue come il C questa distinzione non è troppo rilevante al di fuori dei circoli accademici. Sebbene la maggior parte dei linguaggi OO abbia il supporto per le variabili di classe, il che rende la distinzione tra classe e oggetto / istanza estremamente rilevante: memorizzi i dati in classe (condivisi in tutte le istanze) o in un oggetto specifico? Non farti mordere
friendzis

44

Usa un dizionario a meno che tu non abbia bisogno del meccanismo extra di una classe. Puoi anche usare un namedtupleapproccio ibrido:

>>> from collections import namedtuple
>>> request = namedtuple("Request", "environ request_method url_scheme")
>>> request
<class '__main__.Request'>
>>> request.environ = "foo"
>>> request.environ
'foo'

Le differenze di prestazioni qui saranno minime, anche se sarei sorpreso se il dizionario non fosse più veloce.


15
"Le differenze di prestazioni qui saranno minime , anche se sarei sorpreso se il dizionario non fosse significativamente più veloce." Questo non calcola. :)
mipadi

1
@mipadi: questo è vero. Ora risolto: p
Katriel

Il dict è 1,5 volte più veloce di namedtuple e due volte più veloce di una classe senza slot. Controlla il mio post su questa risposta.
alexpinho98

@ alexpinho98: ho cercato di trovare il "post" a cui ti riferisci, ma non riesco a trovarlo! puoi fornire l'URL. Grazie!
Dan Oblinger

@DanOblinger Suppongo che intenda la sua risposta di seguito.
Adam Lewis

37

Una classe in Python è un dettato sottostante. Ottieni un sovraccarico con il comportamento della classe, ma non sarai in grado di notarlo senza un profiler. In questo caso, credo che tu tragga vantaggio dalla classe perché:

  • Tutta la tua logica vive in un'unica funzione
  • È facile da aggiornare e rimane incapsulato
  • Se modifichi qualcosa in seguito, puoi facilmente mantenere l'interfaccia uguale

Tutta la tua logica non vive in una singola funzione. Una classe è una tupla di stato condiviso e di solito uno o più metodi. Se cambi una classe, non ti viene data alcuna garanzia sulla sua interfaccia.
Lloyd Moore

25

Penso che l'uso di ciascuno di essi sia troppo soggettivo per me per entrare in questo, quindi mi limiterò ai numeri.

Ho confrontato il tempo necessario per creare e modificare una variabile in un dict, una classe new_style e una classe new_style con slot.

Ecco il codice che ho usato per testarlo (è un po 'disordinato ma fa il lavoro.)

import timeit

class Foo(object):

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

def create_dict():

    foo_dict = {}
    foo_dict['foo1'] = 'test'
    foo_dict['foo2'] = 'test'
    foo_dict['foo3'] = 'test'

    return foo_dict

class Bar(object):
    __slots__ = ['foo1', 'foo2', 'foo3']

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

tmit = timeit.timeit

print 'Creating...\n'
print 'Dict: ' + str(tmit('create_dict()', 'from __main__ import create_dict'))
print 'Class: ' + str(tmit('Foo()', 'from __main__ import Foo'))
print 'Class with slots: ' + str(tmit('Bar()', 'from __main__ import Bar'))

print '\nChanging a variable...\n'

print 'Dict: ' + str((tmit('create_dict()[\'foo3\'] = "Changed"', 'from __main__ import create_dict') - tmit('create_dict()', 'from __main__ import create_dict')))
print 'Class: ' + str((tmit('Foo().foo3 = "Changed"', 'from __main__ import Foo') - tmit('Foo()', 'from __main__ import Foo')))
print 'Class with slots: ' + str((tmit('Bar().foo3 = "Changed"', 'from __main__ import Bar') - tmit('Bar()', 'from __main__ import Bar')))

Ed ecco l'output ...

Creazione ...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

Modifica di una variabile ...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

Quindi, se stai solo memorizzando variabili, hai bisogno di velocità e non ti richiederà di fare molti calcoli, ti consiglio di usare un dict (potresti sempre creare una funzione che assomigli a un metodo). Ma, se hai davvero bisogno di lezioni, ricorda: usa sempre __ slot __ .

Nota:

Ho testato la "Classe" con entrambe le classi new_style e old_style. Si scopre che le classi old_style sono più veloci da creare ma più lente da modificare (non di molto ma significativo se stai creando molte classi in un ciclo stretto (suggerimento: stai sbagliando)).

Anche i tempi per la creazione e la modifica delle variabili possono differire sul tuo computer poiché il mio è vecchio e lento. Assicurati di testarlo tu stesso per vedere i risultati "reali".

Modificare:

Successivamente ho testato namedtuple: non posso modificarlo ma per creare i 10000 campioni (o qualcosa del genere) ci sono voluti 1,4 secondi, quindi il dizionario è davvero il più veloce.

Se cambio la funzione dict per includere chiavi e valori e per restituire il dict invece della variabile contenente il dict quando lo creo mi dà 0,65 invece di 0,8 secondi.

class Foo(dict):
    pass

La creazione è come una classe con slot e la modifica della variabile è la più lenta (0,17 secondi), quindi non utilizzare queste classi . vai per un dict (velocità) o per la classe derivata da object ('syntax candy')


Mi piacerebbe vedere i numeri per una sottoclasse di dict(senza metodi sovrascritti, immagino?). Funziona allo stesso modo di una classe di nuovo stile scritta da zero?
Benjamin Hodgson

12

Sono d'accordo con @adw. Non rappresenterei mai un "oggetto" (in senso OO) con un dizionario. I dizionari aggregano coppie nome / valore. Le classi rappresentano gli oggetti. Ho visto il codice in cui gli oggetti sono rappresentati con dizionari e non è chiaro quale sia la forma effettiva della cosa. Cosa succede quando non ci sono determinati nomi / valori? Cosa impedisce al cliente di inserire qualsiasi cosa. O cercare di ottenere qualcosa. La forma della cosa dovrebbe essere sempre chiaramente definita.

Quando si utilizza Python è importante costruire con disciplina poiché il linguaggio consente all'autore di spararsi in piedi in molti modi.


9
Le risposte su SO sono ordinate in base ai voti e le risposte con lo stesso numero di voti sono ordinate in modo casuale. Quindi, per favore, chiarisci chi intendi per "l'ultimo poster".
Mike DeSimone

4
E come fai a sapere che qualcosa che ha solo campi e nessuna funzionalità è un "oggetto" in senso OO?
Muhammad Alkarouri

1
La mia cartina di tornasole è "la struttura di questi dati è corretta?". Se sì, allora usa un oggetto, altrimenti un dict. Allontanarsi da questo creerà solo confusione.
weberc2

Devi analizzare il contesto del problema che stai risolvendo. Gli oggetti dovrebbero essere relativamente ovvi una volta che hai familiarità con quel paesaggio. Personalmente penso che restituire un dizionario dovrebbe essere la tua ultima risorsa, a meno che ciò che stai restituendo DOVREBBE essere una mappatura di coppie nome / valore. Ho visto troppo codice sciatto in cui tutto ciò che è passato e distribuito dai metodi è solo un dizionario. È pigro. Amo i linguaggi digitati dinamicamente, ma mi piace anche che il mio codice sia chiaro e logico. Spingere tutto in un dizionario può essere una comodità che nasconde il significato.
jaydel

5

Consiglierei una lezione, poiché sono tutti i tipi di informazioni coinvolte in una richiesta. Se uno usasse un dizionario, mi aspetterei che i dati memorizzati fossero di natura molto più simile. Una linea guida che tendo a seguire io stesso è che se posso voler scorrere l'intero set di coppie chiave-> valore e fare qualcosa, uso un dizionario. Altrimenti, i dati apparentemente hanno una struttura molto più strutturata di una semplice mappatura chiave-> valore, il che significa che una classe sarebbe probabilmente un'alternativa migliore.

Quindi, resta con la classe.


2
Sono totalmente in disaccordo. Non c'è motivo di limitare l'uso dei dizionari alle cose su cui iterare. Sono per mantenere una mappatura.
Katriel,

1
Leggi un po 'più da vicino, per favore. Ho detto che potrei voler ripetere il ciclo , non lo farò . Per me, l'uso di un dizionario implica una grande somiglianza funzionale tra chiavi e valori. Ad esempio, nomi con età. Usare una classe significherebbe che le varie "chiavi" hanno valori con significati molto diversi. Ad esempio, prendi un corso PErson. In questo caso "nome" sarebbe una stringa e "amici" potrebbe essere un elenco, un dizionario o un altro oggetto adatto. Non eseguirai il ciclo su tutti questi attributi come parte del normale utilizzo di questa classe.
Stigma

1
Penso che la distinzione tra classi e dizionari sia resa indistinta in Python dal fatto che le prime sono implementate usando il secondo (nonostante gli 'slot'). So che questo mi ha confuso un po 'quando stavo imparando la lingua per la prima volta (insieme al fatto che le classi erano oggetti e quindi istanze di alcune misteriose metaclassi).
martineau

4

Se tutto ciò che vuoi ottenere è una sintassi simile a caramelle obj.bla = 5invece di obj['bla'] = 5, specialmente se devi ripeterlo molto, forse vuoi usare qualche semplice classe contenitore come nel suggerimento di martineaus. Tuttavia, il codice è piuttosto gonfio e inutilmente lento. Puoi mantenerlo semplice in questo modo:

class AttrDict(dict):
    """ Syntax candy """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

Un altro motivo per passare a so a namedtupleuna classe __slots__potrebbe essere l'utilizzo della memoria. I dict richiedono molta più memoria rispetto ai tipi di elenco, quindi questo potrebbe essere un punto su cui riflettere.

Ad ogni modo, nel tuo caso specifico, non sembra esserci alcuna motivazione per abbandonare la tua attuale implementazione. Non sembra che tu mantenga milioni di questi oggetti, quindi non sono richiesti tipi derivati ​​da elenchi. E in realtà contiene una logica funzionale all'interno di __init__, quindi non dovresti nemmeno andare con AttrDict.


types.SimpleNamespace(disponibile a partire da Python 3.3) è un'alternativa al AttrDict personalizzato.
Cristian Ciupitu

4

Potrebbe essere possibile avere la tua torta e mangiarla anche tu. In altre parole, puoi creare qualcosa che fornisca la funzionalità sia di un'istanza di classe che di dizionario. Vedere la ricetta Dɪᴄᴛɪᴏɴᴀʀʏ ᴡɪᴛʜ ᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇ ᴀᴄᴄᴇss di ActiveState e commenti sui modi per farlo.

Se decidi di usare una classe normale piuttosto che una sottoclasse, ho trovato la ricetta Tʜᴇ sɪᴍᴘʟᴇ ʙᴜᴛ ʜᴀɴᴅʏ "ᴄᴏʟʟᴇᴄᴛᴏʀ ᴏғ ᴀ ʙᴜɴᴄʜ ᴏғ ɴᴀᴍᴇᴅ sᴛᴜғғ" ᴄʟᴀss (di Alex Martelli) molto flessibile e utile per il genere di cose che sembra che tu stia facendo (cioè creare un aggregatore di informazioni relativamente semplice). Poiché si tratta di una classe, puoi facilmente estendere ulteriormente le sue funzionalità aggiungendo metodi.

Infine va notato che i nomi dei membri della classe devono essere identificatori Python legali, ma le chiavi del dizionario no, quindi un dizionario fornirebbe maggiore libertà a questo riguardo perché le chiavi possono essere qualsiasi cosa hashable (anche qualcosa che non è una stringa).

Aggiornare

Una classe object(che non ha una __dict__) sottoclasse denominata SimpleNamespace(che ne ha una) è stata aggiunta al typesmodulo Python 3.3, ed è ancora un'altra alternativa.


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.