Uso de __slots__?

¿Cuál es el propósito de __slots__ en Python, especialmente con respecto a cuándo querría usarlo y cuándo no?

En Python, ¿cuál es el propósito de __slots__ y cuáles son los casos que se deben evitar?

TLDR:

El atributo especial __slots__ permite __slots__ explícitamente qué atributos de instancia espera que tengan sus instancias de objeto, con los resultados esperados:

  1. Acceso a atributos más rápido .
  2. Ahorro de espacio en memoria.

El ahorro de espacio es de

  1. Almacenar referencias de valor en ranuras en lugar de __dict__ .
  2. __dict__ __weakref__ __dict__ y __weakref__ si las clases de los padres las __slots__ y usted declara __slots__ .

Advertencias rápidas

Advertencia: solo debe declarar un espacio en particular una vez en un árbol de herencia. Por ejemplo:

 class Base: __slots__ = 'foo', 'bar' class Right(Base): __slots__ = 'baz', class Wrong(Base): __slots__ = 'foo', 'bar', 'baz' # redundant foo and bar 

Python no se opone cuando te equivocas (probablemente debería), los problemas podrían no manifestarse de otra manera, pero tus objetos ocuparán más espacio del que deberían.

 >>> from sys import getsizeof >>> getsizeof(Right()), getsizeof(Wrong()) (64, 80) 

La mayor advertencia es para la herencia múltiple: no se pueden combinar varias “clases primarias con espacios no vacíos”.

Para adaptarse a esta restricción, siga las mejores prácticas: factorice la abstracción de todos menos uno o de todos los padres, de los cuales su clase concreta, respectivamente, y su nueva clase concreta heredarán de forma conjunta. biblioteca estándar).

Consulte la sección sobre herencia múltiple a continuación para ver un ejemplo.

Requisitos:

  • Para que los atributos nombrados en __slots__ se almacenen en ranuras en lugar de __dict__ , una clase debe heredar del object .

  • Para evitar la creación de un __dict__ , debe heredar del object y todas las clases en la herencia deben declarar __slots__ y ninguna de ellas puede tener una entrada '__dict__' .

Hay muchos detalles si deseas seguir leyendo.

¿Por qué usar __slots__ : acceso a atributos más rápido?

El creador de Python, Guido van Rossum, afirma que en realidad creó __slots__ para un acceso más rápido a los atributos.

Es trivial demostrar un acceso más rápido y significativamente significativo:

 import timeit class Foo(object): __slots__ = 'foo', class Bar(object): pass slotted = Foo() not_slotted = Bar() def get_set_delete_fn(obj): def get_set_delete(): obj.foo = 'foo' obj.foo del obj.foo return get_set_delete 

y

 >>> min(timeit.repeat(get_set_delete_fn(slotted))) 0.2846834529991611 >>> min(timeit.repeat(get_set_delete_fn(not_slotted))) 0.3664822799983085 

El acceso ranurado es casi un 30% más rápido en Python 3.5 en Ubuntu.

 >>> 0.3664822799983085 / 0.2846834529991611 1.2873325658284342 

En Python 2 en Windows lo he medido alrededor de un 15% más rápido.

Por qué usar __slots__ : Memory Savings

Otro propósito de __slots__ es reducir el espacio en la memoria que ocupa cada instancia de objeto.

Mi propia contribución a la documentación establece claramente las razones detrás de esto :

El espacio guardado sobre el uso de __dict__ puede ser significativo.

SQLAlchemy atribuye muchos ahorros de memoria a __slots__ .

Para verificar esto, usar la distribución Anaconda de Python 2.7 en Ubuntu Linux, con guppy.hpy (también conocido como heapy) y sys.getsizeof , el tamaño de una instancia de clase sin __slots__ declarado, y nada más, es de 64 bytes. Eso no incluye el __dict__ . Gracias de nuevo a Python por su perezosa evaluación, aparentemente el __dict__ se hace __dict__ hasta que se hace referencia, pero las clases sin datos generalmente son inútiles. Cuando se llama a la existencia, el atributo __dict__ tiene un mínimo de 280 bytes adicionales.

En contraste, una instancia de clase con __slots__ declarada como () (sin datos) tiene solo 16 bytes, y 56 bytes totales con un elemento en las ranuras, 64 con dos.

Para Python de 64 bits, ilustro el consumo de memoria en bytes en Python 2.7 y 3.6, para __slots__ y __dict__ (no hay ranuras definidas) para cada punto donde el dict crece en 3.6 (excepto para los atributos 0, 1 y 2):

  Python 2.7 Python 3.6 attrs __slots__ __dict__* __slots__ __dict__* | *(no slots defined) none 16 56 + 272† 16 56 + 112† | †if __dict__ referenced one 48 56 + 272 48 56 + 112 two 56 56 + 272 56 56 + 112 six 88 56 + 1040 88 56 + 152 11 128 56 + 1040 128 56 + 240 22 216 56 + 3344 216 56 + 408 43 384 56 + 3344 384 56 + 752 

Por lo tanto, a pesar de los dictados más pequeños en Python 3, vemos cómo __slots__ escala muy bien para que las instancias nos ahorren memoria, y esa es una de las razones principales por las que querría usar __slots__ .

Solo para completar mis notas, tenga en cuenta que hay un costo por ranura en el espacio de nombres de la clase de 64 bytes en Python 2, y 72 bytes en Python 3, porque las ranuras usan descriptores de datos como propiedades, llamadas “miembros”.

 >>> Foo.foo  >>> type(Foo.foo)  >>> getsizeof(Foo.foo) 72 

Demostración de __slots__ :

Para denegar la creación de un __dict__ , debe crear una subclase de object :

 class Base(object): __slots__ = () 

ahora:

 >>> b = Base() >>> ba = 'a' Traceback (most recent call last): File "", line 1, in  ba = 'a' AttributeError: 'Base' object has no attribute 'a' 

O subclase de otra clase que define __slots__

 class Child(Base): __slots__ = ('a',) 

y ahora:

 c = Child() ca = 'a' 

pero:

 >>> cb = 'b' Traceback (most recent call last): File "", line 1, in  cb = 'b' AttributeError: 'Child' object has no attribute 'b' 

Para permitir la creación de __dict__ mientras se subclasifican objetos ranurados, simplemente agregue '__dict__' a los __slots__ (tenga en cuenta que las ranuras están ordenadas y no debe repetir las que ya están en las clases primarias):

 class SlottedWithDict(Child): __slots__ = ('__dict__', 'b') swd = SlottedWithDict() swd.a = 'a' swd.b = 'b' swd.c = 'c' 

y

 >>> swd.__dict__ {'c': 'c'} 

O ni siquiera necesita declarar __slots__ en su subclase, y seguirá usando las ranuras de los padres, pero sin restringir la creación de un __dict__ :

 class NoSlots(Child): pass ns = NoSlots() ns.a = 'a' ns.b = 'b' 

Y:

 >>> ns.__dict__ {'b': 'b'} 

Sin embargo, __slots__ puede causar problemas por herencia múltiple:

 class BaseA(object): __slots__ = ('a',) class BaseB(object): __slots__ = ('b',) 

Debido a que la creación de una clase secundaria de padres con dos ranuras no vacías falla:

 >>> class Child(BaseA, BaseB): __slots__ = () Traceback (most recent call last): File "", line 1, in  class Child(BaseA, BaseB): __slots__ = () TypeError: Error when calling the metaclass bases multiple bases have instance lay-out conflict 

Si se encuentra con este problema, puede simplemente quitar __slots__ de los padres, o si tiene el control de los padres, darles espacios vacíos o refactorizar las abstracciones:

 from abc import ABC class AbstractA(ABC): __slots__ = () class BaseA(AbstractA): __slots__ = ('a',) class AbstractB(ABC): __slots__ = () class BaseB(AbstractB): __slots__ = ('b',) class Child(AbstractA, AbstractB): __slots__ = ('a', 'b') c = Child() # no problem! 

Agregue '__dict__' a __slots__ para obtener una asignación dinámica:

 class Foo(object): __slots__ = 'bar', 'baz', '__dict__' 

y ahora:

 >>> foo = Foo() >>> foo.boink = 'boink' 

Así que con '__dict__' en las tragamonedas perdemos algunos de los beneficios de tamaño con la ventaja de tener una asignación dinámica y aún tener ranuras para los nombres que esperamos.

Cuando hereda de un objeto que no está ranurado, obtiene el mismo tipo de semántica cuando usa __slots__ : los nombres que están en __slots__ apuntan a valores ranurados, mientras que cualquier otro valor se coloca en el __dict__ la instancia.

Evitar __slots__ porque desea poder agregar atributos sobre la marcha no es realmente una buena razón: simplemente agregue "__dict__" a sus __slots__ si es necesario.

De manera similar, puede agregar __weakref__ a __slots__ explícitamente si necesita esa función.

Establecido para vaciar la tupla al subclasificar una pareja nombrada:

El nombre de timbre incorporado hace instancias inmutables que son muy ligeras (esencialmente, el tamaño de las tuplas), pero para obtener los beneficios, debe hacerlo usted mismo si los clasifica como subclases:

 from collections import namedtuple class MyNT(namedtuple('MyNT', 'bar baz')): """MyNT is an immutable and lightweight object""" __slots__ = () 

uso:

 >>> nt = MyNT('bar', 'baz') >>> nt.bar 'bar' >>> nt.baz 'baz' 

Y al tratar de asignar un atributo inesperado se genera un AttributeError porque hemos evitado la creación de __dict__ :

 >>> nt.quux = 'quux' Traceback (most recent call last): File "", line 1, in  AttributeError: 'MyNT' object has no attribute 'quux' 

Puede permitir la creación de __dict__ al dejar __slots__ = () , pero no puede usar __slots__ no vacíos con subtipos de tuplas.

Mayor advertencia: herencia múltiple

Incluso cuando las ranuras no vacías son las mismas para varios padres, no pueden usarse juntas:

 class Foo(object): __slots__ = 'foo', 'bar' class Bar(object): __slots__ = 'foo', 'bar' # alas, would work if empty, ie () >>> class Baz(Foo, Bar): pass Traceback (most recent call last): File "", line 1, in  TypeError: Error when calling the metaclass bases multiple bases have instance lay-out conflict 

El uso de un __slots__ vacío en el padre parece proporcionar la mayor flexibilidad, permitiendo al niño elegir prevenir o permitir (agregando '__dict__' para obtener una asignación dinámica, consulte la sección anterior) la creación de un __dict__ :

 class Foo(object): __slots__ = () class Bar(object): __slots__ = () class Baz(Foo, Bar): __slots__ = ('foo', 'bar') b = Baz() b.foo, b.bar = 'foo', 'bar' 

No es necesario que tenga ranuras, así que si las agrega y las elimina más adelante, no debería causar ningún problema.

Aquí, si está componiendo mixins o utilizando clases abstractas de base , que no están diseñadas para ser instanciadas, un __slots__ vacío en esos padres parece ser la mejor manera de ir en términos de flexibilidad para los subclases.

Para demostrar, primero, creemos una clase con el código que nos gustaría usar en herencia múltiple

 class AbstractBase: __slots__ = () def __init__(self, a, b): self.a = a self.b = b def __repr__(self): return f'{type(self).__name__}({repr(self.a)}, {repr(self.b)})' 

Podríamos usar lo anterior directamente heredando y declarando las ranuras esperadas:

 class Foo(AbstractBase): __slots__ = 'a', 'b' 

Pero eso no nos importa, es una herencia simple trivial, necesitamos otra clase de la que también podríamos heredar, quizás con un atributo ruidoso:

 class AbstractBaseC: __slots__ = () @property def c(self): print('getting c!') return self._c @c.setter def c(self, arg): print('setting c!') self._c = arg 

Ahora si ambas bases tuvieran espacios no vacíos, no podríamos hacer lo siguiente. (De hecho, si quisiéramos, podríamos haber dado a AbstractBase ranuras no vacías a y b, y dejarlas fuera de la siguiente statement, dejarlas adentro sería incorrecto):

 class Concretion(AbstractBase, AbstractBaseC): __slots__ = 'ab _c'.split() 

Y ahora tenemos la funcionalidad tanto de la herencia múltiple, y aún podemos negar la instanciación de __dict__ y __weakref__ :

 >>> c = Concretion('a', 'b') >>> cc = c setting c! >>> cc getting c! Concretion('a', 'b') >>> cd = 'd' Traceback (most recent call last): File "", line 1, in  AttributeError: 'Concretion' object has no attribute 'd' 

Otros casos para evitar ranuras:

  • __class__ cuando desee realizar la asignación de __class__ con otra clase que no los tenga (y no puede agregarlos) a menos que los diseños de las ranuras sean idénticos. (Estoy muy interesado en saber quién está haciendo esto y por qué).
  • Evítelos si desea subclasificar elementos de longitud variable como long, tuple o str, y desea agregarles atributos.
  • Evítelos si insiste en proporcionar valores predeterminados a través de atributos de clase para variables de instancia.

Es posible que pueda desentrañar otras advertencias del rest de la documentación de __slots__ (los documentos de desarrollo de 3.7 son los más recientes) , a los que he hecho importantes contribuciones recientes.

Críticas de otras respuestas.

Las respuestas principales actuales citan información obsoleta y son bastante onduladas y fallan en algunos aspectos importantes.

No “solo use __slots__ cuando __slots__ muchos objetos”

Yo cito:

__slots__ usar __slots__ si va a crear una instancia de muchos (cientos, miles) de objetos de la misma clase”.

Las clases base abstractas, por ejemplo, del módulo de collections , no se __slots__ instancias, pero se declaran __slots__ para ellas.

¿Por qué?

Si un usuario desea negar la creación de __dict__ o __weakref__ , esas cosas no deben estar disponibles en las clases principales.

__slots__ contribuye a la reutilización al crear interfaces o mixins.

Es cierto que muchos usuarios de Python no escriben para reusar, pero cuando lo es, es valioso tener la opción de negar el uso innecesario de espacio.

__slots__ no rompe el decapado

Cuando decapado un objeto ranurado, puede encontrar que se queja con un TypeError engañoso:

 >>> pickle.loads(pickle.dumps(f)) TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled 

Esto es realmente incorrecto. Este mensaje proviene del protocolo más antiguo, que es el predeterminado. Puede seleccionar el último protocolo con el argumento -1 . En Python 2.7 esto sería 2 (que fue introducido en 2.3), y en 3.6 es 4 .

 >>> pickle.loads(pickle.dumps(f, -1)) <__main__.Foo object at 0x1129C770> 

en Python 2.7:

 >>> pickle.loads(pickle.dumps(f, 2)) <__main__.Foo object at 0x1129C770> 

en Python 3.6

 >>> pickle.loads(pickle.dumps(f, 4)) <__main__.Foo object at 0x1129C770> 

Así que tengo esto en cuenta, ya que es un problema resuelto.

Crítica de la respuesta aceptada hasta el 2 de octubre de 2016.

El primer párrafo es medio breve explicación, medio predictivo. Aquí está la única parte que responde la pregunta

El uso correcto de __slots__ es ahorrar espacio en los objetos. En lugar de tener un dict dynamic que permita agregar atributos a los objetos en cualquier momento, hay una estructura estática que no permite adiciones después de la creación. Esto ahorra la sobrecarga de un dictado para cada objeto que utiliza ranuras

La segunda mitad es una ilusión, y fuera de lugar:

Si bien esta es a veces una optimización útil, sería completamente innecesario si el intérprete de Python fuera lo suficientemente dynamic como para que solo requiriera el dictado cuando en realidad hubiera adiciones al objeto.

Python realmente hace algo similar a esto, solo crea el __dict__ cuando se accede, pero crear muchos objetos sin datos es bastante ridículo.

El segundo párrafo simplifica en exceso y __slots__ razones reales para evitar __slots__ . La siguiente no es una razón real para evitar las tragamonedas (por razones reales , consulte el rest de mi respuesta anterior):

Cambian el comportamiento de los objetos que tienen ranuras de una manera que puede ser abusada por los fanáticos del control y las tipografías estáticas.

Luego continúa discutiendo otras formas de lograr esa meta perversa con Python, sin discutir nada que tenga que ver con __slots__ .

El tercer párrafo es más ilusorio. Juntos, en su mayoría son contenidos fuera de la marca que el autor de la pregunta ni siquiera escribió y contribuye a la munición para los críticos del sitio.

Evidencia de uso de memoria

Crea algunos objetos normales y objetos ranurados:

 >>> class Foo(object): pass >>> class Bar(object): __slots__ = () 

Instancia un millón de ellos:

 >>> foos = [Foo() for f in xrange(1000000)] >>> bars = [Bar() for b in xrange(1000000)] 

Inspeccione con guppy.hpy().heap() :

 >>> guppy.hpy().heap() Partition of a set of 2028259 objects. Total size = 99763360 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1000000 49 64000000 64 64000000 64 __main__.Foo 1 169 0 16281480 16 80281480 80 list 2 1000000 49 16000000 16 96281480 97 __main__.Bar 3 12284 1 987472 1 97268952 97 str ... 

Acceda a los objetos normales y sus __dict__ e inspeccione nuevamente:

 >>> for f in foos: ... f.__dict__ >>> guppy.hpy().heap() Partition of a set of 3028258 objects. Total size = 379763480 bytes. Index Count % Size % Cumulative % Kind (class / dict of class) 0 1000000 33 280000000 74 280000000 74 dict of __main__.Foo 1 1000000 33 64000000 17 344000000 91 __main__.Foo 2 169 0 16281480 4 360281480 95 list 3 1000000 33 16000000 4 376281480 99 __main__.Bar 4 12284 0 987472 0 377268952 99 str ... 

Esto es coherente con la historia de Python, desde Unificar tipos y clases en Python 2.2

Si crea una subclase de un tipo incorporado, se agregará automáticamente espacio adicional a las instancias para acomodar a __dict__ y __weakrefs__ . (Sin __dict__ el __dict__ no se inicializa hasta que lo use, por lo que no debe preocuparse por el espacio ocupado por un diccionario vacío para cada instancia que cree). Si no necesita este espacio adicional, puede agregar la frase ” __slots__ = [] “a su clase.

Citando a Jacob Hallen :

El uso correcto de __slots__ es ahorrar espacio en los objetos. En lugar de tener un dict dynamic que permita agregar atributos a los objetos en cualquier momento, hay una estructura estática que no permite adiciones después de la creación. [Este uso de __slots__ elimina la sobrecarga de un dictado para cada objeto.] Aunque a veces es una optimización útil, sería completamente innecesario si el intérprete de Python fuera lo suficientemente dynamic como para que solo necesitara el dictado cuando en realidad hubiera adiciones a el objeto.

Desafortunadamente hay un efecto secundario en las tragamonedas. Cambian el comportamiento de los objetos que tienen ranuras de una manera que puede ser abusada por los fanáticos del control y las tipografías estáticas. Esto es malo, porque los fanáticos del control deberían estar abusando de las metaclases y los tipos de escritura estática deberían estar abusando de los decoradores, ya que en Python, solo debería haber una manera obvia de hacer algo.

Hacer que CPython sea lo suficientemente inteligente como para manejar el ahorro de espacio sin __slots__ es una tarea importante, por lo que probablemente no esté en la lista de cambios para P3k (todavía).

__slots__ usar __slots__ si va a crear una instancia de muchos (cientos, miles) de objetos de la misma clase. __slots__ solo existe como herramienta de optimización de memoria.

Es altamente desaconsejable usar __slots__ para restringir la creación de atributos, y en general, usted quiere evitarlo porque rompe el problema, junto con otras características de introspección de python.

Cada objeto python tiene un __dict__ __dict__ que es un diccionario que contiene todos los demás atributos. por ejemplo, cuando escribe self.attr python está haciendo self.__dict__['attr'] . Como puede imaginar, el uso de un diccionario para almacenar atributos requiere algo de espacio y tiempo extra para acceder a él.

Sin embargo, cuando usa __slots__ , cualquier objeto creado para esa clase no tendrá un atributo __dict__ . En su lugar, todos los atributos de acceso se realizan directamente a través de punteros.

Entonces, si desea una estructura de estilo C en lugar de una clase completa, puede usar __slots__ para compactar el tamaño de los objetos y reducir el tiempo de acceso a los atributos. Un buen ejemplo es una clase de puntos que contiene atributos x y y. Si va a tener muchos puntos, puede intentar usar __slots__ para conservar algo de memoria.

Además de las otras respuestas, aquí hay un ejemplo del uso de __slots__ :

 >>> class Test(object): #Must be new-style class! ... __slots__ = ['x', 'y'] ... >>> pt = Test() >>> dir(pt) ['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__slots__', '__str__', 'x', 'y'] >>> pt.x Traceback (most recent call last): File "", line 1, in  AttributeError: x >>> pt.x = 1 >>> pt.x 1 >>> pt.z = 2 Traceback (most recent call last): File "", line 1, in  AttributeError: 'Test' object has no attribute 'z' >>> pt.__dict__ Traceback (most recent call last): File "", line 1, in  AttributeError: 'Test' object has no attribute '__dict__' >>> pt.__slots__ ['x', 'y'] 

Por lo tanto, para implementar __slots__ , solo se necesita una línea adicional (y si no lo está ya, su clase es una clase de nuevo estilo). De esta manera, puede reducir la huella de memoria de esas clases cinco veces , a costa de tener que escribir un código de encurtido personalizado, cuando sea necesario.

Las ranuras son muy útiles para que las llamadas de la biblioteca eliminen el “envío del método nombrado” al realizar llamadas de función. Esto se menciona en la documentación de SWIG. Para bibliotecas de alto rendimiento que desean reducir la sobrecarga de funciones para funciones comúnmente llamadas que usan ranuras es mucho más rápido.

Ahora esto puede no estar directamente relacionado con la pregunta de los OP. Se relaciona más con la creación de extensiones que con la syntax de las ranuras en un objeto. Pero sí ayuda a completar la imagen para el uso de las tragamonedas y algunos de los razonamientos detrás de ellos.

Un atributo de una instancia de clase tiene 3 propiedades: la instancia, el nombre del atributo y el valor del atributo.

En el acceso de atributo regular , la instancia actúa como un diccionario y el nombre del atributo actúa como la clave en ese diccionario buscando valor.

instancia (atributo) -> valor

En el acceso a __slots__ , el nombre del atributo actúa como el diccionario y la instancia actúa como la clave en el diccionario buscando valor.

atributo (instancia) -> valor

En el patrón de peso mosca , el nombre del atributo actúa como el diccionario y el valor actúa como la clave en ese diccionario que busca la instancia.

atributo (valor) -> instancia

Usted tiene – esencialmente – no uso para __slots__ .

Para el momento en que crees que podrías necesitar __slots__ , en realidad quieres usar los patrones de diseño Lightweight o Flyweight . Estos son casos en los que ya no desea utilizar objetos puramente de Python. En su lugar, desea un envoltorio similar a un objeto de Python alrededor de una matriz, estructura o matriz numpy.

 class Flyweight(object): def get(self, theData, index): return theData[index] def set(self, theData, index, value): theData[index]= value 

El contenedor tipo clase no tiene atributos, solo proporciona métodos que actúan sobre los datos subyacentes. Los métodos se pueden reducir a métodos de clase. De hecho, podría reducirse a solo funciones que operan en la matriz de datos subyacente.

Un ejemplo muy simple de atributo __slot__ .

Problema: Sin __slots__

Si no tengo el atributo __slot__ en mi clase, puedo agregar nuevos atributos a mis objetos.

 class Test: pass obj1=Test() obj2=Test() print(obj1.__dict__) #--> {} obj1.x=12 print(obj1.__dict__) # --> {'x': 12} obj1.y=20 print(obj1.__dict__) # --> {'x': 12, 'y': 20} obj2.x=99 print(obj2.__dict__) # --> {'x': 99} 

Si observa el ejemplo anterior, puede ver que obj1 y obj2 tienen sus propios atributos x e y, y python también ha creado un atributo dict para cada objeto ( obj1 y obj2 ).

Supongamos que mi prueba de clase tiene miles de tales objetos? La creación de un dict atributo adicional para cada objeto causará muchos gastos generales (memoria, potencia de cálculo, etc.) en mi código.

Solución: Con __slots__

Ahora en el siguiente ejemplo, mi clase Test contiene el atributo __slots__ . Ahora no puedo agregar nuevos atributos a mis objetos (excepto el atributo x ) y python ya no crea un atributo dict . Esto elimina la sobrecarga de cada objeto, lo que puede ser significativo si tiene muchos objetos.

 class Test: __slots__=("x") obj1=Test() obj2=Test() obj1.x=12 print(obj1.x) # --> 12 obj2.x=99 print(obj2.x) # --> 99 obj1.y=28 print(obj1.y) # --> AttributeError: 'Test' object has no attribute 'y' 

Otro uso un tanto oscuro de __slots__ es agregar atributos a un objeto proxy del paquete ProxyTypes, anteriormente parte del proyecto PEAK. Su ObjectWrapper permite representar otro objeto, pero interceptar todas las interacciones con el objeto proxy. No se usa con mucha frecuencia (y no es compatible con Python 3), pero lo hemos utilizado para implementar una envoltura de locking segura para subprocesos en torno a una implementación asíncrona basada en tornados que rebota todo el acceso al objeto proxy a través del ioloop, usando la seguridad en subprocesos Objetos concurrent.Future para sincronizar y devolver resultados.

De forma predeterminada, cualquier acceso de atributo al objeto proxy le dará el resultado del objeto proxy. Si necesita agregar un atributo en el objeto proxy, puede usar __slots__ .

 from peak.util.proxies import ObjectWrapper class Original(object): def __init__(self): self.name = 'The Original' class ProxyOriginal(ObjectWrapper): __slots__ = ['proxy_name'] def __init__(self, subject, proxy_name): # proxy_info attributed added directly to the # Original instance, not the ProxyOriginal instance self.proxy_info = 'You are proxied by {}'.format(proxy_name) # proxy_name added to ProxyOriginal instance, since it is # defined in __slots__ self.proxy_name = proxy_name super(ProxyOriginal, self).__init__(subject) if __name__ == "__main__": original = Original() proxy = ProxyOriginal(original, 'Proxy Overlord') # Both statements print "The Original" print "original.name: ", original.name print "proxy.name: ", proxy.name # Both statements below print # "You are proxied by Proxy Overlord", since the ProxyOriginal # __init__ sets it to the original object print "original.proxy_info: ", original.proxy_info print "proxy.proxy_info: ", proxy.proxy_info # prints "Proxy Overlord" print "proxy.proxy_name: ", proxy.proxy_name # Raises AttributeError since proxy_name is only set on # the proxy object print "original.proxy_name: ", proxy.proxy_name 

La pregunta original era sobre casos de uso general, no solo sobre memoria. Por lo tanto, debe mencionarse aquí que también obtiene un mejor rendimiento al crear instancias de grandes cantidades de objetos, lo que es interesante, por ejemplo, al analizar documentos grandes en objetos o desde una base de datos.

Aquí hay una comparación de la creación de árboles de objetos con un millón de entradas, usando ranuras y sin ranuras. Como referencia, también el rendimiento cuando se utilizan dicts simples para los árboles (Py2.7.10 en OSX):

 ********** RUN 1 ********** 1.96036410332  3.02922606468  2.90828204155 dict ********** RUN 2 ********** 1.77050495148  3.10655999184  2.84120798111 dict ********** RUN 3 ********** 1.84069895744  3.21540498734  2.59615707397 dict ********** RUN 4 ********** 1.75041103363  3.17366290092  2.70941114426 dict 

Clases de prueba (ident, appart from slots):

 class Element(object): __slots__ = ['_typ', 'id', 'parent', 'childs'] def __init__(self, typ, id, parent=None): self._typ = typ self.id = id self.childs = [] if parent: self.parent = parent parent.childs.append(self) class ElementNoSlots(object): (same, w/o slots) 

código de prueba, modo detallado

 na, nb, nc = 100, 100, 100 for i in (1, 2, 3, 4): print '*' * 10, 'RUN', i, '*' * 10 # tree with slot and no slot: for cls in Element, ElementNoSlots: t1 = time.time() root = cls('root', 'root') for i in xrange(na): ela = cls(typ='a', id=i, parent=root) for j in xrange(nb): elb = cls(typ='b', id=(i, j), parent=ela) for k in xrange(nc): elc = cls(typ='c', id=(i, j, k), parent=elb) to = time.time() - t1 print to, cls del root # ref: tree with dicts only: t1 = time.time() droot = {'childs': []} for i in xrange(na): ela = {'typ': 'a', id: i, 'childs': []} droot['childs'].append(ela) for j in xrange(nb): elb = {'typ': 'b', id: (i, j), 'childs': []} ela['childs'].append(elb) for k in xrange(nc): elc = {'typ': 'c', id: (i, j, k), 'childs': []} elb['childs'].append(elc) td = time.time() - t1 print td, 'dict' del droot