Como evitar o compartilhamento de dados de classe entre instâncias?


145

O que eu quero é esse comportamento:

class a:
    list = []

x = a()
y = a()

x.list.append(1)
y.list.append(2)
x.list.append(3)
y.list.append(4)

print(x.list) # prints [1, 3]
print(y.list) # prints [2, 4]

Obviamente, o que realmente acontece quando imprimo é:

print(x.list) # prints [1, 2, 3, 4]
print(y.list) # prints [1, 2, 3, 4]

Claramente eles estão compartilhando os dados em sala de aula a. Como obtenho instâncias separadas para alcançar o comportamento que desejo?


14
Por favor, não use listcomo um nome de atributo. listé uma função embutida para construir uma nova lista. Você deve escrever classes de nome com letra maiúscula.
Marc Tudurí

Respostas:


147

Você quer isso:

class a:
    def __init__(self):
        self.list = []

Declarar as variáveis ​​dentro da declaração de classe faz com que sejam membros da "classe" e não membros da instância. Declará-los dentro do __init__método garante que uma nova instância dos membros seja criada ao lado de cada nova instância do objeto, que é o comportamento que você está procurando.


5
Um esclarecimento adicional: se você redesignasse a propriedade list em uma das instâncias, ela não afetaria as outras. Portanto, se você fizer algo assim x.list = [], poderá alterá-lo e não afetar outros. O problema que você enfrenta é essa x.liste y.listé a mesma lista; portanto, quando você chama o anexo em um, isso afeta o outro.
Matt Moriarty

Mas por que isso acontece apenas para a lista? Quando eu declarei um número inteiro ou string fora do init , ele não era compartilhado entre os objetos? Alguém pode compartilhar qualquer link de documento para esse conceito?
Amal Ts

1
@AmalTs Parece que você não entende como a atribuição em python funciona. Veja este vídeo ou esta postagem do SO . O comportamento que você vê é causado pelo fato de você estar alterando listas, mas refazendo referências a ints e strings.
Андрей Беньковский

4
@AmalTs Nota: é uma prática recomendada usar atributos de classe como valores padrão "preguiçosos" para atributos de instância. Mesmo que os atributos sejam de um tipo imutável, é melhor atribuí-los a eles __init__.
Андрей Беньковский

21

A resposta aceita funciona, mas um pouco mais de explicação não dói.

Atributos de classe não se tornam atributos de instância quando uma instância é criada. Eles se tornam atributos de instância quando um valor é atribuído a eles.

No código original, nenhum valor é atribuído ao listatributo após a instanciação; portanto, permanece um atributo de classe. A definição de lista interna __init__funciona porque __init__é chamada após a instanciação. Como alternativa, esse código também produziria a saída desejada:

>>> class a:
    list = []

>>> y = a()
>>> x = a()
>>> x.list = []
>>> y.list = []
>>> x.list.append(1)
>>> y.list.append(2)
>>> x.list.append(3)
>>> y.list.append(4)
>>> print(x.list)
[1, 3]
>>> print(y.list)
[2, 4]

No entanto, o cenário confuso da pergunta nunca acontecerá com objetos imutáveis, como números e cadeias, porque seu valor não pode ser alterado sem atribuição. Por exemplo, um código semelhante ao original com o tipo de atributo string funciona sem nenhum problema:

>>> class a:
    string = ''


>>> x = a()
>>> y = a()
>>> x.string += 'x'
>>> y.string += 'y'
>>> x.string
'x'
>>> y.string
'y'

Então, para resumir: os atributos de classe tornam-se atributos de instância se e somente se um valor é atribuído a eles após a instanciação, estando no __init__método ou não . Isso é bom porque dessa forma você pode ter atributos estáticos se nunca atribuir um valor a um atributo após a instanciação.


11

Você declarou "lista" como "propriedade de nível de classe" e não "propriedade de nível de instância". Para ter propriedades com escopo definido no nível da instância, é necessário inicializá-las através da referência ao parâmetro "self" no __init__método (ou em outro lugar, dependendo da situação).

Você não precisa estritamente inicializar as propriedades da instância no __init__método, mas facilita o entendimento.


11

Embora a resposta aceita esteja correta, eu gostaria de adicionar uma pequena descrição.

Vamos fazer um pequeno exercício

Antes de tudo, defina uma classe da seguinte maneira:

class A:
    temp = 'Skyharbor'

    def __init__(self, x):
        self.x = x

    def change(self, y):
        self.temp = y

Então o que temos aqui?

  • Temos uma classe muito simples que tem um atributo tempque é uma string
  • Um __init__método que defineself.x
  • Um método de mudança define self.temp

Bem direto até agora, sim? Agora vamos começar a brincar com essa classe. Vamos inicializar esta classe primeiro:

a = A('Tesseract')

Agora faça o seguinte:

>>> print(a.temp)
Skyharbor
>>> print(A.temp)
Skyharbor

Bem, a.tempfuncionou como esperado, mas como diabos A.tempfuncionou? Bem, funcionou porque temp é um atributo de classe. Tudo em python é um objeto. Aqui A também é um objeto de classe type. Portanto, o atributo temp é um atributo mantido pela Aclasse e se você alterar o valor de temp através A(e não através de uma instância de a), o valor alterado será refletido em toda a instância da Aclasse. Vamos em frente e faça isso:

>>> A.temp = 'Monuments'
>>> print(A.temp)
Monuments
>>> print(a.temp)
Monuments

Interessante, não é? E note que id(a.temp)e id(A.temp)ainda são os mesmos .

Qualquer objeto Python recebe automaticamente um __dict__atributo, que contém sua lista de atributos. Vamos investigar o que esse dicionário contém para nossos objetos de exemplo:

>>> print(A.__dict__)
{
    'change': <function change at 0x7f5e26fee6e0>,
    '__module__': '__main__',
    '__init__': <function __init__ at 0x7f5e26fee668>,
    'temp': 'Monuments',
    '__doc__': None
}
>>> print(a.__dict__)
{x: 'Tesseract'}

Observe que o tempatributo é listado entre Aos atributos da classe, enquanto xé listado para a instância.

Então, como é que obtemos um valor definido a.tempse ele nem está listado para a instância a? Bem, essa é a mágica do __getattribute__()método. No Python, a sintaxe pontilhada invoca automaticamente esse método; portanto, quando escrevemos a.temp, o Python é executado a.__getattribute__('temp'). Esse método executa a ação de pesquisa de atributo, ou seja, localiza o valor do atributo procurando em lugares diferentes.

A implementação padrão de __getattribute__()pesquisas primeiro no dicionário interno ( dict ) de um objeto, depois no tipo do objeto em si. Nesse caso, a.__getattribute__('temp')executa primeiro a.__dict__['temp']e depoisa.__class__.__dict__['temp']

Ok, agora vamos usar o nosso changemétodo:

>>> a.change('Intervals')
>>> print(a.temp)
Intervals
>>> print(A.temp)
Monuments

Bem, agora que usamos self, print(a.temp)nos dá um valor diferente de print(A.temp).

Agora, se compararmos id(a.temp)e id(A.temp), eles serão diferentes.


3

Sim, você deve declarar no "construtor" se desejar que a lista se torne uma propriedade de objeto e não uma propriedade de classe.


3

Portanto, quase todas as respostas aqui parecem perder um ponto específico. Variáveis ​​de classe nunca se tornam variáveis ​​de instância, conforme demonstrado pelo código abaixo. Utilizando uma metaclasse para interceptar a atribuição de variável no nível da classe, podemos ver que quando a.myattr é reatribuído, o método mágico de atribuição de campo na classe não é chamado. Isso ocorre porque a atribuição cria uma nova variável de instância . Esse comportamento não tem absolutamente nada a ver com a variável de classe, como demonstrado pela segunda classe, que não possui variáveis ​​de classe e ainda permite a atribuição de campos.

class mymeta(type):
    def __init__(cls, name, bases, d):
        pass

    def __setattr__(cls, attr, value):
        print("setting " + attr)
        super(mymeta, cls).__setattr__(attr, value)

class myclass(object):
    __metaclass__ = mymeta
    myattr = []

a = myclass()
a.myattr = []           #NOTHING IS PRINTED
myclass.myattr = [5]    #change is printed here
b = myclass()
print(b.myattr)         #pass through lookup on the base class

class expando(object):
    pass

a = expando()
a.random = 5            #no class variable required
print(a.random)         #but it still works

IN SHORT As variáveis ​​de classe não têm nada a ver com variáveis ​​de instância.

Mais claramente Eles estão no escopo de pesquisas em instâncias. Variáveis ​​de classe são de fato variáveis ​​de instância no próprio objeto de classe. Você também pode ter variáveis ​​de metaclasse, se desejar, porque as metaclasses também são objetos. Tudo é um objeto, seja ele usado para criar outros objetos ou não, portanto, não fique vinculado à semântica do uso de outras línguas da classe de palavras. Em python, uma classe é realmente apenas um objeto usado para determinar como criar outros objetos e quais serão seus comportamentos. Metaclasses são classes que criam classes, apenas para ilustrar melhor este ponto.


0

Para proteger sua variável compartilhada por outra instância, você precisa criar uma nova variável de instância sempre que criar uma instância. Quando você está declarando uma variável dentro de uma classe, ela é variável e compartilhada por todas as instâncias. Se você quiser fazê-lo, por exemplo, é necessário usar o método init para reinicializar a variável, conforme a instância

Dos objetos e classe Python da Programiz.com :

__init__()função. Essa função especial é chamada sempre que um novo objeto dessa classe é instanciado.

Esse tipo de função também é chamado de construtores na Programação Orientada a Objetos (OOP). Nós normalmente o usamos para inicializar todas as variáveis.

Por exemplo:

class example:
    list=[] #This is class variable shared by all instance
    def __init__(self):
        self.list = [] #This is instance variable referred to specific instance
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.