from - python 3 collections
Uso de__slots__? (11)
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:
- Acceso a atributos más rápido .
- Ahorro de espacio en memoria.
El ahorro de espacio es de
- Almacenar referencias de valor en ranuras en lugar de
__dict__
. -
__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 se equivoca (probablemente debería), los problemas podrían no manifestarse de otra manera, pero sus 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, dando lugar a las ranuras vacías de la abstracción (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.
Requisitos:
Para que los atributos nombrados en
__slots__
se almacenen en ranuras en lugar de__dict__
, una clase debe heredar delobject
.Para evitar la creación de un
__dict__
, debe heredar delobject
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, states 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 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__
declarada, 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
<member ''foo'' of ''Foo'' objects>
>>> type(Foo.foo)
<class ''member_descriptor''>
>>> 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()
>>> 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 de 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 creación de __dict__
mientras se subclasifican objetos ranurados, solo 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'',)
Porque 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 "<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 te encuentras con este problema, puedes eliminar __slots__
de los padres, o si tienes 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:
La incorporada namedtuple crea instancias inmutables que son muy ligeras (esencialmente, el tamaño de las tuplas), pero para obtener los beneficios, debe hacerlo usted mismo si las clasifica como subcategorías:
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 intentar asignar un atributo inesperado se 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''
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, 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 __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, por lo que si las agrega y las elimina más adelante, no debería causar ningún problema.
Ir al límite aquí : si estás componiendo mixins o usando clases base abstractas , 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 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 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 declaración, dejarlas adentro sería incorrecto):
class Concretion(AbstractBase, AbstractBaseC):
__slots__ = ''a b _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'')
>>> 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''
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 resto 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 se pierden la marca 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 tendría 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 dinámico 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 dinámico 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 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 tipos de escritura estática.
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 es contenido 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.
¿Cuál es el propósito de __slots__
en Python, especialmente con respecto a cuándo querría usarlo y cuándo no?
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 "<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'']
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.
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, todo el acceso a los atributos se realiza directamente a través de los 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. Si va a tener muchos puntos, puede intentar usar __slots__
para conservar algo de memoria.
Citando a Jacob Hallen :
El uso correcto de
__slots__
es ahorrar espacio en los objetos. En lugar de tener un dict dinámico 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 dinámico 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 los tipos de escritura estática. 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 abusar 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).
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; 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, utilizando 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 <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, 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
Las ranuras son muy útiles para que las llamadas de biblioteca eliminen el "envío del método nombrado" cuando se realizan llamadas a funciones. Esto se menciona en la documentation 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 OP. Está más relacionado con la creación de extensiones que con la sintaxis 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.
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 bloqueo 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, utilizando la seguridad en subprocesos Objetos concurrent.Future
para sincronizar y devolver resultados.
De forma predeterminada, cualquier atributo de acceso al objeto proxy le dará el resultado del objeto proxy. Si necesita agregar un atributo en el objeto proxy, se pueden 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
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 normal , 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
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''
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 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 envoltorio 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.
__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 utilizar __slots__
para restringir la creación de atributos, y en general, usted quiere evitarlo porque rompe el pickle, junto con otras características de introspección de python.