Saltar al contenido

¿Cómo puedo memorizar una instanciación de clase en Python?

Solución:

Veamos dos puntos sobre su pregunta.

Usando memoize

Puede utilizar la memorización, pero debe decorar el clase, no la __init__ método. Supongamos que tenemos este memorizador:

def get_id_tuple(f, args, kwargs, mark=object()):
    """ 
    Some quick'n'dirty way to generate a unique key for an specific call.
    """
    l = [id(f)]
    for arg in args:
        l.append(id(arg))
    l.append(id(mark))
    for k, v in kwargs:
        l.append(k)
        l.append(id(v))
    return tuple(l)

_memoized = {}
def memoize(f):
    """ 
    Some basic memoizer
    """
    def memoized(*args, **kwargs):
        key = get_id_tuple(f, args, kwargs)
        if key not in _memoized:
            _memoized[key] = f(*args, **kwargs)
        return _memoized[key]
    return memoized

Ahora solo necesitas decorar la clase:

@memoize
class Test(object):
    def __init__(self, somevalue):
        self.somevalue = somevalue

¿Veamos una prueba?

tests = [Test(1), Test(2), Test(3), Test(2), Test(4)]
for test in tests:
    print test.somevalue, id(test)

La salida está a continuación. Tenga en cuenta que los mismos parámetros producen la misma identificación del objeto devuelto:

1 3072319660
2 3072319692
3 3072319724
2 3072319692
4 3072319756

De todos modos, preferiría crear una función para generar los objetos y memorizarlos. Me parece más limpio, pero puede ser una molestia irrelevante:

class Test(object):
    def __init__(self, somevalue):
        self.somevalue = somevalue

@memoize
def get_test_from_value(somevalue):
    return Test(somevalue)

Utilizando __new__:

O, por supuesto, puede anular __new__. Hace unos días publiqué una respuesta sobre los entresijos y las mejores prácticas de anular __new__ eso puede ser útil. Básicamente, dice pasar siempre *args, **kwargs para usted __new__ método.

Yo, por mi parte, preferiría memorizar una función que crea los objetos, o incluso escribir una función específica que se encargaría de nunca volver a crear un objeto con el mismo parámetro. Por supuesto, sin embargo, esto es principalmente una opinión mía, no una regla.

La solución que terminé usando es esta:

class memoize(object):
    def __init__(self, cls):
        self.cls = cls
        self.__dict__.update(cls.__dict__)

        # This bit allows staticmethods to work as you would expect.
        for attr, val in cls.__dict__.items():
            if type(val) is staticmethod:
                self.__dict__[attr] = val.__func__

    def __call__(self, *args):
        key = '//'.join(map(str, args))
        if key not in self.cls.instances:
            self.cls.instances[key] = self.cls(*args)
        return self.cls.instances[key]

Y luego decoras el clase con esto, no __init__. Aunque brandizzi me proporcionó esa información clave, su decorador de ejemplo no funcionó como deseaba.

Encontré este concepto bastante sutil, pero básicamente cuando usas decoradores en Python, debes entender que lo que se decora (ya sea un método o una clase) es en realidad reemplazado por el propio decorador. Entonces, por ejemplo, cuando intentaba acceder Photograph.instances o Camera.generate_id() (un método estático), no pude acceder a ellos porque Photograph en realidad no se refiere a la clase de fotografía original, se refiere a la memoized función (del ejemplo de brandizzi).

Para evitar esto, tuve que crear una clase de decorador que en realidad tomara todos los atributos y métodos estáticos de la clase decorada y los expusiera como si fueran propios. Casi como una subclase, excepto que la clase decoradora no sabe de antemano qué clases decorará, por lo que tiene que copiar los atributos después del hecho.

El resultado final es que cualquier instancia del memoize class se convierte en una envoltura casi transparente alrededor de la clase real que ha decorado, con la excepción de que intentar instanciarla (pero realmente llamarla) le proporcionará copias en caché cuando estén disponibles.

Los parámetros para __new__ también se pasa a __init__, asi que:

def __init__(self, flubid):
    ...

Debes aceptar el flubid argumento allí, incluso si no lo usa en __init__

Aquí está el comentario relevante tomado de typeobject.c en Python2.7.3

/* You may wonder why object.__new__() only complains about arguments
   when object.__init__() is not overridden, and vice versa.

   Consider the use cases:

   1. When neither is overridden, we want to hear complaints about
      excess (i.e., any) arguments, since their presence could
      indicate there's a bug.

   2. When defining an Immutable type, we are likely to override only
      __new__(), since __init__() is called too late to initialize an
      Immutable object.  Since __new__() defines the signature for the
      type, it would be a pain to have to override __init__() just to
      stop it from complaining about excess arguments.

   3. When defining a Mutable type, we are likely to override only
      __init__().  So here the converse reasoning applies: we don't
      want to have to override __new__() just to stop it from
      complaining.

   4. When __init__() is overridden, and the subclass __init__() calls
      object.__init__(), the latter should complain about excess
      arguments; ditto for __new__().

   Use cases 2 and 3 make it unattractive to unconditionally check for
   excess arguments.  The best solution that addresses all four use
   cases is as follows: __init__() complains about excess arguments
   unless __new__() is overridden and __init__() is not overridden
   (IOW, if __init__() is overridden or __new__() is not overridden);
   symmetrically, __new__() complains about excess arguments unless
   __init__() is overridden and __new__() is not overridden
   (IOW, if __new__() is overridden or __init__() is not overridden).

   However, for backwards compatibility, this breaks too much code.
   Therefore, in 2.6, we'll *warn* about excess arguments when both
   methods are overridden; for all other cases we'll use the above
   rules.

*/
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *