É uma boa prática declarar variáveis ​​de instância como None em uma classe em Python?


68

Considere a seguinte classe:

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

Meus colegas de trabalho tendem a defini-lo assim:

class Person:
    name = None
    age = None

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

A principal razão para isso é que o editor de sua escolha mostra as propriedades do preenchimento automático.

Pessoalmente, não gosto do último, porque não faz sentido que uma classe tenha essas propriedades definidas None.

Qual seria a melhor prática e por que razões?


63
Nunca deixe seu IDE ditar o código que você escreve?
Martijn Pieters

14
A propósito: usando um IDE python adequado (por exemplo, PyCharm), definir os atributos no __init__já fornece preenchimento automático etc. Além disso, o uso Noneevita que o IDE deduza um tipo melhor para o atributo, por isso é melhor usar um padrão sensato (quando possível).
Bakuriu 27/08/14

Se for apenas para preenchimento automático, você pode usar dicas de tipo, e as instruções adicionais também serão uma vantagem.
dashesy

3
"Nunca deixe seu IDE ditar o código que você escreve" é um problema debatido. A partir de Python 3.6 há anotações in-line e um typingmódulo, que permitem fornecer dicas para o IDE e linter, se esse tipo de coisa lhe apeteça ...
CZ

11
Essas tarefas no nível da classe não têm efeito no restante do código. Eles não têm efeito self. Mesmo se self.nameou self.agenão foram atribuídos em __init__que não iria aparecer na instância self, eles só aparecem na classe Person.
jolvi

Respostas:


70

Eu chamo a última prática ruim sob a regra "isso não faz o que você pensa que faz".

A posição do seu colega de trabalho pode ser reescrita como: "Vou criar um monte de variáveis ​​quase globais estáticas de classe que nunca são acessadas, mas que ocupam espaço nas tabelas de namespace das várias classes ( __dict__), apenas para fazer meu IDE funcionar alguma coisa."


4
Um pouco como as doutrinas então ;-) É claro que o Python tem um modo útil de remover essas -OO, para os poucos que precisam dela.
Steve Jessop

15
O espaço ocupado é, de longe, a razão menos importante pela qual esta convenção fede. É uma dúzia de bytes por nome (por processo). Sinto que perdi meu tempo apenas lendo a parte da sua resposta referente a ela; é assim que não importa o custo do espaço.

8
@delnan Concordo que o tamanho da memória das entradas não tem sentido, eu estava pensando no espaço lógico / mental ocupado, fazendo a depuração introspectiva e exigir mais leitura e classificação, eu acho. I ~ LL
StarWeaver 27/08/14

3
Na verdade, se você fosse paranóico com relação ao espaço, perceberia que isso economiza espaço e desperdiça tempo. Os membros não inicializados passam a usar o valor da classe e, portanto, não há um monte de cópias do nome da variável mapeadas para None (uma para cada instância). Portanto, o custo é de alguns bytes na classe e a economia é de alguns bytes por instância. Mas cada pesquisa de nome de variável com falha custa um pouco de tempo.
JavaScript é necessário

2
Se você estivesse preocupado com 8 bytes, não usaria o Python.
CZ

26

1. Torne seu código fácil de entender

O código é lido com muito mais frequência do que escrito. Facilite a tarefa do seu mantenedor de código (também pode ser você mesmo no próximo ano).

Não conheço regras rígidas, mas prefiro que qualquer estado de instância futuro seja claramente declarado. Bater com um já AttributeErroré ruim o suficiente. Não ver claramente o ciclo de vida de um atributo de instância é pior. A quantidade de ginástica mental necessária para restaurar possíveis sequências de chamadas que levam ao atributo a ser atribuído pode facilmente se tornar não trivial, levando a erros.

Portanto, eu geralmente não apenas defino tudo no construtor, mas também me esforço para manter o número mínimo de atributos mutáveis.

2. Não misture membros de classe e instância

Qualquer coisa que você definir dentro da classdeclaração pertence à classe e é compartilhada por todas as instâncias da classe. Por exemplo, quando você define uma função dentro de uma classe, ela se torna um método igual para todas as instâncias. O mesmo se aplica aos membros de dados. Isso é totalmente diferente dos atributos de instância nos quais você geralmente define __init__.

Membros de dados em nível de classe são mais úteis como constantes:

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
Sim, mas misturar membros em nível de classe e de instância é o que def faz. Ele cria funções que consideramos atributos de objetos, mas na verdade são membros da classe. A mesma coisa vale para a propriedade e seus afins. Dar a ilusão de que o trabalho pertence aos objetos quando é realmente mediado pela classe é uma maneira consagrada pelo tempo de não enlouquecer. Como pode ser tão ruim assim, se está OK para o próprio Python.
JavaScript é necessário

Bem, a ordem de resolução do método em Python (também aplicável aos membros de dados) não é muito simples. O que não é encontrado no nível da instância, será pesquisado no nível da classe e, em seguida, entre as classes base, etc. Você pode realmente ocultar um membro no nível da classe (dados ou método) atribuindo um membro no nível da instância com o mesmo nome. Porém, os métodos no nível da instância estão vinculados aos da instância selfe não precisam selfser passados, enquanto os métodos no nível da classe são ilimitados e são funções simples, como visto no defmomento, e aceitam uma instância como primeiro argumento. Então, essas são coisas diferentes.
9000

Eu acho que um AttributeErroré um bom sinal do que um bug. Caso contrário, você engoliria o None e obteria resultados sem sentido. Especialmente importante no caso em que os atributos são definidos no diretório __init__, portanto, um atributo ausente (mas existente no nível da classe) só pode ser causado por herança de buggy.
Davidmh

@ Davididmh: Um erro detectado é sempre melhor do que um erro não detectado, de fato! Eu diria que se você precisar criar um atributo Nonee esse valor não fizer sentido no momento da construção da instância, você terá um problema em sua arquitetura e precisará repensar o ciclo de vida do valor do atributo ou seu valor inicial. Observe que, definindo seus atributos antecipadamente, você pode detectar esse problema antes mesmo de escrever o restante da classe, sem falar em executar o código.
9000

Diversão! mísseis! de qualquer maneira, tenho certeza de que não há problema em criar vars no nível da classe e misturá-los ... desde que o nível da classe contenha padrões, etc.
Erik Aronesty

18

Pessoalmente, defino os membros no método __ init __ (). Eu nunca pensei em defini-los na parte da aula. Mas o que eu sempre faço: inicio todos os membros no método __ init__, mesmo aqueles que não são necessários no método __ init__.

Exemplo:

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

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

Eu acho que é importante definir todos os membros em um só lugar. Isso torna o código mais legível. Se é dentro de __ init __ () ou fora, não é tão importante. Mas é importante que uma equipe se comprometa com mais ou menos o mesmo estilo de codificação.

Ah, e você pode notar que eu adiciono o prefixo "_" às variáveis ​​de membro.


13
Você deve definir todos os valores no construtor. Se o valor fosse definido na própria classe, ele seria compartilhado entre instâncias, o que é bom para Nenhum, mas não para a maioria dos valores. Portanto, não mude nada sobre isso. ;)
Remco Haszing

4
Os valores padrão dos parâmetros e a capacidade de aceitar argumentos posicionais e de palavras-chave arbitrários tornam muito menos doloroso do que se poderia esperar. (E é realmente possível delegar a construção para outros métodos ou até funções independentes, por isso, se você realmente precisar de vários despachos, poderá fazer isso também).
Sean Vieira

6
O @Benedict Python não tem controle de acesso. O principal sublinhado é a convenção aceita para detalhes da implementação. Veja PEP 8 .
Doval

3
@Doval Há uma razão extraordinariamente boa para prefixar nomes de atributos com _: Para indicar que é privado! (Por que tantas pessoas neste segmento confuso ou meio confuso Python com outras línguas?)

11
Ah, então você é uma daquelas pessoas que interagem com propriedades ou funções para todas as pessoas expostas ... Desculpe por entender mal. Esse estilo sempre parece excessivo para mim, mas tem muitos seguidores.
precisa saber é o seguinte

11

Esta é uma má prática. Você não precisa desses valores, eles confundem o código e podem causar erros.

Considerar:

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

Eu seria pressionado a chamar isso de "causando um erro". É ambíguo o que você quer dizer com solicitar 'x' aqui, mas é possível que você queira o valor inicial mais provável.
JavaScript é necessário

Eu deveria ter elaborado que isso pode causar um erro se usado incorretamente.
Daenyth 27/08/14

O risco mais alto ainda está inicializando em um objeto. Nenhuma é realmente muito segura. O único erro que isso causará é engolir exceções realmente fracas.
JavaScript é necessário

11
Deixar de causar erros é um problema se os erros impedirem problemas mais sérios.
Erik Aronesty 03/09

0

Eu irei com "um pouco como as doutrinas, então" e declararei isso inofensivo, desde que seja sempreNone , ou uma faixa estreita de outros valores, todos imutáveis.

Cheira a atavismo e apego excessivo a linguagens estaticamente tipadas. E isso não serve como código. Mas tem um objetivo menor que permanece na documentação.

Ele documenta quais são os nomes esperados; portanto, se eu combinar código com alguém e um de nós tiver 'username' e o outro user_name, haverá uma pista para os humanos de que nos separamos e não estamos usando as mesmas variáveis.

Forçar a inicialização completa como uma política consegue a mesma coisa de uma maneira mais pitônica, mas se houver um código real no __init__, isso fornece um local mais claro para documentar as variáveis ​​em uso.

Obviamente, o grande problema aqui é que ele tenta que as pessoas inicializem com valores diferentes de None, o que pode ser ruim:

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

deixa um rastreio global e não cria uma instância para x.

Mas isso é mais uma peculiaridade no Python como um todo do que nesta prática, e já deveríamos estar paranóicos com relação a isso.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.