Não é possível definir atributos na instância da classe “objeto”

87

Então, eu estava brincando com Python enquanto respondia a esta pergunta e descobri que isso não é válido:

o = object()
o.attr = 'hello'

devido a um AttributeError: 'object' object has no attribute 'attr'. No entanto, com qualquer classe herdada de objeto, é válido:

class Sub(object):
    pass

s = Sub()
s.attr = 'hello'

A impressão s.attrexibe 'olá' conforme o esperado. Por que isso acontece? O que na especificação da linguagem Python especifica que você não pode atribuir atributos a objetos vanilla?

Smashery
fonte
Pura suposição: o objecttipo é imutável e novos atributos não podem ser adicionados? Parece que faria mais sentido.
Chris Lutz,
2
@ S.Lott: Veja a primeira linha desta questão. Pura curiosidade.
Smashery
3
Seu título é enganoso, você está tentando definir atributos em objectinstâncias de classe, não em objectclasse.
dhill

Respostas:

129

Para suportar a atribuição arbitrária de atributos, um objeto precisa de um __dict__: um dict associado ao objeto, onde atributos arbitrários podem ser armazenados. Caso contrário, não há onde colocar novos atributos.

Uma instância de objectque não transportar cerca de um __dict__- se isso acontecesse, antes que o problema da dependência circular horrível (desde dict, como tudo o mais, herda de object;-), isso sela cada objeto em Python com um dicionário, o que significaria uma sobrecarga de muitos bytes por objeto que atualmente não tem ou precisa de um dict (essencialmente, todos os objetos que não têm atributos atribuíveis arbitrariamente não têm ou precisam de um dict).

Por exemplo, usando o excelente pymplerprojeto (você pode obtê-lo via svn a partir daqui ), podemos fazer algumas medições ...:

>>> from pympler import asizeof
>>> asizeof.asizeof({})
144
>>> asizeof.asizeof(23)
16

Você não gostaria que todos intocupassem 144 bytes em vez de apenas 16, certo? -)

Agora, quando você faz uma classe (herdando de qualquer coisa), as coisas mudam ...:

>>> class dint(int): pass
... 
>>> asizeof.asizeof(dint(23))
184

... o agora __dict__ é adicionado (além de um pouco mais de sobrecarga) - portanto, uma dintinstância pode ter atributos arbitrários, mas você paga um custo de espaço considerável por essa flexibilidade.

E daí se você quisesse ints com apenas um atributo extra foobar...? É uma necessidade rara, mas Python oferece um mecanismo especial para o propósito ...

>>> class fint(int):
...   __slots__ = 'foobar',
...   def __init__(self, x): self.foobar=x+100
... 
>>> asizeof.asizeof(fint(23))
80

... não muito pequenos como um int, você mente! (ou mesmo os dois ints, um o selfe um o self.foobar- o segundo pode ser reatribuído), mas certamente muito melhor do que a dint.

Quando a classe tem o __slots__atributo especial (uma sequência de strings), a classinstrução (mais precisamente, a metaclasse padrão type) não equipa cada instância dessa classe com um __dict__(e, portanto, a capacidade de ter atributos arbitrários), apenas um , conjunto rígido de "slots" (basicamente lugares em que cada um pode conter uma referência a algum objeto) com os nomes dados.

Em troca da flexibilidade perdida, você ganha um monte de bytes por exemplo (provavelmente significativas somente se você tem zilhões de casos divertir ao redor, mas, não são casos de uso para isso).

Alex Martelli
fonte
5
Isso explica como o mecanismo é implementado, mas não explica por que ele é implementado dessa forma. Posso pensar em pelo menos duas ou três maneiras de implementar a adição de dict dinamicamente, o que não terá a desvantagem de sobrecarga, mas adicionará um pouco de simplicidade.
Георги Кременлиев
Note-se que não vazio __slots__não funcionam com os tipos de comprimento variável, tal como str, tuplee em Python 3 também int.
arekolek de
É uma ótima explicação, mas ainda não responde por que (ou como) Subo __dict__atributo e o objeto não, sendo que Subherdam de object, como esse atributo (e outros semelhantes __module__) é adicionado na herança? Pode ser que esta seja uma pergunta nova
Rodrigo E. Principe
2
Um objeto __dict__só é criado na primeira vez que é necessário, portanto, a situação do custo de memória não é tão simples quanto a asizeofsaída faz parecer. ( asizeofnão sabe como evitar a __dict__materialização.) Você pode ver o dict não sendo materializado até que seja necessário neste exemplo e pode ver um dos caminhos de código responsáveis ​​pela __dict__materialização aqui .
usuário2357112 suporta Monica
17

Como outros respondentes disseram, um objectnão tem a __dict__. objecté a classe base de todos os tipos, incluindo intou str. Assim, tudo o que for fornecido por objectserá um fardo para eles também. Mesmo algo tão simples como um opcional __dict__ precisaria de um ponteiro extra para cada valor; isso desperdiçaria 4-8 bytes adicionais de memória para cada objeto no sistema, para uma utilidade muito limitada.


Em vez de fazer uma instância de uma classe fictícia, no Python 3.3+, você pode (e deve) usar types.SimpleNamespacepara isso.

Antti Haapala
fonte
4

É simplesmente devido à otimização.

Dictos são relativamente grandes.

>>> import sys
>>> sys.getsizeof((lambda:1).__dict__)
140

A maioria (talvez todas) as classes definidas em C não possuem um dicionário para otimização.

Se você olhar o código-fonte , verá que existem muitas verificações para ver se o objeto tem um dicionário ou não.

Desconhecido
fonte
1

Então, investigando minha própria pergunta, descobri o seguinte sobre a linguagem Python: você pode herdar de coisas como int e você vê o mesmo comportamento:

>>> class MyInt(int):
       pass

>>> x = MyInt()
>>> print x
0
>>> x.hello = 4
>>> print x.hello
4
>>> x = x + 1
>>> print x
1
>>> print x.hello
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
AttributeError: 'int' object has no attribute 'hello'

Presumo que o erro no final seja porque a função add retorna um int, então eu teria que substituir funções como __add__e tal para manter meus atributos personalizados. Mas tudo isso agora faz sentido para mim (eu acho), quando penso em "objeto" como "int".

Smashery
fonte
0

É porque o objeto é um "tipo", não uma classe. Em geral, todas as classes definidas em extensões C (como todos os tipos de dados integrados e coisas como matrizes numpy) não permitem a adição de atributos arbitrários.

Ryan
fonte
Mas object () é um objeto, assim como Sub () é um objeto. Meu entendimento é que tanto s quanto o são objetos. Então, qual é a diferença fundamental entre s e o? Será que um é um tipo instanciado e o outro é uma classe instanciada?
Smashery,
Bingo. Esse é exatamente o problema.
Ryan,
1
No Python 3, a diferença entre tipos e classes não existe realmente. Portanto, "tipo" e "classe" agora são sinônimos. Mas você ainda não pode adicionar atributos a essas classes que não têm um __dict__, pelas razões apresentadas por Alex Martelli.
PM 2Ring
-2

Esta é (IMO) uma das limitações fundamentais do Python - você não pode reabrir classes. Eu acredito que o problema real, entretanto, é causado pelo fato de que as classes implementadas em C não podem ser modificadas em tempo de execução ... as subclasses podem, mas não as classes básicas.

Peter
fonte