Qual é a diferença entre os atributos de classe e instância?
-
03-07-2019 - |
Pergunta
Existe alguma distinção significativa entre:
class A(object):
foo = 5 # some default value
vs.
class B(object):
def __init__(self, foo=5):
self.foo = foo
Se você está criando um monte de casos, há alguma diferença em requisitos de desempenho ou de espaço para os dois estilos? Quando você ler o código, você considera o significado dos dois estilos ser significativamente diferente?
Solução
considerações de desempenho Além, há um significativo semântica diferença. No caso atributo de classe, há apenas um objeto referido. No exemplo-atributo-set-em-instanciação, pode haver múltiplos objectos a que se refere. Por exemplo
>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
... def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[]
Outras dicas
A diferença é que o atributo na classe é compartilhada por todas as instâncias. O atributo em uma instância é exclusivo para essa instância.
Se vier de C ++, atributos de classe são mais como variáveis ??membro estático.
Aqui está uma muito boa pós , e resumo lo como abaixo.
class Bar(object):
## No need for dot syntax
class_var = 1
def __init__(self, i_var):
self.i_var = i_var
## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = MyClass(2)
## Finds i_var in foo's instance namespace
foo.i_var
## 2
## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1
E de forma visual
atribuição do atributo Class
-
Se um atributo de classe é definido acessando a classe, ele substituirá o valor para todas as instâncias
foo = Bar(2) foo.class_var ## 1 Bar.class_var = 2 foo.class_var ## 2
-
Se uma variável de classe é definido acessando uma instância, ele substituirá o valor apenas para essa instância . Isto essencialmente substitui a variável de classe e transforma-lo em uma variável de instância disponível, intuitivamente, apenas para essa instância .
foo = Bar(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 Bar.class_var ## 1
Quando você usaria atributo de classe?
-
Armazenamento constantes . Como atributos de classe pode ser acessada como atributos da própria classe, muitas vezes é bom para usá-los para armazenar Class-wide, constantes específicas-Class
class Circle(object): pi = 3.14159 def __init__(self, radius): self.radius = radius def area(self): return Circle.pi * self.radius * self.radius Circle.pi ## 3.14159 c = Circle(10) c.pi ## 3.14159 c.area() ## 314.159
-
Definir valores padrão . Como um exemplo trivial, poderíamos criar uma lista limitada (ou seja, uma lista que só pode conter um certo número de elementos ou menos) e optar por ter uma tampa padrão de 10 itens
class MyClass(object): limit = 10 def __init__(self): self.data = [] def item(self, i): return self.data[i] def add(self, e): if len(self.data) >= self.limit: raise Exception("Too many elements") self.data.append(e) MyClass.limit ## 10
Uma vez que as pessoas nos comentários aqui e em duas outras questões marcadas como dups todos parecem estar confuso sobre isso, da mesma forma, eu acho que vale a pena acrescentar uma resposta adicional no topo de de Alex Coventry.
O fato de que Alex está atribuindo um valor de um tipo mutável, como uma lista, não tem nada a ver com se as coisas são compartilhados ou não. Podemos ver isso com a função id
ou o operador is
:
>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
... def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False
(Se você está se perguntando por que eu usei object()
em vez de, digamos, 5
, desse para evitar correr em inteiros duas outras questões que eu não quero entrar aqui, por duas razões diferentes, totalmente independente 5
s -Criado pode acabar sendo a mesma instância do número 5
. Mas inteiramente object()
s criados separadamente não pode.)
Então, por que é que a.foo.append(5)
no exemplo de Alex afeta b.foo
, mas a.foo = 5
no meu exemplo não? Bem, tente a.foo = 5
no exemplo de Alex, e aviso de que ela não afeta b.foo
há qualquer .
a.foo = 5
é apenas fazer a.foo
em um nome para 5
. Isso não afeta b.foo
, ou qualquer outro nome para o valor antigo que a.foo
usado para se referir a. * É um pouco complicado que estamos criando um atributo instância que esconde um atributo de classe, ** mas uma vez que você conseguir isso, nada complicado está acontecendo aqui.
Esperamos que agora é óbvio por que Alex usou uma lista: o fato de que você pode se transformar um meio de lista é mais fácil mostrar que duas variáveis ??nomear a mesma lista, e também significa que é mais importante no código da vida real para saber se você tem duas listas ou dois nomes para a mesma lista.
* A confusão para as pessoas que vêm de uma linguagem como C ++ é que em Python, os valores não são armazenados em variáveis. Valores vivem no valor da terra, por conta própria, as variáveis ??são nomes apenas para valores, e atribuição apenas cria um novo nome para um valor. Se ajudar, pense em cada variável Python como um shared_ptr<T>
em vez de um T
.
** Algumas pessoas tomam vantagem deste usando um atributo de classe como um "valor padrão" para um atributo de instância que casos pode ou não definido. Isso pode ser útil em alguns casos, mas também pode ser confuso, por isso tome cuidado com ele.
Há mais uma situação.
Class e instância atributos é descritor .
# -*- encoding: utf-8 -*-
class RevealAccess(object):
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
return self.val
class Base(object):
attr_1 = RevealAccess(10, 'var "x"')
def __init__(self):
self.attr_2 = RevealAccess(10, 'var "x"')
def main():
b = Base()
print("Access to class attribute, return: ", Base.attr_1)
print("Access to instance attribute, return: ", b.attr_2)
if __name__ == '__main__':
main()
saída vontade Acima:
('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)
O mesmo tipo de acesso exemplo, através de classe ou instância de retorno resultado diferente!
E eu achei em c.PyObject_GenericGetAttr definição , e uma grande pós .
Explique
Se o atributo é encontrado no dicionário das classes que compõem. Os objetos MRO, em seguida, verificar para ver se o ser atributo olhou para cima aponta para um descritor de dados (que nada mais é que uma classe que implementa tanto o
__get__
e os métodos__set__
). Se isso acontecer, resolver a pesquisa atributo chamando o método__get__
dos dados do descritor (linhas 28-33).