Respuestas:
En Python, ¿cuál es el propósito
__slots__
y cuáles son los casos en los que se debe evitar esto?
El atributo especial le __slots__
permite establecer explícitamente qué atributos de instancia espera que tengan sus instancias de objeto, con los resultados esperados:
El ahorro de espacio es de
__dict__
.__dict__
y __weakref__
creación si las clases primarias las niegan y usted declara __slots__
.Pequeña 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), de lo contrario los problemas podrían no manifestarse, pero tus objetos ocuparán más espacio del que deberían. Python 3.8:
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
Esto se debe a que el descriptor de ranura de la Base tiene una ranura separada de la de Wrong's. Esto generalmente no debería aparecer, pero podría:
>>> w = Wrong()
>>> w.foo = 'foo'
>>> Base.foo.__get__(w)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: foo
>>> Wrong.foo.__get__(w)
'foo'
La mayor advertencia es para la herencia múltiple: no se pueden combinar varias "clases principales con ranuras no vacías".
Para acomodar esta restricción, siga las mejores prácticas: Factorice la abstracción de todos menos uno o todos los padres de los cuales su clase concreta respectivamente y su nueva clase concreta heredarán colectivamente, dando a la abstracción espacios vacíos (al igual que las clases base abstractas en el biblioteca estándar).
Consulte la sección sobre herencia múltiple a continuación para ver un ejemplo.
Para que los atributos nombrados __slots__
se almacenen realmente en ranuras en lugar de a __dict__
, una clase debe heredar de object
.
Para evitar la creación de a __dict__
, debe heredar de object
y todas las clases en la herencia deben declarar __slots__
y ninguna de ellas puede tener una '__dict__'
entrada.
Hay muchos detalles si desea seguir leyendo.
__slots__
: Acceso más rápido a los atributos.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 apreciablemente 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 aproximadamente un 15% más rápido.
__slots__
: Ahorro de memoriaOtro propósito de __slots__
es reducir el espacio en 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 ahorrado sobre el uso
__dict__
puede ser significativo.
SQLAlchemy atribuye muchos ahorros de memoria __slots__
.
Para verificar esto, usando la distribución Anaconda de Python 2.7 en Ubuntu Linux, con guppy.hpy
(también conocido como pesado) y sys.getsizeof
, el tamaño de una instancia de clase sin __slots__
declarar, y nada más, es de 64 bytes. Eso no incluye el __dict__
. Gracias Python por la evaluación perezosa nuevamente, __dict__
aparentemente no se activa hasta que se hace referencia a ella, pero las clases sin datos generalmente son inútiles. Cuando se llama a la existencia, el __dict__
atributo tiene un mínimo de 280 bytes adicionalmente.
Por el contrario, una instancia de clase con __slots__
declarada ser()
(sin datos) tiene solo 16 bytes y 56 bytes totales con un elemento en las ranuras, 64 con dos.
Para Python de 64 bits, ilustra el consumo de memoria en bytes en Python 2.7 y 3.6, para __slots__
y __dict__
(sin ranuras definidas) para cada punto donde el dict crece en 3.6 (excepto 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
Entonces, a pesar de los dictados más pequeños en Python 3, vemos cuán bien escalamos las __slots__
instancias para ahorrarnos memoria, y esa es una razón importante por la que querría usar __slots__
.
Solo para completar mis notas, tenga en cuenta que hay un costo único por espacio en el espacio de nombres de la clase de 64 bytes en Python 2 y 72 bytes en Python 3, porque los espacios usan descriptores de datos como propiedades, llamados "miembros".
>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
__slots__
:Para negar la creación de a __dict__
, debe subclasificar object
:
class Base(object):
__slots__ = ()
ahora:
>>> b = Base()
>>> b.a = 'a'
Traceback (most recent call last):
File "<pyshell#38>", line 1, in <module>
b.a = 'a'
AttributeError: 'Base' object has no attribute 'a'
O subclase otra clase que define __slots__
class Child(Base):
__slots__ = ('a',)
y ahora:
c = Child()
c.a = 'a'
pero:
>>> c.b = 'b'
Traceback (most recent call last):
File "<pyshell#42>", line 1, in <module>
c.b = 'b'
AttributeError: 'Child' object has no attribute 'b'
Para permitir la __dict__
creación mientras se subclasifican los objetos ranurados, simplemente agregue '__dict__'
a __slots__
(tenga en cuenta que las ranuras están ordenadas y no debe repetir ranuras que ya están en las clases principales):
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á utilizando espacios de los padres, pero no 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 para la herencia múltiple:
class BaseA(object):
__slots__ = ('a',)
class BaseB(object):
__slots__ = ('b',)
Porque la creación de una clase secundaria a partir de padres con ambos espacios no vacíos falla:
>>> class Child(BaseA, BaseB): __slots__ = ()
Traceback (most recent call last):
File "<pyshell#68>", line 1, in <module>
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 eliminarlo __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!
'__dict__'
a __slots__
para obtener una asignación dinámica:class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
y ahora:
>>> foo = Foo()
>>> foo.boink = 'boink'
Entonces, '__dict__'
en las máquinas 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 lo usa __slots__
: nombres que __slots__
apuntan a valores ranurados, mientras que cualquier otro valor se coloca en la instancia __dict__
.
Evitar __slots__
porque desea poder agregar atributos sobre la marcha en realidad no es una buena razón, solo agregue "__dict__"
a su __slots__
si es necesario.
Del mismo modo se puede añadir __weakref__
a __slots__
explícitamente si necesita esa característica.
El nombre de la tupla incorporada crea instancias inmutables que son muy livianas (esencialmente, el tamaño de las tuplas) pero para obtener los beneficios, debe hacerlo usted mismo si las subclasifica:
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 tratar de asignar un atributo inesperado genera un AttributeError
porque hemos impedido la creación de __dict__
:
>>> nt.quux = 'quux'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'
Usted puede permitir __dict__
la creación dejando fuera __slots__ = ()
, pero no se puede utilizar no vacía__slots__
con subtipos de tupla.
Incluso cuando las ranuras no vacías son iguales para varios padres, no se pueden usar juntas:
class Foo(object):
__slots__ = 'foo', 'bar'
class Bar(object):
__slots__ = 'foo', 'bar' # alas, would work if empty, i.e. ()
>>> class Baz(Foo, Bar): pass
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict
El uso de un vacío __slots__
en el padre parece proporcionar la mayor flexibilidad, permitiendo que el niño elija prevenir o permitir (agregando '__dict__'
para obtener una asignación dinámica, consulte la sección anterior) la creación de__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 tiene que tener ranuras, por lo que si las agrega y las elimina más tarde, no debería causar ningún problema.
Salir de apuros aquí : si estás componiendo mixins o usando clases base abstractas , que no están destinadas a ser instanciadas, un vacío __slots__
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 bajo 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 podamos 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 tenían ranuras no vacías, no podríamos hacer lo siguiente. (De hecho, si quisiéramos, podríamos haber asignado AbstractBase
ranuras no vacías a y b, y haberlas dejado fuera de la siguiente declaración; dejarlas dentro estaría mal):
class Concretion(AbstractBase, AbstractBaseC):
__slots__ = 'a b _c'.split()
Y ahora tenemos funcionalidad de ambos a través de herencia múltiple, y aún podemos negar __dict__
e __weakref__
instanciar:
>>> c = Concretion('a', 'b')
>>> c.c = c
setting c!
>>> c.c
getting c!
Concretion('a', 'b')
>>> c.d = 'd'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Concretion' object has no attribute 'd'
__class__
tarea 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é).Es posible que pueda descifrar más advertencias del resto de la __slots__
documentación ( los documentos de desarrollo 3.7 son los más actuales) , a los que he realizado importantes contribuciones recientes.
Las principales respuestas actuales citan información obsoleta y son bastante onduladas y pierden la marca de algunas maneras importantes.
__slots__
al crear instancias de muchos objetos"Yo cito:
"Te gustaría usar
__slots__
si vas a crear una instancia de muchos (cientos, miles) de objetos de la misma clase".
Las clases base abstractas, por ejemplo, del collections
módulo, no se instancian, pero __slots__
se declaran para ellas.
¿Por qué?
Si un usuario desea negar __dict__
o __weakref__
crear, 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 la reutilización, pero cuando lo haces, tener la opción de negar el uso innecesario de espacio es valioso.
__slots__
no rompe el decapadoAl encurtir un objeto ranurado, es posible que se queje con un error TypeError
:
>>> 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 -1
argumento. En Python 2.7 esto sería 2
(que se introdujo en 2.3), y en 3.6 lo 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 tendría esto en mente, ya que es un problema resuelto.
El primer párrafo es mitad breve explicación, mitad predictiva. Aquí está la única parte que realmente responde la pregunta
El uso adecuado de
__slots__
es para ahorrar espacio en los objetos. En lugar de tener un dict dinámico que permite agregar atributos a los objetos en cualquier momento, existe una estructura estática que no permite adiciones después de la creación. Esto ahorra la sobrecarga de un dict por cada objeto que usa ranuras
La segunda mitad es una ilusión, y fuera de lugar:
Si bien esto a veces es una optimización útil, sería completamente innecesario si el intérprete de Python fuera lo suficientemente dinámico como para que solo requiera el dict cuando realmente haya adiciones al objeto.
Python en realidad hace algo similar a esto, solo crea __dict__
cuando se accede a él, pero crear muchos objetos sin datos es bastante ridículo.
El segundo párrafo simplifica demasiado y pierde razones reales para evitar __slots__
. Lo siguiente no es una razón real para evitar espacios (por razones reales , vea el resto 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 los weenies de escritura estática.
Luego continúa discutiendo otras formas de lograr ese objetivo perverso con Python, sin discutir nada que ver con __slots__
.
El tercer párrafo es más ilusiones. Juntos, es en su mayoría contenido fuera de la marca que el respondedor ni siquiera escribió y contribuye a las municiones para los críticos del sitio.
Cree algunos objetos normales y objetos ranurados:
>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()
Instanciar un millón de ellos:
>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]
Inspeccionar 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 regulares 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 consistente con la historia de Python, desde Unificar tipos y clases en Python 2.2
Si subclasifica un tipo incorporado, se agrega automáticamente espacio adicional a las instancias para acomodar
__dict__
y__weakrefs__
. (Sin__dict__
embargo, 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 Tu clase.
__slots__
. ¡Seriamente! ¡Gracias!
__slots__
aproximadamente un año: github.com/python/cpython/pull/1819/files
Citando a Jacob Hallen :
El uso adecuado de
__slots__
es para ahorrar espacio en los objetos. En lugar de tener un dict dinámico que permite 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 dict por cada objeto.] Si bien esto a veces es una optimización útil, sería completamente innecesario si el intérprete de Python fuera lo suficientemente dinámico como para que solo requiriera el dict cuando realmente hubiera adiciones al 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 los weenies de escritura estática. Esto es malo, porque los fanáticos del control deberían estar abusando de las metaclases y las weenies de tipeo estático deberían estar abusando de los decoradores, ya que en Python, solo debería haber una forma 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__
no aborda los mismos problemas que la escritura estática. Por ejemplo, en C ++, no es que la declaración de una variable miembro esté restringida, es la asignación de un tipo no intencionado (y el compilador forzado) a esa variable. No estoy tolerando el uso de __slots__
, solo estoy interesado en la conversación. ¡Gracias!
Debería usarlo __slots__
si va a crear una instancia de muchos (cientos, miles) de objetos de la misma clase. __slots__
solo existe como una herramienta de optimización de memoria.
Se desaconseja su uso __slots__
para restringir la creación de atributos.
Decapado de objetos con __slots__
no funcionará con el protocolo de decapado predeterminado (más antiguo); Es necesario especificar una versión posterior.
Algunas otras características de introspección de python también pueden verse afectadas negativamente.
Cada objeto de Python tiene un __dict__
atributo que es un diccionario que contiene todos los demás atributos. por ejemplo, cuando escribe self.attr
python realmente está haciendo self.__dict__['attr']
. Como puede imaginar, el uso de un diccionario para almacenar atributos requiere espacio y tiempo extra para acceder a él.
Sin embargo, cuando lo use __slots__
, cualquier objeto creado para esa clase no tendrá un __dict__
atributo. En cambio, todo el acceso a los atributos se realiza directamente a través de punteros.
Entonces, si desea una estructura de estilo C en lugar de una clase completa, puede usarla __slots__
para compactar el tamaño de los objetos y reducir el tiempo de acceso a los atributos. Un buen ejemplo es una clase Point que contiene los atributos x & y. Si va a tener muchos puntos, puede intentar usarlos __slots__
para conservar algo de memoria.
__slots__
definido no es como una estructura de estilo C. Existe un nombre de atributo de asignación de diccionario a nivel de clase para índices, de lo contrario, no sería posible lo siguiente: class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)
Realmente creo que esta respuesta debería aclararse (puedo hacerlo si lo desea). Además, no estoy seguro de que instance.__hidden_attributes[instance.__class__[attrname]]
sea más rápido que instance.__dict__[attrname]
.
Además de las otras respuestas, aquí hay un ejemplo de uso __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 "<stdin>", line 1, in <module>
AttributeError: x
>>> pt.x = 1
>>> pt.x
1
>>> pt.z = 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute 'z'
>>> pt.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__dict__'
>>> pt.__slots__
['x', 'y']
Entonces, para implementar __slots__
, solo se necesita una línea adicional (y hacer que su clase sea una clase de nuevo estilo si aún no lo está). De esta forma, puede reducir la huella de memoria de esas clases 5 veces , a expensas de tener que escribir un código personalizado de pickle, cuando sea necesario.
Las ranuras son muy útiles para las llamadas a la biblioteca para eliminar el "envío de método con nombre" al realizar llamadas a funciones. Esto se menciona en la documentación SWIG . Para las bibliotecas de alto rendimiento que desean reducir la sobrecarga de funciones para las 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. Está más relacionado con la construcción de extensiones que con el uso de la sintaxis de ranuras en un objeto. Pero ayuda a completar la imagen del uso de las máquinas tragamonedas y algunos de los razonamientos detrás de ellas.
Un atributo de una instancia de clase tiene 3 propiedades: la instancia, el nombre del atributo y el valor del atributo.
En el acceso regular a los atributos , 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 __slots__ , el nombre del atributo actúa como el diccionario y la instancia actúa como la clave en el valor de búsqueda del diccionario.
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 buscando la instancia.
atributo (valor) -> instancia
__slots__
?
Un ejemplo muy simple de __slot__
atributo.
__slots__
Si no tengo __slot__
atributo 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 dict
atributo para cada objeto ( obj1 y obj2 ).
¿Y si mi clase Test tiene miles de tales objetos? Crear un atributo adicional dict
para cada objeto causará mucha sobrecarga (memoria, potencia informática, etc.) en mi código.
__slots__
Ahora, en el siguiente ejemplo, mi clase Test contiene un __slots__
atributo. Ahora no puedo agregar nuevos atributos a mis objetos (excepto el atributo x
) y Python ya no crea un dict
atributo. Esto elimina la sobrecarga de cada objeto, que puede ser importante 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 __slots__
es agregar atributos a un proxy de objeto desde el paquete ProxyTypes, anteriormente parte del proyecto PEAK. Su ObjectWrapper
le permite al proxy de otro objeto, pero interceptan todas las interacciones con los objetos proxy. No se usa con mucha frecuencia (y no es compatible con Python 3), pero lo hemos usado para implementar un contenedor de bloqueo seguro para subprocesos alrededor de una implementación asincrónica basada en tornado que rebota todo el acceso al objeto proxy a través del ioloop, usando subprocesos seguros concurrent.Future
objetos para sincronizar y devolver resultados.
Por defecto, cualquier acceso de atributo al objeto proxy le dará el resultado del objeto proxy. Si necesita agregar un atributo en el objeto proxy, __slots__
puede usarse.
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
Usted no tiene - esencialmente - uso para __slots__
.
Para el momento en que cree que podría necesitar __slots__
, en realidad desea usar patrones de diseño Ligero o Flyweight . Estos son casos en los que ya no desea utilizar únicamente objetos de Python. En cambio, desea un contenedor similar a un objeto 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 de 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 subyacente de datos.
__slots__
?
__slots__
son técnicas de optimización para ahorrar memoria. __slots__
muestra beneficios cuando tiene muchos objetos, así como el patrón de diseño Flyweight. Ambos resuelven el mismo problema.
__slots__
realmente es la respuesta, y como Evgeni señala, se puede agregar como una simple idea de último momento (por ejemplo, puede enfocarse primero en la corrección y luego agregar rendimiento).
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 al usar dictados simples para los árboles (Py2.7.10 en OSX):
********** RUN 1 **********
1.96036410332 <class 'css_tree_select.element.Element'>
3.02922606468 <class 'css_tree_select.element.ElementNoSlots'>
2.90828204155 dict
********** RUN 2 **********
1.77050495148 <class 'css_tree_select.element.Element'>
3.10655999184 <class 'css_tree_select.element.ElementNoSlots'>
2.84120798111 dict
********** RUN 3 **********
1.84069895744 <class 'css_tree_select.element.Element'>
3.21540498734 <class 'css_tree_select.element.ElementNoSlots'>
2.59615707397 dict
********** RUN 4 **********
1.75041103363 <class 'css_tree_select.element.Element'>
3.17366290092 <class 'css_tree_select.element.ElementNoSlots'>
2.70941114426 dict
Clases de prueba (ident, aparte de las ranuras):
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
class Child(BaseA, BaseB): __slots__ = ('a', 'b')
ejemplo con los empy-slot-parents. ¿Por qué aquí sedictproxy
crea un en lugar de crear unAttributeError
parac
?