Python: ¿Cómo funciona realmente la herencia de__slots__ en subclases?
inheritance subclass (5)
Python: ¿Cómo funciona realmente la herencia de
__slots__
en subclases?Estoy totalmente confundido por los ítems 1º y 6º, porque parecen contradecirse entre sí.
Esos elementos en realidad no se contradicen entre sí. La primera se refiere a las subclases de las clases que no implementan __slots__
, la segunda se refiere a las subclases de clases que implementan __slots__
.
Subclases de clases que no implementan __slots__
Cada vez me doy más cuenta de que, a pesar de que los documentos de Python tienen (con razón) fama, no son perfectos, especialmente con respecto a las características menos utilizadas del lenguaje. Yo modificaría los docs siguiente manera:
Al heredar de una clase sin
__slots__
, el atributo__dict__
de esa clase siempre será accesible, por lo que una definición de.__slots__
en la subclase no tiene sentido
__slots__
todavía es significativo para dicha clase. Documenta los nombres esperados de los atributos de la clase. También crea espacios para esos atributos: obtendrán búsquedas más rápidas y usarán menos espacio. Solo permite otros atributos, que se asignarán al __dict__
.
Este cambio ha sido aceptado y ahora se encuentra en la última documentación .
Aquí hay un ejemplo:
class Foo:
"""instances have __dict__"""
class Bar(Foo):
__slots__ = ''foo'', ''bar''
Bar
no solo tiene las máquinas tragamonedas que declara, también tiene las máquinas tragamonedas de Foo, que incluyen __dict__
:
>>> b = Bar()
>>> b.foo = ''foo''
>>> b.quux = ''quux''
>>> vars(b)
{''quux'': ''quux''}
>>> b.foo
''foo''
Subclases de clases que implementan __slots__
La acción de una declaración
__slots__
se limita a la clase donde está definida. Como resultado, las subclases tendrán un__dict__
menos que también definan__slots__
(que solo debe contener nombres de ranuras adicionales).
Bueno, eso tampoco está del todo bien. La acción de una declaración __slots__
no está completamente limitada a la clase donde está definida. Pueden tener implicaciones para la herencia múltiple, por ejemplo.
Yo cambiaría eso a:
Para las clases en un árbol de herencia que define
__slots__
, las subclases tendrán un__dict__
menos que también definan__slots__
(que solo debe contener nombres de ranuras adicionales).
Lo actualicé para leer:
La acción de una declaración
__slots__
no se limita a la clase donde está definida.__slots__
declarados en los padres están disponibles en clases para niños. Sin embargo, las subclases de niños recibirán un__dict__
y__weakref__
menos que también definan__slots__
(que solo debe contener nombres de ranuras adicionales).
Aquí hay un ejemplo:
class Foo:
__slots__ = ''foo''
class Bar(Foo):
"""instances get __dict__ and __weakref__"""
Y vemos que una subclase de una clase ranurada puede usar las ranuras:
>>> b = Bar()
>>> b.foo = ''foo''
>>> b.bar = ''bar''
>>> vars(b)
{''bar'': ''bar''}
>>> b.foo
''foo''
(Para más sobre __slots__
, vea mi respuesta aquí .)
En la sección de referencia del modelo de datos de Python en las máquinas tragamonedas hay una lista de notas sobre el uso de __slots__
. Estoy totalmente confundido por los ítems 1º y 6º, porque parecen contradecirse entre sí.
Primer elemento:
- Al heredar de una clase sin
__slots__
, el atributo__dict__
de esa clase siempre será accesible, por lo que una definición de__slots__
en la subclase no tiene sentido.
Sexto artículo:
- La acción de una declaración
__slots__
se limita a la clase donde está definida. Como resultado, las subclases tendrán un__dict__
menos que también definan__slots__
(que solo debe contener nombres de ranuras adicionales).
Me parece que estos elementos podrían estar mejor redactados o mostrados a través del código, pero he estado tratando de entender esto y todavía estoy confundido. Entiendo cómo se supone que __slots__
se deben usar , y estoy tratando de comprender mejor cómo funcionan.
La pregunta:
¿Puede alguien explicarme en lenguaje sencillo cuáles son las condiciones para la herencia de las máquinas tragamonedas al crear subclases?
(Los ejemplos de código simple serían útiles pero no necesarios).
Como otros han mencionado, la única razón para definir __slots__
es guardar algo de memoria, cuando tienes objetos simples con un conjunto de atributos predefinidos y no quieres que cada uno lleve un diccionario. Esto es significativo solo para las clases de las que planea tener muchas instancias, por supuesto.
El ahorro puede no ser inmediatamente obvio, considere ...:
>>> class NoSlots(object): pass
...
>>> n = NoSlots()
>>> class WithSlots(object): __slots__ = ''a'', ''b'', ''c''
...
>>> w = WithSlots()
>>> n.a = n.b = n.c = 23
>>> w.a = w.b = w.c = 23
>>> sys.getsizeof(n)
32
>>> sys.getsizeof(w)
36
A partir de esto, parece que el tamaño de las tragamonedas es más grande que el tamaño sin ranuras. Pero eso es un error, porque sys.getsizeof
no considera "contenido de objetos" como el diccionario:
>>> sys.getsizeof(n.__dict__)
140
Como el dict solo toma 140 bytes, claramente el objeto n
"32 bytes" se supone que toma n no está considerando todo lo que está involucrado en cada instancia. Puede hacer un mejor trabajo con extensiones de terceros, como por ejemplo, el pympler :
>>> import pympler.asizeof
>>> pympler.asizeof.asizeof(w)
96
>>> pympler.asizeof.asizeof(n)
288
Esto muestra mucho más claramente la huella de memoria que se guarda con __slots__
: para un objeto simple como este caso, es un poco menos de 200 bytes, casi 2/3 de la huella total del objeto. Ahora, dado que en la actualidad, un megabyte más o menos no importa demasiado para la mayoría de las aplicaciones, esto también te dice que __slots__
no vale la pena si solo tienes unos pocos miles de instancias a la vez: Sin embargo, para millones de instancias, sin duda hace una diferencia muy importante. También puede obtener una aceleración microscópica (en parte debido a un mejor uso de la memoria caché para objetos pequeños con __slots__
):
$ python -mtimeit -s''class S(object): __slots__="x","y"'' -s''s=S(); s.x=s.y=23'' ''s.x''
10000000 loops, best of 3: 0.37 usec per loop
$ python -mtimeit -s''class S(object): pass'' -s''s=S(); s.x=s.y=23'' ''s.x''
1000000 loops, best of 3: 0.604 usec per loop
$ python -mtimeit -s''class S(object): __slots__="x","y"'' -s''s=S(); s.x=s.y=23'' ''s.x=45''
1000000 loops, best of 3: 0.28 usec per loop
$ python -mtimeit -s''class S(object): pass'' -s''s=S(); s.x=s.y=23'' ''s.x=45''
1000000 loops, best of 3: 0.332 usec per loop
pero esto es algo dependiente de la versión de Python (estos son los números que muestro repetidamente con 2.5; con 2.6, veo una mayor ventaja relativa para __slots__
para establecer un atributo, pero ninguno en absoluto, de hecho una pequeña desventaja , para obtenerlo ) .
Ahora, con respecto a la herencia: para que una instancia sea dict-less, todas las clases de su cadena de herencia también deben tener instancias sin dict. Las clases con instancias sin __slots__
son aquellas que definen __slots__
, más la mayoría de los tipos incorporados (tipos incorporados cuyas instancias tienen dicts son aquellas en cuyas instancias puede establecer atributos arbitrarios, como funciones). Las superposiciones en los nombres de las tragamonedas no están prohibidas, pero son inútiles y desperdician algo de memoria, ya que las máquinas tragamonedas se heredan:
>>> class A(object): __slots__=''a''
...
>>> class AB(A): __slots__=''b''
...
>>> ab=AB()
>>> ab.a = ab.b = 23
>>>
como ve, puede establecer el atributo a
en una instancia AB
- AB
solo define la ranura b
, pero hereda la ranura a
desde A
No está prohibido repetir el espacio heredado:
>>> class ABRed(A): __slots__=''a'',''b''
...
>>> abr=ABRed()
>>> abr.a = abr.b = 23
pero pierde un poco de memoria:
>>> pympler.asizeof.asizeof(ab)
88
>>> pympler.asizeof.asizeof(abr)
96
así que realmente no hay razón para hacerlo.
De la respuesta que vinculó:
El uso apropiado de
__slots__
es para ahorrar espacio en los objetos. En lugar de tener un dict dinámico ...
"Al heredar de una clase sin __slots__
, el atributo __dict__
de esa clase siempre será accesible", por lo que agregar sus propios __slots__
no puede evitar que los objetos tengan un __dict__
y no puede ahorrar espacio.
El poco sobre __slots__
no heredado es un poco obtuso. Recuerde que es un atributo mágico y no se comporta como otros atributos, luego vuelva a leerlo diciendo que este comportamiento de las máquinas tragamonedas mágicas no se hereda. (Eso es todo lo que hay que hacer).
Mi entendimiento es el siguiente:
la clase
X
no tiene__dict__
<------->
claseX
y todas sus superclases tienen__slots__
especificadosen este caso, las ranuras reales de la clase se componen de la unión de declaraciones
__slots__
paraX
y sus superclases; el comportamiento no está definido (y se convertirá en un error) si esta unión no es disjunta
class WithSlots(object):
__slots__ = "a_slot"
class NoSlots(object): # This class has __dict__
pass
Primer elemento
class A(NoSlots): # even though A has __slots__, it inherits __dict__
__slots__ = "a_slot" # from NoSlots, therefore __slots__ has no effect
Sexto elemento
class B(WithSlots): # This class has no __dict__
__slots__ = "some_slot"
class C(WithSlots): # This class has __dict__, because it doesn''t
pass # specify __slots__ even though the superclass does.
Probablemente no necesite usar __slots__
en el futuro cercano. Solo tiene la intención de ahorrar memoria a costa de cierta flexibilidad. A menos que tengas decenas de miles de objetos, no importará.