È buona norma dichiarare variabili di istanza come nessuna in una classe in Python?


68

Considera la seguente classe:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

I miei colleghi tendono a definirlo così:

class Person:
    name = None
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

Il motivo principale di ciò è che il loro editor di scelta mostra le proprietà per il completamento automatico.

Personalmente, non mi piace quest'ultimo, perché non ha senso che una classe abbia queste proprietà impostate None.

Quale sarebbe la migliore pratica e per quali motivi?


63
Non lasciare mai che il tuo IDE imponga il codice che scrivi?
Martijn Pieters,

14
A proposito: usando un IDE python appropriato (ad es. PyCharm), impostando gli attributi nel __init__già fornisce il completamento automatico ecc. Inoltre, usando Noneimpedisce all'IDE di dedurre un tipo migliore per l'attributo, quindi è meglio usare invece un valore predefinito ragionevole (quando possibile).
Bakuriu,

Se è solo per il completamento automatico, puoi utilizzare il suggerimento per tipo e anche i documenti aggiuntivi saranno un vantaggio.
dashesy

3
"Non lasciare che il tuo IDE imponga il codice che scrivi" è un problema discusso. A partire da Python 3.6 ci sono annotazioni in linea e un typingmodulo, che ti consentono di fornire suggerimenti all'IDE e alla linter, se quel genere di cose solletica la tua fantasia ...
cz

1
Questi compiti a livello di classe non hanno alcun effetto sul resto del codice. Non hanno alcun effetto self. Anche se non sono stati assegnati self.nameo self.agenon __init__vengono visualizzati nell'istanza self, vengono visualizzati solo nella classe Person.
jolvi,

Risposte:


70

Chiamo quest'ultima cattiva pratica sotto la regola "questo non fa ciò che pensi".

La posizione del tuo collega può essere riscritta come: "Ho intenzione di creare un gruppo di variabili quasi-globali statiche di classe a cui non si accede mai, ma che occupano spazio nelle tabelle dello spazio dei nomi delle varie classi ( __dict__), solo per far funzionare il mio IDE qualcosa."


4
Un po 'come i docstrings allora ;-) Certo, Python ha una comoda modalità per eliminare quelli -OO, per quei pochi che ne hanno bisogno.
Steve Jessop,

15
Lo spazio occupato è di gran lunga la ragione meno importante per cui questa convenzione puzza. Sono una dozzina di byte per nome (per processo). Mi sembra di aver sprecato il mio tempo a leggere solo la parte della tua risposta pertinente ad essa, è così poco importante il costo dello spazio.

8
@delnan Sono d'accordo sul fatto che la dimensione della memoria delle voci sia insignificante, pensavo più allo spazio logico / mentale occupato, facevo il debug introspettivo e richiedevo più lettura e ordinamento, immagino. I ~ LL
StarWeaver,

3
In realtà, se fossi così paranoico sullo spazio, noteresti che questo fa risparmiare spazio e fa perdere tempo. I membri non inizializzati falliscono per usare il valore della classe e quindi non ci sono un sacco di copie del nome della variabile mappate su None (una per ogni istanza). Quindi il costo è di pochi byte nella classe e il risparmio è di pochi byte per istanza. Ma ogni ricerca di nomi di variabili non riuscita costa un po 'di tempo.
Jon Jay Obermark,

2
Se fossi preoccupato per 8 byte non utilizzeresti Python.
CZ

26

1. Rendi il tuo codice facile da capire

Il codice viene letto molto più spesso di quanto scritto. Rendi il compito del manutentore del codice più semplice (potrebbe anche essere tu stesso l'anno prossimo).

Non sono a conoscenza di regole rigide, ma preferisco che qualsiasi stato di istanza futuro sia dichiarato chiaramente. Arrestare con un AttributeErrorè abbastanza brutto. Non vedere chiaramente il ciclo di vita di un attributo di istanza è peggio. La quantità di ginnastica mentale necessaria per ripristinare possibili sequenze di chiamate che portano all'attribuzione assegnata può facilmente diventare non banale, portando a errori.

Quindi di solito non solo definisco tutto nel costruttore, ma cerco anche di ridurre al minimo il numero di attributi mutabili.

2. Non mescolare membri a livello di classe e di istanza

Qualunque cosa tu definisca proprio all'interno della classdichiarazione appartiene alla classe ed è condivisa da tutte le istanze della classe. Ad esempio, quando si definisce una funzione all'interno di una classe, questa diventa un metodo uguale per tutte le istanze. Lo stesso vale per i membri dei dati. Questo è totalmente diverso dagli attributi di istanza che di solito definisci __init__.

I membri di dati a livello di classe sono più utili come costanti:

class Missile(object):
  MAX_SPEED = 100  # all missiles accelerate up to this speed
  ACCELERATION = 5  # rate of acceleration per game frame

  def move(self):
    self.speed += self.ACCELERATION
    if self.speed > self.MAX_SPEED:
      self.speed = self.MAX_SPEED
    # ...

2
Sì, ma mescolare membri a livello di classe e di istanza è praticamente ciò che fa def . Crea funzioni che pensiamo siano attributi degli oggetti, ma sono veramente membri della classe. La stessa cosa vale per la proprietà e per i suoi simili. Dare l'illusione che il lavoro appartenga agli oggetti quando è realmente mediato dalla classe è un modo consueto di non impazzire. Come può essere tutto così male, se va bene per Python stesso.
Jon Jay Obermark,

Bene, l'ordine di risoluzione dei metodi in Python (applicabile anche ai membri dei dati) non è molto semplice. Ciò che non si trova a livello di istanza, verrà cercato a livello di classe, quindi tra le classi di base, ecc. È possibile infatti eseguire l'ombra di un membro a livello di classe (dati o metodo) assegnando un membro a livello di istanza con lo stesso nome. Ma i metodi a livello di istanza sono tenuti ad esempio di selfe non hanno bisogno selfdi essere passato, mentre i metodi a livello di classe sono non legato e sono funzioni semplici come si è visto in deftempo, e accettano un esempio come primo argomento. Quindi queste sono cose diverse.
9000

Penso che un AttributeErrorsia un buon segnale di quanto ci sia un bug. Altrimenti, ingeriresti il ​​Nessuno e otterrai risultati insignificanti. Particolarmente importante nel caso in esame, in cui gli attributi sono definiti in __init__, quindi un attributo mancante (ma esistente a livello di classe) potrebbe essere causato solo da eredità errata.
Davidmh,

@Davidmh: un errore rilevato è sempre meglio di un errore non rilevato, anzi! Direi che se devi creare un attributo Nonee questo valore non ha senso al momento della costruzione dell'istanza, hai un problema nella tua architettura e devi ripensare il ciclo di vita del valore dell'attributo o del suo valore iniziale. Nota che definendo i tuoi attributi in anticipo puoi rilevare questo problema anche prima di scrivere il resto della classe, e tanto meno eseguire il codice.
9000

Divertimento! missili! comunque, sono abbastanza sicuro che sia OK fare vari livelli di classe e mescolarli ... fintanto che il livello di classe contiene impostazioni predefinite, ecc.
Erik Aronesty,

18

Personalmente definisco i membri nel metodo __ init __ (). Non ho mai pensato di definirli nella parte di classe. Ma quello che faccio sempre: avvio tutti i membri nel metodo __ init__, anche quelli non necessari nel metodo __ init__.

Esempio:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
        self._selected = None

   def setSelected(self, value):
        self._selected = value

Penso che sia importante definire tutti i membri in un unico posto. Rende il codice più leggibile. Che si tratti di __ init __ () o esterno, non è così importante. Ma è importante che un team si impegni più o meno allo stesso stile di codifica.

Oh, e potresti notare che ho mai aggiunto il prefisso "_" alle variabili membro.


13
È necessario impostare tutti i valori nel costruttore. Se il valore fosse impostato nella classe stessa, sarebbe condiviso tra istanze, il che va bene per Nessuno, ma non per la maggior parte dei valori. Quindi non cambiare nulla al riguardo. ;)
Remco Haszing,

4
I valori predefiniti per i parametri e la capacità di accettare argomenti posizionali e di parole chiave arbitrari rende molto meno doloroso di quanto ci si aspetterebbe. (Ed è in realtà possibile delegare la costruzione ad altri metodi o persino funzioni autonome, quindi se hai davvero bisogno di spedizioni multiple puoi farlo anche tu).
Sean Vieira,

6
@Benedict Python non ha controllo di accesso. Il carattere di sottolineatura principale è la convenzione accettata per i dettagli di implementazione. Vedi PEP 8 .
Doval,

3
@Doval C'è un motivo straordinariamente valido per aggiungere il prefisso ai nomi degli attributi _: Per indicare che è privato! (Perché così tante persone in questa discussione confondono o confondono Python con altre lingue?)

1
Ah, quindi sei una di quelle persone-o-funzioni-per-tutte-persone-interazione esposte ... Mi spiace fraintendere. Quello stile mi sembra sempre eccessivo, ma ha un seguito.
Jon Jay Obermark,

11

Questa è una cattiva pratica. Non hai bisogno di quei valori, ingombrano il codice e possono causare errori.

Tener conto di:

>>> class WithNone:
...   x = None
...   y = None
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> class InitOnly:
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> wn = WithNone(1,2)
>>> wn.x
1
>>> WithNone.x #Note that it returns none, no error
>>> io = InitOnly(1,2)
>>> InitOnly.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: class InitOnly has no attribute 'x'

Mi farebbe fatica a chiamarlo "causa di un errore". È ambiguo cosa intendi richiedendo 'x' qui, ma potresti voler il valore iniziale più probabile.
Jon Jay Obermark,

Avrei dovuto capire che può causare un errore se usato in modo errato.
Daenyth,

Il rischio più elevato è ancora l'inizializzazione di un oggetto. Nessuno è davvero abbastanza sicuro. L'unico errore che sta per causare è di ingoiare eccezioni davvero scarse.
Jon Jay Obermark,

1
Non riuscire a causare errori è un problema se gli errori avrebbero impedito problemi più gravi.
Erik Aronesty,

0

Andrò con "un po 'come i docstring, quindi" e lo dichiarerò innocuo, purché sia ​​sempreNone , o una gamma ristretta di altri valori, tutti immutabili.

Puzza di atavismo e di eccessivo attaccamento alle lingue tipicamente statiche. E non va bene come codice. Ma ha uno scopo secondario che rimane, nella documentazione.

Documenta quali sono i nomi previsti, quindi se combino il codice con qualcuno e uno di noi ha "username" e l'altro user_name, c'è un indizio per gli umani che ci siamo separati e non stiamo usando le stesse variabili.

Forzare l'inizializzazione completa come politica raggiunge la stessa cosa in un modo più Pythonic, ma se c'è un codice reale nel __init__, questo fornisce un posto più chiaro per documentare le variabili in uso.

Ovviamente il GRANDE problema qui è che tenta la gente a inizializzare con valori diversi da None, il che può essere negativo:

class X:
    v = {}
x = X()
x.v[1] = 2

lascia una traccia globale e non crea un'istanza per x.

Ma è più una stranezza in Python nel suo insieme che in questa pratica, e dovremmo già essere paranoici al riguardo.

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.