Como devo declarar valores padrão para variáveis ​​de instância em Python?

91

Devo dar aos meus alunos valores padrão como este:

class Foo:
    num = 1

ou assim?

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

Em esta pergunta eu descobri que em ambos os casos,

bar = Foo()
bar.num += 1

é uma operação bem definida.

Eu entendo que o primeiro método me dará uma variável de classe, enquanto o segundo não. No entanto, se eu não exigir uma variável de classe, mas apenas precisar definir um valor padrão para minhas variáveis ​​de instância, os dois métodos são igualmente bons? Ou um deles mais 'pitônico' do que o outro?

Uma coisa que notei é que no tutorial do Django, eles usam o segundo método para declarar Modelos. Pessoalmente, acho que o segundo método é mais elegante, mas gostaria de saber qual é a maneira 'padrão'.

int3
fonte

Respostas:

135

Estendendo a resposta de bp, eu gostaria de mostrar o que ele quis dizer com tipos imutáveis.

Primeiro, está tudo bem:

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

No entanto, isso só funciona para tipos imutáveis ​​(inalteráveis). Se o valor padrão fosse mutável (o que significa que pode ser substituído), isso aconteceria:

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

Observe que ambos ae btêm um atributo compartilhado. Isso geralmente é indesejado.

Esta é a maneira pitônica de definir valores padrão para variáveis ​​de instância, quando o tipo é mutável:

>>> 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
[]

A razão pela qual meu primeiro trecho de código funciona é porque, com tipos imutáveis, o Python cria uma nova instância dele sempre que você quiser. Se você precisava adicionar 1 a 1, o Python cria um novo 2 para você, porque o 1 antigo não pode ser alterado. A razão é principalmente para hash, eu acredito.

Xavier Ho
fonte
2
Recentemente, descobri essa maneira muito mais simples de implementar valores padrão para atributos mutáveis. Basta escrever self.attr = attr or []dentro do __init__método. Alcança o mesmo resultado (eu acho) e ainda é claro e legível.
Bill
5
Desculpe pessoal. Eu pesquisei mais sobre a sugestão no meu comentário acima e, aparentemente, não é exatamente o mesmo e não é recomendado .
Bill
Por que a classe TestC tem os parênteses vazios? Isso é um legado do Python 2?
ChrisG,
55

Os dois snippets fazem coisas diferentes, então não é uma questão de gosto, mas uma questão de qual é o comportamento correto em seu contexto. A documentação do Python explica a diferença, mas aqui estão alguns exemplos:

Anexo A

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

Isso se liga numàs instâncias Foo . A alteração neste campo não é propagada para outras instâncias.

Portanto:

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

Prova B

class Bar:
  num = 1

Isso se liga numà classe Bar . As mudanças são propagadas!

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

Resposta real

Se eu não exigir uma variável de classe, mas apenas precisar definir um valor padrão para minhas variáveis ​​de instância, os dois métodos são igualmente bons? Ou um deles mais 'pitônico' do que o outro?

O código na exposição B está totalmente errado para isso: por que você deseja vincular um atributo de classe (valor padrão na criação da instância) à única instância?

O código na prova A está correto.

Se você quiser fornecer padrões para variáveis ​​de instância em seu construtor, eu faria o seguinte:

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

... ou mesmo:

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

... ou mesmo: (preferível, mas se e somente se você estiver lidando com tipos imutáveis!)

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

Desta forma, você pode fazer:

foo1 = Foo(4)
foo2 = Foo() #use default
badp
fonte
1
é o método init usado neste exemplo diferente de underscore___init__underscore para inicialização @badp
Eliethesaiyan
O primeiro exemplo não deveria serdef __init__(self, num = None)
PyWalker2797
6

Usar membros da classe para fornecer valores padrão funciona muito bem, desde que você tenha o cuidado de fazer isso apenas com valores imutáveis. Se você tentar fazer isso com uma lista ou um ditado, seria mortal. Também funciona onde o atributo de instância é uma referência a uma classe, desde que o valor padrão seja Nenhum.

Eu vi essa técnica usada com muito sucesso no repoze, que é um framework que roda em cima do Zope. A vantagem aqui não é apenas que quando sua classe é persistida no banco de dados, apenas os atributos não padrão precisam ser salvos, mas também quando você precisa adicionar um novo campo ao esquema, todos os objetos existentes veem o novo campo com seu padrão valor sem qualquer necessidade de realmente alterar os dados armazenados.

Acho que também funciona bem na codificação mais geral, mas é uma coisa de estilo. Use o que você estiver mais feliz.

Duncan
fonte
3

Usar membros de classe para valores padrão de variáveis ​​de instância não é uma boa ideia, e é a primeira vez que vejo essa ideia ser mencionada. Funciona no seu exemplo, mas pode falhar em muitos casos. Por exemplo, se o valor for mutável, alterá-lo em uma instância não modificada alterará o padrão:

>>> class c:
...     l = []
... 
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]
Max Shawabkeh
fonte
exceto que apenas o primeiro []foi clonado por ali; x.le y.lsão nomes malignos e diferentes valores imediatos vinculados ao mesmo referente. (Termos do advogado de idiomas inventados)
Phlip
1

Você também pode declarar variáveis ​​de classe como Nenhum, o que impedirá a propagação. Isso é útil quando você precisa de uma classe bem definida e deseja evitar AttributeErrors. Por exemplo:

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

Além disso, se você precisar de padrões:

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

É claro que ainda siga as informações nas outras respostas sobre o uso de tipos mutáveis ​​e imutáveis ​​como o padrão em __init__.

Realista
fonte
0

Com as classes de dados , um recurso adicionado ao Python 3.7, agora existe outra maneira (bastante conveniente) de definir os valores padrão nas instâncias de classe. O decorador dataclassirá gerar automaticamente alguns métodos em sua classe, como o construtor. Como a documentação vinculada acima observa, "[as] variáveis ​​de membro para usar nesses métodos gerados são definidas usando anotações de tipo PEP 526 ".

Considerando o exemplo do OP, poderíamos implementá-lo assim:

from dataclasses import dataclass

@dataclass
class Foo:
    num: int = 0

Ao construir um objeto do tipo desta classe, podemos opcionalmente sobrescrever o valor.

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)
Johannes Gehrs
fonte