oriented - python inheritance
No se pueden establecer los atributos de la clase de objeto (6)
Entonces, estaba jugando con Python mientras respondía esta pregunta , y descubrí que esto no es válido:
o = object()
o.attr = ''hello''
debido a un AttributeError: ''object'' object has no attribute ''attr''
. Sin embargo, con cualquier clase heredada de un objeto, es válida:
class Sub(object):
pass
s = Sub()
s.attr = ''hello''
Al imprimir en forma s.attr
muestra ''hola'' como se esperaba. ¿Por qué es este el caso? ¿Qué especificación del lenguaje de Python especifica que no se pueden asignar atributos a los objetos de vainilla?
Como han dicho otros contestadores, un object
no tiene un __dict__
. object
es la clase base de todos los tipos, incluidos int
o str
. Por lo tanto, todo lo que proporciona el object
será una carga para ellos también. Incluso algo tan simple como un __dict__
opcional necesitaría un puntero adicional para cada valor; esto desperdiciaría 4-8 bytes de memoria adicionales para cada objeto en el sistema, para una utilidad muy limitada.
En lugar de hacer una instancia de una clase ficticia, en Python 3.3+, puede (y debería) usar types.SimpleNamespace
para esto.
Entonces, al investigar mi propia pregunta, descubrí esto sobre el lenguaje Python: puedes heredar cosas como int, y ves el mismo comportamiento:
>>> 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''
Supongo que el error al final se debe a que la función agregar devuelve un int, por lo que tendría que anular funciones como __add__
y tal para retener mis atributos personalizados. Pero todo esto ahora tiene sentido para mí (creo), cuando pienso en "objeto" como "int".
Es porque el objeto es un "tipo", no una clase. En general, todas las clases que se definen en extensiones C (como todos los tipos de datos incorporados y cosas como matrices numpy) no permiten la adición de atributos arbitrarios.
Es simplemente debido a la optimización.
Los dicts son relativamente grandes.
>>> import sys
>>> sys.getsizeof((lambda:1).__dict__)
140
La mayoría (tal vez todas) las clases que se definen en C no tienen un dict para la optimización.
Si miras el código fuente verás que hay muchos controles para ver si el objeto tiene un dict o no.
Esta es (IMO) una de las limitaciones fundamentales de Python: no puede volver a abrir clases. Creo que el problema real, sin embargo, es causado por el hecho de que las clases implementadas en C no se pueden modificar en el tiempo de ejecución ... las subclases pueden, pero no las clases base.
Para admitir la asignación de atributos arbitraria, un objeto necesita un __dict__
: un dict asociado con el objeto, donde se pueden almacenar atributos arbitrarios. De lo contrario, no hay ningún lugar para poner nuevos atributos.
Una instancia de object
no lleva consigo un __dict__
- si lo hizo, antes del horrible problema de dependencia circular (ya que dict
, como casi todo lo demás, hereda del object
;-), esto ensillaría cada objeto en Python con un dict, que significaría una sobrecarga de muchos bytes por objeto que actualmente no tiene o no necesita un dict (en esencia, todos los objetos que no tienen atributos asignables arbitrariamente no tienen o no necesitan un dict).
Por ejemplo, usando el excelente proyecto de pympler
(puedes obtenerlo a través de svn desde here ), podemos hacer algunas mediciones ...:
>>> from pympler import asizeof
>>> asizeof.asizeof({})
144
>>> asizeof.asizeof(23)
16
No querría que cada int
tome 144 bytes en lugar de solo 16, ¿verdad? -)
Ahora, cuando haces una clase (heredando de lo que sea), las cosas cambian ...:
>>> class dint(int): pass
...
>>> asizeof.asizeof(dint(23))
184
... el __dict__
ahora se agrega (más, un poco más por encima), por lo que una instancia dint
puede tener atributos arbitrarios, pero usted paga un costo bastante grande por esa flexibilidad.
Entonces, ¿qué int
si quieres int
s con solo un atributo adicional foobar
...? Es una necesidad extraña, pero Python ofrece un mecanismo especial para este propósito ...
>>> class fint(int):
... __slots__ = ''foobar'',
... def __init__(self, x): self.foobar=x+100
...
>>> asizeof.asizeof(fint(23))
80
... no es tan pequeño como un int
, ¡fíjate! (o incluso los dos int
s, uno el uno self
y otro el self.foobar
- el segundo puede ser reasignado), pero seguramente mucho mejor que un dint
.
Cuando la clase tiene el __slots__
especial __slots__
(una secuencia de cadenas), la instrucción de class
(más precisamente, la metaclase predeterminada, type
) no equipa cada instancia de esa clase con un __dict__
(y por lo tanto la capacidad de tener atributos arbitrarios) , solo un conjunto finito y rígido de "slots" (básicamente lugares que pueden contener una referencia a algún objeto) con los nombres dados.
A cambio de la flexibilidad perdida, obtienes una gran cantidad de bytes por instancia (probablemente significativo solo si tienes miles de millones de casos dando vueltas, pero hay casos de uso para eso).