Variáveis ​​de instância vs. variáveis ​​de classe em Python


120

Eu tenho classes Python, das quais preciso apenas de uma instância em tempo de execução, portanto, seria suficiente ter os atributos apenas uma vez por classe e não por instância. Se houver mais de uma instância (o que não acontecerá), todas as instâncias deverão ter a mesma configuração. Gostaria de saber qual das seguintes opções seria melhor ou mais Python "idiomático".

Variáveis ​​de classe:

class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

Variáveis ​​de instância:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass

4
Depois de ler esta pergunta e ver a resposta, uma das minhas primeiras perguntas foi: "Então, como faço para acessar variáveis ​​de classe?" --que é porque até este ponto eu usei apenas variáveis ​​de instância. Em resposta à minha própria pergunta, você faz isso através do próprio nome da classe, embora tecnicamente também seja possível através de uma instância. Aqui está um link para ler para qualquer outra pessoa com a mesma pergunta: stackoverflow.com/a/3434596/4561887
Gabriel Staples

Respostas:


158

Se você tiver apenas uma instância de qualquer maneira, é melhor tornar todas as variáveis ​​por instância, simplesmente porque elas serão acessadas (um pouco) mais rapidamente (um nível a menos de "pesquisa" devido à "herança" de classe para instância), e não há desvantagens para pesar contra essa pequena vantagem.


7
Nunca ouviu falar do padrão Borg? Só ter uma instância era a maneira errada de tê-la em primeiro lugar.
Devin Jeanpierre

434
@ Kevin, sim, ouvi falar do padrão Borg, desde que fui eu quem o introduziu (em 2001, cfr code.activestate.com/recipes/… ;-). Mas não há nada errado, em casos simples, em simplesmente ter uma única instância sem aplicação.
precisa

2
@ user1767754, fácil de fazer você mesmo python -mtimeit- mas tendo feito isso em python3.4, observo que o acesso a uma intvariável de classe é realmente cerca de 5 a 11 nanossegundos mais rápido que o mesmo que a variável de instância na minha antiga estação de trabalho - não sei ao certo o que codepath faz isso.
Alex Martelli

45

Mais ecoando os conselhos de Mike e Alex e adicionando minha própria cor ...

Usar atributos de instância é o típico ... o Python mais idiomático. Os atributos de classe não são usados ​​tanto quanto os casos de uso são específicos. O mesmo vale para métodos estáticos e de classe vs. métodos "normais". Eles são construções especiais que abordam casos de uso específicos; caso contrário, é um código criado por um programador aberrante que deseja mostrar que conhece algum canto obscuro da programação em Python.

Alex menciona em sua resposta que o acesso será (um pouco) mais rápido devido a um nível a menos de pesquisa ... deixe-me esclarecer mais para quem ainda não sabe como isso funciona. É muito semelhante ao acesso variável - cuja ordem de pesquisa é:

  1. locais
  2. não-locais
  3. globais
  4. embutidos

Para acesso ao atributo, o pedido é:

  1. instância
  2. classe
  3. classes base, conforme determinado pelo MRO (ordem de resolução do método)

Ambas as técnicas funcionam de maneira "de dentro para fora", o que significa que os objetos mais locais são verificados primeiro e as camadas externas são verificadas sucessivamente.

No seu exemplo acima, digamos que você esteja procurando o pathatributo. Quando encontrar uma referência como " self.path", o Python examinará os atributos da instância primeiro para uma correspondência. Quando isso falha, verifica a classe da qual o objeto foi instanciado. Por fim, ele pesquisará as classes base. Como Alex afirmou, se o seu atributo for encontrado na instância, ele não precisará procurar em outro lugar, portanto, você economizará um pouco de tempo.

No entanto, se você insistir nos atributos da classe, precisará dessa pesquisa extra. Ou , sua outra alternativa é se referir ao objeto por meio da classe em vez da instância, por exemplo, em MyController.pathvez deself.path . Essa é uma pesquisa direta que contorna a pesquisa adiada, mas, como alex menciona abaixo, é uma variável global; portanto, você perde aquele pedaço que pensou em salvar (a menos que crie uma referência local ao nome da classe [global] )

A conclusão é que você deve usar atributos de instância na maioria das vezes. No entanto, haverá ocasiões em que um atributo de classe é a ferramenta certa para o trabalho. O código que usa os dois ao mesmo tempo exigirá a maior diligência, pois o uso selfapenas fornecerá o objeto de atributo da instância e sombreia o acesso ao atributo de classe com o mesmo nome. Nesse caso, você deve usar acessar o atributo pelo nome da classe para fazer referência a ele.


@wescpy, mas MyControlleré procurado nas globais, então o custo total é maior do que o local self.pathonde pathestá uma variável de instância (já que selfé local para o método == pesquisa super-rápida).
Alex Martelli

ah verdade. boa pegada. Eu acho que a única solução alternativa é criar uma referência local ... neste momento, não vale a pena.
Wescpy

24

Em caso de dúvida, você provavelmente deseja um atributo de instância.

Os atributos de classe são mais reservados para casos especiais em que fazem sentido. O único caso de uso muito comum são os métodos. Não é incomum usar atributos de classe para constantes somente leitura que as instâncias precisam saber (embora o único benefício disso seja se você também deseja acessar de fora da classe), mas certamente deve ser cauteloso ao armazenar qualquer estado nelas , o que raramente é o que você deseja. Mesmo que você tenha apenas uma instância, escreva a classe como faria com qualquer outra, o que geralmente significa usar atributos de instância.


1
As variáveis ​​de classe são um tipo de constantes somente leitura. Se o Python me deixasse definir constantes, eu teria escrito como constantes.
deamon

1
@ Deamon, eu sou um pouco mais provável colocar minhas constantes completamente fora das definições de uma classe e nomeá-las em maiúsculas. Colocá-los dentro da classe também é bom. Torná-los atributos de instância não prejudicará nada, mas pode ser um pouco estranho. Não acho que esse seja um problema em que a comunidade fique muito atrás de uma das opções.
Mike Graham

@MikeGraham FWIW, o Python Style Guide do Google sugere evitar variáveis ​​globais em favor das variáveis ​​de classe. Existem exceções, no entanto.
Dennis

Aqui está um novo link para o Python Style Guide do Google . Agora, está simplesmente escrito: avoid global variablese sua definição é que variáveis ​​globais também são variáveis ​​declaradas como atributos de classe. No entanto, o próprio guia de estilo do Python ( PEP-8 ) deve ser o primeiro lugar para perguntas desse tipo. Então, sua mente deve ser a ferramenta de escolha (é claro que você também pode obter idéias do Google, por exemplo).
Colidyre 12/07/19

4

Mesma pergunta em Desempenho de acessar variáveis ​​de classe em Python - o código aqui adaptado de @Edward Loper

As variáveis ​​locais são as mais rápidas de acessar, praticamente ligadas às variáveis ​​de módulo, seguidas pelas variáveis ​​de classe e pelas variáveis ​​de instância.

Existem 4 escopos nos quais você pode acessar variáveis:

  1. Variáveis ​​de instância (self.varname)
  2. Variáveis ​​de classe (Classname.varname)
  3. Variáveis ​​do módulo (VARNAME)
  4. Variáveis ​​locais (varname)

O teste:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

O resultado:

access via instance variable: 93.456
access via class variable: 82.169
access via module variable: 72.634
access via local variable: 72.199
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.