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?

Foi útil?

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

enter descrição da imagem aqui

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 5s -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 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).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top