Esta receta incluye una implementación «comentada» del patrón de diseño «Flyweight» (peso mosca) usando una metaclase Python.

Introducción

Para una mejor comprensión y máximo aprovechamiento, se recomienda al lector no iniciado en las metaclases, leer la receta Ahí va la virgen! Metaclases! antes de proceder con ésta.

Patrón flyweight

El peso mosca es un patrón muy útil en aplicaciones que utilizan muchos objetos parecidos o idénticos. El objetivo es compartir parte del estado del objeto. Cuando varias instancias comporten los mismos valores para sus atributos (tamaño, color, forma, etc.) realmente el objeto solo se crea una vez y las siguientes instancias referencian el estado del objeto existente.

El patrón Memoization se puede considerar una particularización de flyweight para funciones. Memoization cachea el resultado de una función o método de modo que la siguiente vez que se invoque la misma función con los mismos argumentos se obtiene el valor cacheado en lugar de recalcularse.

No se debe confundir flyweight con el patrón Singleton. En el singleton solo existe una estancia para la clase que implementa dicho patrón, mientras que con flyweight habrá tantas instancias como «objetos diferentes» haya.

Es posible aplicar flyweight aunque los objetos solo compartan una parte de su estado (el estado intrínseco) y tengan almacenamiento adicional para sus valores distintivos (estado extrínseco). Por ejemplo, un colección de pelotas del mismo color y tamaño (estado intrínseco) pueden tener posiciones diferentes (estado extrínseco). En esta receta vamos a tratar únicamente el caso en el que los objetos son idénticos y dependientes de una clave: a igual clave, igual estado. Veamos un ejemplo de cómo debería funcionar esto:

>>> luna = Astro("luna")
>>> moon = Astro("luna")
>>> sol = Astro("sol")
>>> luna is moon
... True
>>> luna is sol
... False

Esa es la teoría, en la práctica las limitaciones de los lenguajes de programación no siempre permiten que el patrón se pueda implementar de una forma tan transparente. En esos casos (Java, C++, etc.) la implementación del patrón incluye una «factoría», es decir, una función u objeto que crea, almacena y devuelve las instancias a los objetos conforme se le piden.

flyweight como metaclase

Pero Python siempre da la nota, y esta vez no podía ser menos. Por medio de una metaclase se puede implementar flyweight de una forma elegante y completamente transparente para el usuario de la clase. El constructor va a hacer el papel de factoría de forma transparente.

Ésta es la metaclase:

class Flyeight(type):
    def __init__(cls, name, bases, dct):
        cls.__instances = {}
        type.__init__(cls, name, bases, dct)

    def __call__(cls, key, *args, **kw):
        instance = cls.__instances.get(key)
        if instance is None:
            instance = type.__call__(cls, key, *args, **kw)
            cls.__instances[key] = instance
        return instance

Para usarlo basta añadir el típico “metaclass” a tu clase y ya será un peso mosca en toda regla.

class Astro:
    __metaclass__ = Flyweight

    def __init__(self, key):
        print "Creando la instancia '%s'..." % key
        time.sleep(2)
        print "'%s' creado" % key

a = Astro("luna")
b = Astro("luna")
assert a is b

El método __init__ de la clase Astro no hace nada útil, solo sirve para ilustrar que únicamente se crean dos instancias y que a y b son realmente el mismo objeto. El sleep enfatiza el ahorro de recursos (tiempo, en este caso). Aunque normalmente este patrón se utiliza para evitar pedir memoria para almacenar los atributos de objetos idénticos, también es muy útil para ahorrar el tiempo que lleva construirlos, un efecto nada despreciable si la construcción de la instancia implica acceder a ficheros en disco.

Puedes descargar el código de nuestro repo público. Al ejecutar este fichero debería obtener esta salida:

$ python flyweight.py
Creando la instancia 'luna'...
'luna' creado
Creando la instancia 'sol'...
'sol' creado

Relacionado



blog comments powered by Disqus