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.attr
exibe '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?
python
attributes
language-design
Smashery
fonte
fonte
object
tipo é imutável e novos atributos não podem ser adicionados? Parece que faria mais sentido.object
instâncias de classe, não emobject
classe.Respostas:
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
object
que não transportar cerca de um__dict__
- se isso acontecesse, antes que o problema da dependência circular horrível (desdedict
, como tudo o mais, herda deobject
;-), 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
pympler
projeto (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
int
ocupassem 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, umadint
instância pode ter atributos arbitrários, mas você paga um custo de espaço considerável por essa flexibilidade.E daí se você quisesse
int
s com apenas um atributo extrafoobar
...? É 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 doisint
s, um oself
e um oself.foobar
- o segundo pode ser reatribuído), mas certamente muito melhor do que adint
.Quando a classe tem o
__slots__
atributo especial (uma sequência de strings), aclass
instrução (mais precisamente, a metaclasse padrãotype
) 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).
fonte
__slots__
não funcionam com os tipos de comprimento variável, tal comostr
,tuple
e em Python 3 tambémint
.__dict__
, sua classe deve ter um__slot__
atributo?Sub
o__dict__
atributo e o objeto não, sendo queSub
herdam deobject
, como esse atributo (e outros semelhantes__module__
) é adicionado na herança? Pode ser que esta seja uma pergunta nova__dict__
só é criado na primeira vez que é necessário, portanto, a situação do custo de memória não é tão simples quanto aasizeof
saída faz parecer. (asizeof
nã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 .Como outros respondentes disseram, um
object
não tem a__dict__
.object
é a classe base de todos os tipos, incluindoint
oustr
. Assim, tudo o que for fornecido porobject
será 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.SimpleNamespace
para isso.fonte
É 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.
fonte
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".fonte
É 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.
fonte
__dict__
, pelas razões apresentadas por Alex Martelli.https://docs.python.org/3/library/functions.html#object :
fonte
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.
fonte