Come devo dichiarare i valori predefiniti per le variabili di istanza in Python?


91

Devo dare ai membri della mia classe valori predefiniti come questo:

class Foo:
    num = 1

o così?

class Foo:
    def __init__(self):
        self.num = 1

In questa domanda ho scoperto che in entrambi i casi,

bar = Foo()
bar.num += 1

è un'operazione ben definita.

Capisco che il primo metodo mi darà una variabile di classe mentre il secondo no. Tuttavia, se non richiedo una variabile di classe, ma ho solo bisogno di impostare un valore predefinito per le mie variabili di istanza, entrambi i metodi sono ugualmente buoni? O uno di loro più "pitonico" dell'altro?

Una cosa che ho notato è che nel tutorial Django, usano il secondo metodo per dichiarare i modelli. Personalmente penso che il secondo metodo sia più elegante, ma mi piacerebbe sapere qual è il modo "standard".

Risposte:


135

Estendendo la risposta di bp, volevo mostrarti cosa intendeva per tipi immutabili.

Innanzitutto, va bene:

>>> class TestB():
...     def __init__(self, attr=1):
...         self.attr = attr
...     
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1

Tuttavia, questo funziona solo per i tipi immutabili (non modificabili). Se il valore predefinito fosse modificabile (ovvero può essere sostituito), ciò accadrebbe invece:

>>> class Test():
...     def __init__(self, attr=[]):
...         self.attr = attr
...     
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>> 

Nota che entrambi ae bhanno un attributo condiviso. Questo è spesso indesiderato.

Questo è il modo pitonico di definire i valori di default per le variabili di istanza, quando il tipo è mutabile:

>>> class TestC():
...     def __init__(self, attr=None):
...         if attr is None:
...             attr = []
...         self.attr = attr
...     
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]

Il motivo per cui il mio primo frammento di codice funziona è perché, con tipi immutabili, Python ne crea una nuova istanza ogni volta che ne vuoi uno. Se hai bisogno di aggiungere 1 a 1, Python ne crea un nuovo 2, perché il vecchio 1 non può essere modificato. Il motivo è principalmente per l'hashing, credo.


2
Recentemente mi sono imbattuto in questo modo molto più semplice di implementare i valori predefiniti per gli attributi modificabili. Basta scrivere self.attr = attr or []all'interno del __init__metodo. Ottiene lo stesso risultato (credo) ed è ancora chiaro e leggibile.
Bill

5
Scusate gente. Ho fatto altre ricerche sul suggerimento nel mio commento sopra e apparentemente non è proprio lo stesso e non è raccomandato .
Fattura

Perché la classe TestC ha le parentesi vuote? È un'eredità di Python 2?
ChrisG

55

I due snippet fanno cose diverse, quindi non è una questione di gusti ma una questione di quale sia il comportamento giusto nel tuo contesto. Documentazione Python spiega la differenza, ma ecco alcuni esempi:

Allegato A

class Foo:
  def __init__(self):
    self.num = 1

Questo si lega numalle istanze di Foo . Le modifiche a questo campo non vengono propagate ad altre istanze.

Quindi:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

Allegato B

class Bar:
  num = 1

Questo si lega numal Bar classe . Le modifiche vengono propagate!

>>> bar1 = Bar()
>>> bar2 = Bar()
>>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation
>>> bar2.num
1
>>> Bar.num = 3
>>> bar2.num
3
>>> bar1.num
2
>>> bar1.__class__.num
3

Risposta effettiva

Se non ho bisogno di una variabile di classe, ma ho solo bisogno di impostare un valore predefinito per le mie variabili di istanza, entrambi i metodi sono ugualmente validi? O uno di loro più "pitonico" dell'altro?

Il codice nella mostra B è semplicemente sbagliato per questo: perché dovresti voler associare un attributo di classe (valore predefinito sulla creazione dell'istanza) alla singola istanza?

Il codice nella mostra A va bene.

Se vuoi fornire valori predefiniti per le variabili di istanza nel tuo costruttore, lo farei comunque:

class Foo:
  def __init__(self, num = None):
    self.num = num if num is not None else 1

...o anche:

class Foo:
  DEFAULT_NUM = 1
  def __init__(self, num = None):
    self.num = num if num is not None else DEFAULT_NUM

... o anche: (preferibile, ma se e solo se hai a che fare con tipi immutabili!)

class Foo:
  def __init__(self, num = 1):
    self.num = num

In questo modo puoi fare:

foo1 = Foo(4)
foo2 = Foo() #use default

1
è il metodo di inizializzazione utilizzato in questo esempio diverso da underscore___init__underscore per l'inizializzazione @badp
Eliethesaiyan

Il primo esempio non dovrebbe esseredef __init__(self, num = None)
PyWalker2797

6

Usare i membri della classe per fornire valori predefiniti funziona molto bene fintanto che stai attento a farlo solo con valori immutabili. Se provi a farlo con una lista o un dict sarebbe piuttosto mortale. Funziona anche quando l'attributo di istanza è un riferimento a una classe purché il valore predefinito sia Nessuno.

Ho visto questa tecnica usata con molto successo in repoze, che è un framework che gira su Zope. Il vantaggio qui non è solo che quando la tua classe è persistente nel database è necessario salvare solo gli attributi non predefiniti, ma anche quando devi aggiungere un nuovo campo nello schema tutti gli oggetti esistenti vedono il nuovo campo con il suo valore predefinito valore senza alcuna necessità di modificare effettivamente i dati memorizzati.

Trovo che funzioni bene anche nella codifica più generale, ma è una cosa di stile. Usa quello con cui sei più felice.


3

Usare i membri della classe per i valori predefiniti delle variabili di istanza non è una buona idea ed è la prima volta che vedo menzionata questa idea. Funziona nel tuo esempio, ma in molti casi potrebbe non funzionare. Ad esempio, se il valore è modificabile, modificarlo su un'istanza non modificata altererà il valore predefinito:

>>> class c:
...     l = []
... 
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]

tranne che solo il primo è []stato clonato lì intorno; x.le y.lsono entrambi nomi malvagi e valori immediati diversi collegati allo stesso referente. (Termini formulati dall'avvocato di lingua)
Phlip

1

Puoi anche dichiarare le variabili di classe come Nessuna che impedirà la propagazione. Ciò è utile quando è necessaria una classe ben definita e si desidera impedire AttributeErrors. Per esempio:

>>> class TestClass(object):
...     t = None
... 
>>> test = TestClass()
>>> test.t
>>> test2 = TestClass()
>>> test.t = 'test'
>>> test.t
'test'
>>> test2.t
>>>

Inoltre, se hai bisogno di impostazioni predefinite:

>>> class TestClassDefaults(object):
...    t = None
...    def __init__(self, t=None):
...       self.t = t
... 
>>> test = TestClassDefaults()
>>> test.t
>>> test2 = TestClassDefaults([])
>>> test2.t
[]
>>> test.t
>>>

Ovviamente segui ancora le informazioni nelle altre risposte sull'uso di tipi mutabili e immutabili come predefiniti in __init__.


0

Con le classi di dati , una funzionalità aggiunta in Python 3.7, ora c'è ancora un altro modo (abbastanza conveniente) per ottenere l'impostazione dei valori predefiniti sulle istanze di classe. Il decoratore dataclassgenererà automaticamente alcuni metodi sulla tua classe, come il costruttore. Come la documentazione collegata sopra le note, "[le] e variabili membro da utilizzare in questi metodi generati sono definite utilizzando annotazioni di tipo PEP 526 ".

Considerando l'esempio di OP, potremmo implementarlo in questo modo:

from dataclasses import dataclass

@dataclass
class Foo:
    num: int = 0

Quando si costruisce un oggetto del tipo di questa classe, è possibile facoltativamente sovrascrivere il valore.

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)
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.