Pregunta

En el Python modelo de datos de la sección de referencia en las ranuras hay una lista de notas sobre el uso de __slots__.Estoy completamente confundido por el 1 y 6 de los artículos, porque parece ser que contradicen el uno al otro.

Primer elemento:

  • Cuando se hereda de una clase sin __slots__, el __dict__ atributo de que clase siempre se accesible, por lo que un __slots__ definición en la subclase es carece de sentido.

Sexto punto:

  • La acción de un __slots__ la declaración se limita a la clase donde está definido.Como resultado, subclases tendrá un __dict__ a menos que también definir __slots__ (que sólo debe contener los nombres de cualquier ranuras adicionales).

A mí me parece que estos elementos podría ser mejor redactado o muestre a través de código, pero he estado tratando de envolver mi cabeza alrededor de esto y todavía estoy llegando a confundirse.Yo no entiendo cómo __slots__ son supone que se utiliza, y estoy tratando de obtener una mejor comprensión de cómo funcionan.

La Pregunta:

Puede por favor alguien que me explique en lenguaje claro cuáles son las condiciones para la herencia de las ranuras cuando subclases?

(Simples ejemplos de código sería útil pero no necesario.)

¿Fue útil?

Solución

Como otros han mencionado, la única razón para definir __slots__ es ahorrar algo de memoria, cuando tiene objetos simples con un conjunto predefinido de atributos y no desea que cada uno lleve consigo un diccionario. Esto es significativo solo para las clases de las cuales planea tener muchas instancias, por supuesto.

Los ahorros pueden no ser inmediatamente obvios; 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

¡De esto, parecería que el tamaño con ranuras es más grande que el tamaño sin ranuras! Pero eso es un error, porque sys.getsizeof no considera & Quot; contenido del objeto & Quot; como el diccionario:

>>> sys.getsizeof(n.__dict__)
140

Dado que el dict solo toma 140 bytes, claramente el " 32 bytes " Se supone que el objeto n toma no considera todo lo que está involucrado en cada instancia. Puede hacer un mejor trabajo con extensiones de terceros como pympler :

>>> import pympler.asizeof
>>> pympler.asizeof.asizeof(w)
96
>>> pympler.asizeof.asizeof(n)
288

Esto muestra mucho más claramente la huella de memoria guardada por a: 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 estos días un megabyte más o menos realmente no importa tanto para la mayoría de las aplicaciones, esto también le dice que AB no vale la pena si va a tener solo unos pocos miles de instancias por vez tiempo, sin embargo, para millones de instancias, seguro que hace una diferencia muy importante. También puede obtener una aceleración microscópica (en parte debido al mejor uso de caché para objetos pequeños con b):

$ 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 mido repetidamente con 2.5; con 2.6, veo una ventaja relativa mayor a A para configurar un atributo, pero ninguno en todo, de hecho una pequeña ventaja de dis , para obtener eso).

Ahora, con respecto a la herencia: para que una instancia no tenga dict, todas clases en su cadena de herencia también deben tener instancias sin dict. Las clases con instancias sin dict son aquellas que definen <=>, más la mayoría de los tipos incorporados (los tipos incorporados cuyas instancias tienen dictados son aquellos en cuyas instancias puede establecer atributos arbitrarios, como funciones). Las superposiciones en los nombres de las ranuras no están prohibidas, pero son inútiles y desperdician algo de memoria, ya que las ranuras se heredan:

>>> class A(object): __slots__='a'
... 
>>> class AB(A): __slots__='b'
... 
>>> ab=AB()
>>> ab.a = ab.b = 23
>>> 

como puede ver, puede establecer el atributo <=> en una instancia de <=> - <=> solo define el espacio <=>, pero hereda el espacio <=> de <=>. No está prohibido repetir el espacio heredado:

>>> class ABRed(A): __slots__='a','b'
... 
>>> abr=ABRed()
>>> abr.a = abr.b = 23

pero desperdicia un poco de memoria:

>>> pympler.asizeof.asizeof(ab)
88
>>> pympler.asizeof.asizeof(abr)
96

así que realmente no hay razón para hacerlo.

Otros consejos

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 pretende ahorrar memoria a costa de cierta flexibilidad. A menos que tenga decenas de miles de objetos, no importará.

  

Python: ¿Cómo funciona realmente la herencia de __slots__ en las subclases?

     

Estoy completamente confundido por los ítems primero y sexto, porque parecen estar contradiciéndose entre sí.

Esos elementos no se contradicen entre sí. El primero se refiere a las subclases de clases que no implementan __dict__, el segundo se refiere a las subclases de clases que implementan Bar.

Subclases de clases que no implementan __weakref__

Cada vez soy más consciente de que, aunque los documentos de Python tienen (correctamente) fama de ser, no son perfectos, especialmente con respecto a las características menos utilizadas del lenguaje. Alteraría los docs de la siguiente manera:

  

Cuando se hereda de una clase sin <=>, el atributo <=>   de esa clase siempre estará accesible , por lo que una definición <=> en   la subclase no tiene sentido .

<=> sigue siendo 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. Simplemente permite otros atributos, que se asignarán a <=>.

Este cambio ha sido aceptado y ahora está en última documentación .

Aquí hay un ejemplo:

class Foo: 
    """instances have __dict__"""

class Bar(Foo):
    __slots__ = 'foo', 'bar'

<=> no solo tiene las ranuras que declara, también tiene las ranuras de Foo, que incluyen <=>:

>>> b = Bar()
>>> b.foo = 'foo'
>>> b.quux = 'quux'
>>> vars(b)
{'quux': 'quux'}
>>> b.foo
'foo'

Subclases de clases que hacen implementan <=>

  

La acción de una declaración <=> se limita a la clase donde   se define. Como resultado, las subclases tendrán un <=> a menos que   también defina <=> (que solo debe contener nombres de cualquier adicional   ranuras).

Bueno, eso tampoco está del todo bien. La acción de una declaración <=> es no completamente limitada a la clase donde se define. Pueden tener implicaciones para la herencia múltiple, por ejemplo.

Cambiaría eso a:

  

Para las clases en un árbol de herencia que define <=>, las subclases tendrán un <=> a menos que   también defina <=> (que solo debe contener nombres de cualquier adicional   ranuras).

Realmente lo actualicé para leer:

  

La acción de una declaración <=> no se limita a la clase   donde se define <=> declarado en padres están disponibles en   Clases para niños. Sin embargo, las subclases secundarias obtendrán un <=> y   <=> a menos que también definan <=> (que solo debe contener nombres de espacios 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 con ranuras puede usar las ranuras:

>>> b = Bar()
>>> b.foo = 'foo'
>>> b.bar = 'bar'
>>> vars(b)
{'bar': 'bar'}
>>> b.foo
'foo'

(Para más información sobre <=>, vea mi respuesta aquí .)

De la respuesta que vinculó:

  

El uso apropiado de __slots__ es ahorrar espacio en los objetos. En lugar de tener un dict dinámico ...

" Al heredar de una clase sin __dict__, el atributo <=> de esa clase siempre estará accesible " ;, por lo que agregar su propio <=> no puede evitar que los objetos tengan un < =>, y no puede ahorrar espacio.

El bit sobre <=> no se hereda es un poco obtuso. Recuerde que es un atributo mágico y no se comporta como otros atributos, luego vuelva a leer eso como diciendo que este comportamiento de las tragamonedas mágicas no se hereda. (Eso es todo lo que hay que hacer).

Mi entendimiento es como sigue:

  • clase X no tiene __dict__ <-------> clase X y sus superclases todos tienen __slots__ especificado

  • en este caso, el real ranuras de la clase se compone de la unión de __slots__ las declaraciones de X y sus superclases;el comportamiento no está definido (y se convertirá en un error) si esta unión no es distinto

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top