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'.
self.attr = attr or []
dentro do__init__
método. Alcança o mesmo resultado (eu acho) e ainda é claro e legível.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
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
fonte
def __init__(self, num = None)
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.
fonte
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]
fonte
[]
foi clonado por ali;x.l
ey.l
são nomes malignos e diferentes valores imediatos vinculados ao mesmo referente. (Termos do advogado de idiomas inventados)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__
.fonte
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
dataclass
irá 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)
fonte