python inheritance subclass slots

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__ <-------> clase X y todas sus superclases tienen __slots__ especificados

  • en este caso, las ranuras reales de la clase se componen de la unión de declaraciones __slots__ para X 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á.