Pregunta Uso de __slots__?


Cual es el proposito de __slots__ en Python, especialmente con respecto a cuándo quisiera usarlo y cuándo no?


504
2018-01-23 05:37


origen


Respuestas:


En Python, ¿cuál es el propósito de __slots__ y ¿cuáles son los casos en que uno debería evitar esto?

TLDR:

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

  1. Más rápido acceso a los atributos
  2. ahorro de espacio en memoria.

El ahorro de espacio es de

  1. Almacenar referencias de valor en ranuras en lugar de __dict__.
  2. Negando __dict__ y __weakref__ creación si las clases de padres les niegan y usted declara __slots__.

Advertencias rápidas

Pequeña advertencia, solo deberías declarar una ranura 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 obtienes este error (probablemente debería), los problemas pueden no manifestarse, pero tus objetos ocuparán más espacio de lo que deberían.

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

La advertencia más importante es para la herencia múltiple: no se pueden combinar varias "clases principales con ranuras no vacías".

Para acomodarse a esta restricción, siga las mejores prácticas: Factorice todas las abstracciones menos una o todas las que sus clases concretas respectivamente y su nueva clase concreta colectivamente heredarán, dando a las abstracciones espacios vacíos (al igual que las clases base abstractas en el biblioteca estándar).

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

Requisitos:

  • Para tener atributos nombrados en __slots__ para ser realmente almacenado en ranuras en lugar de un __dict__, una clase debe heredar de object.

  • Para evitar la creación de un __dict__, debes heredar de object y todas las clases en la herencia deben declarar __slots__ y ninguno de ellos puede tener un '__dict__' entrada.

Hay muchos detalles si deseas seguir leyendo.

Por que usar __slots__: Acceso de atributo más rápido.

El creador de Python, Guido van Rossum, estados que él realmente creó __slots__ para un acceso más rápido a los atributos.

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

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.

Por que usar __slots__: Ahorro de memoria

Otro objetivo 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.

Atributos de SQLAlchemy una gran cantidad de ahorros de memoria para __slots__.

Para verificar esto, usando 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 64 bytes. Eso hace no incluir la __dict__. Gracias Python por la evaluación perezosa de nuevo, el __dict__aparentemente no se llama hasta que se hace referencia a él, pero las clases sin datos generalmente son inútiles. Cuando se llama a la existencia, el __dict__ atributo es un mínimo de 280 bytes adicionalmente.

Por el contrario, una instancia de clase con __slots__ declarado ser () (sin datos) solo tiene 16 bytes, y 56 bytes en total con un elemento en las ranuras, 64 con dos.

Para Python de 64 bits, ilustré 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 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

Entonces, a pesar de los dictados más pequeños en Python 3, vemos cuán bien __slots__ Escala por instancias para salvarnos la memoria, y esa es una de las principales razones por las que querrías usar __slots__.

Solo para completar mis notas, tenga en cuenta que hay un costo por ranura en el espacio de nombre de la clase de 64 bytes en Python 2 y 72 bytes en Python 3, porque los slots usan descriptores de datos como propiedades, llamados "miembros".

>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> getsizeof(Foo.foo)
72

Demostración de __slots__:

Para negar la creación de un __dict__, debes 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'

Permitir __dict__ creación mientras subclases objetos ranurados, simplemente agrega '__dict__' al __slots__ (tenga en cuenta que las ranuras están ordenadas, y no debe repetir las 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 necesitas declarar __slots__ en su subclase, y aún usará 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',)

Debido a que no se puede crear una clase secundaria de padres con ranuras no vacías:

>>> 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, tu podría solo eliminar __slots__ de los padres, o si tienes el control de los padres, dales espacios vacíos o refactoriza a 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!

Añadir '__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 con '__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 espacios para los nombres que esperamos.

Cuando heredas de un objeto que no está ranurado, obtienes el mismo tipo de semántica cuando lo usas __slots__ - nombres que están en __slots__ apuntar a valores ranurados, mientras que cualquier otro valor se coloca en la instancia __dict__.

Evitando __slots__ porque no desea agregar atributos sobre la marcha en realidad no es una buena razón, simplemente agregue "__dict__" para usted __slots__ si esto es requerido

Puedes agregar de manera similar __weakref__ a __slots__ explícitamente si necesita esa característica.

Establecer como tupla vacía cuando se subclasifica una tupla nombrada:

El comando namedtuple incorporado hace instancias inmutables que son muy livianas (esencialmente, del tamaño de tuplas) pero para obtener los beneficios, debe hacerlo usted mismo si los 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 plantea una 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'

poder permitir __dict__ creación por dejar __slots__ = (), pero no puedes usar no vacíos __slots__ con subtipos de tupla.

Mayor advertencia: Herencia múltiple

Incluso cuando las máquinas tragamonedas no vacías son iguales para padres múltiples, 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

Usando un vacío __slots__ en el padre parece proporcionar la mayor flexibilidad, permitiendo que el niño elija prevenir o permitir (añadiendo '__dict__' para obtener una asignación dinámica, ver 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'

Tu no tener tener espacios, así que si los agrega y los elimina más adelante, no debería causar ningún problema.

Salir en una extremidad aquí: Si estás componiendo Mixins o usando clases base abstractas, que no están destinados a ser instanciados, un vacío __slots__ en esos padres parece ser la mejor manera de ir en términos de flexibilidad para los subclasistas.

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 los espacios previstos:

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

Pero no nos importa eso, es herencia simple trivial, necesitamos otra clase de la que también podamos heredar, tal vez 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 tienen ranuras no vacías, no podríamos hacer lo siguiente. (De hecho, si quisiéramos, podríamos haber dado AbstractBase ranuras no vacías a y b, y las dejaron fuera de la declaración de abajo - dejarlas en sería incorrecto):

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

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

>>> 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:

  • Evítalos cuando quieras actuar __class__ asignación con otra clase que no los tiene (y no puede agregarlos) a menos que los diseños de las tragamonedas sean idénticos. (Estoy muy interesado en saber quién está haciendo esto y por qué).
  • Evítelos si desea crear subclases de duración variable como long, tuple o str, y desea agregar atributos a ellos.
  • Evítelos si insiste en proporcionar valores predeterminados a través de atributos de clase para variables de instancia.

Es posible que pueda sacar más advertencias del resto del __slots__  documentación (los documentos 3.7 dev son los más recientes), a lo que hice importantes contribuciones recientes.

Críticas de otras respuestas

Las respuestas principales actuales citan información desactualizada y son bastante onduladas a mano y pasan por alto la marca en algunas formas importantes.

No "solo use" __slots__ al crear instancias de muchos objetos "

Yo cito:

"Te gustaría usar __slots__ si va a instanciar mucho (cientos, miles) de objetos de la misma clase ".

Clases base abstractas, por ejemplo, del collections módulo, no están instanciados, aún __slots__ son declarados por ellos.

¿Por qué?

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

__slots__contribuye a la reutilización cuando se crean interfaces o mixins.

Es cierto que muchos usuarios de Python no están escribiendo para ser reutilizados, pero cuando lo haces, tener la opción de rechazar el uso de espacio innecesario es valioso.

__slots__ no rompe el decapado

Al decapado de un objeto ranurado, puede encontrar que se queja 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 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 la mitad de la explicación, la mitad predictiva. Aquí está la única parte que realmente responde la pregunta

El uso apropiado 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. 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 requiriera el dict cuando realmente había adiciones al objeto.

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

El segundo párrafo simplifica en exceso y omite las razones reales para evitar __slots__. El siguiente es no una verdadera razón para evitar las máquinas tragamonedas (para real razones, 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 monstruos de control y los "weenies" estáticos.

A continuación, analiza otras formas de lograr ese objetivo perverso con Python, sin discutir nada que ver con __slots__.

El tercer párrafo es más una ilusión. En conjunto, es sobre todo el contenido fuera de la marca que el que responde ni siquiera autor 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__ = ()

Crea una instancia de 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 inspeccionar de nuevo:

>>> 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__. (Los __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 tu clase.


525
2018-01-21 04:46



Citando Jacob Hallen:

El uso apropiado de __slots__ es para ahorrar espacio en los objetos. En vez de tener   un dict dinámico que permite agregar atributos a 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 era lo suficientemente dinámico como para que lo haría   solo requiere el dict cuando realmente hubo adiciones al objeto.

Lamentablemente, hay un efecto secundario en las máquinas tragamonedas. Ellos cambian el comportamiento de   los objetos que tienen ranuras de una manera que puede ser abusada por fanáticos del control   y weenies de tipeo estáticos. Esto es malo, porque los fanáticos del control deberían   estar abusando de las metaclases y las locuras de tipeo estático deberían estar abusando   decoradores, ya que en Python, solo debería haber una forma obvia de hacer algo.

Hacer CPython lo suficientemente inteligente como para manejar el ahorro de espacio sin __slots__ es un Mayor   empresa, que es probablemente la razón por la cual no está en la lista de cambios para P3k (todavía).


257
2018-01-23 05:54



Te gustaría usar __slots__ si va a instanciar mucho (cientos, miles) de objetos de la misma clase. __slots__ solo existe como una herramienta de optimización de memoria.

Es muy desaconsejable usar __slots__ para restringir la creación de atributos y, en general, desea evitarlo porque rompe el pickle, junto con algunas otras características de introspección de python.


117
2018-01-23 05:50



Cada objeto python tiene una __dict__ atttribute, que es un diccionario que contiene todos los demás atributos. p.ej. cuando escribes self.attr Python lo está haciendo self.__dict__['attr']. Como puede imaginar, el uso de un atributo de diccionario para almacenar requiere más espacio y tiempo extra para acceder a él.

Sin embargo, cuando usas __slots__, cualquier objeto creado para esa clase no tendrá una __dict__ atributo. En cambio, todos los accesos a los atributos 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 de los atributos. Un buen ejemplo es una clase Point que contiene atributos xy y. Si vas a tener muchos puntos, puedes intentar usar __slots__ para conservar algo de memoria.


54
2018-01-23 13:38



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 toma una línea adicional (y hace que tu clase sea una clase de nuevo estilo si no lo es). De esta manera puedes reducir la huella de memoria de esas clases 5 veces, a expensas de tener que escribir código personalizado de pickle, siempre y cuando sea necesario.


17
2018-06-03 07:43



Las máquinas tragamonedas son muy útiles para que las llamadas a bibliotecas eliminen el "envío de métodos con nombre" al hacer llamadas a funciones. Esto se menciona en SWIG documentación. Para las bibliotecas de alto rendimiento que desean reducir la sobrecarga de funciones para funciones comúnmente llamadas usando ranuras es mucho más rápido.

Ahora bien, esto puede no estar directamente relacionado con la pregunta de OPs. Se relaciona más con crear extensiones que con usar el ranuras sintaxis 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.


11
2017-11-25 03:06



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

En 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 acceso __slots__, el nombre del atributo actúa como el diccionario y la instancia actúa como la clave en el diccionario que busca el valor.

atributo (instancia) -> valor

En 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


5
2018-06-04 22:09



Usted tiene, esencialmente, ningún uso para __slots__.

Para el momento en que piensas que podrías necesitar __slots__, de hecho quieres usar Ligero o Peso mosca patrones de diseño. Estos son casos en los que ya no desea usar objetos puramente de Python. En su lugar, desea una envoltura tipo Python alrededor de una matriz, estructura o conjunto 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 subyacente de datos.


2
2018-01-23 11:15



Otro uso algo oscuro de __slots__ es agregar atributos a un proxy de objeto del paquete ProxyTypes, anteriormente parte del proyecto PEAK. Sus ObjectWrapper le permite proxy otro objeto, pero interceptar todas las interacciones con el objeto proxy. No es muy común (y no admite Python 3), pero lo hemos utilizado para implementar un contenedor de bloqueo de subprocesos alrededor de una implementación asincrónica basada en un tornado que rebota todo el acceso al objeto proxy a través de ioloop, usando thread-safe 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 ser usado.

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

2
2018-02-07 20:56



Un ejemplo muy simple de __slot__ atributo.

Problema: Sin __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 miras el ejemplo anterior, puedes ver que obj1 y obj2 tener su propio X y y atributos y python también ha creado un dict atributo para cada objeto (obj1 y obj2)

Supongamos que si mi clase Prueba tiene miles de tales objetos? Creando un atributo adicional dict para cada objeto causará mucha sobrecarga (memoria, potencia de cálculo, etc.) en mi código.

Solución: con __slots__

Ahora en el siguiente ejemplo mi clase Prueba contiene __slots__ atributo. Ahora no puedo agregar nuevos atributos a mis objetos (excepto el atributo x) y Python no crea un dict atributo más. Esto elimina la sobrecarga de cada objeto, lo que puede volverse 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'

1
2017-11-02 09:18



La pregunta original fue sobre casos de uso general no solo sobre la memoria. Por lo tanto, se debe mencionar aquí que también se mejora actuación al crear instancias de grandes cantidades de objetos, por ejemplo, interesantes al analizar documentos grandes en objetos o desde una base de datos.

Aquí hay una comparación de crear árboles de objetos con un millón de entradas, usando máquinas tragamonedas y sin ranuras. Como referencia también el rendimiento al usar 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 of 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

0
2018-01-12 18:42