Cómo utilizar decoradores y el patrón Singleton en Python

in #programming7 years ago (edited)

Tutorial sobre para qué sirven y cómo utilizar en Python los decoradores además del patrón Singleton. Para este tutorial asumo que conoces python o lo has trabajado alguna vez. Todo el código usado lo tienes en el siguiente link:

Código completo en Github


Utilizar un decorador para entrada y salida de funciones

El siguiente decorador es una clase lo que hace es ejecutar la función en el __init__(), para luego hacer una salida por medio del método __call__().

class my_decorator(object):
     def __init__ self , f):
         print("Dentro de my_decorador.__init__()")
         f()

     def __call__ (self):
         print ("Dentro de my_decorador.__call__()")

 @my_decorator
 def my_function():
     print("Dentro de my_function()")

 print("Decorada my_function()")

my_function()

Llamar a la función dentro del decorador

Para ejecutar el código dentro de la función decoradora y jugar con él podemos pasarlo como un objeto función, dentro de los parámetros de iniciación del decorador.

class entry_exit(object):
     def __init__ (self, f):
         self.f = f

     def __call__ ( self ):
         print("Entrando en", self.f.__name__)
         self.f() # Llamamos a la función dentro
         print("Saliendo de", self.f,__name__)

@entry_exit
def func1():
     print("Dentro de func1()")

@entry_exit
def func2 ():
     print("Dentro de func2()")

func1()
func2()

En el código anterior hemos llamado a nuestras funciones a través del método call de la clase, pero realmente no necesitamos una clase para eso, podemos decorar con una función:

def entry_exit(f):
    def new_f():   # Mucho más fácil
        print("Entrando en" , f.__name__)
        f()
        print("Exited", f.__name__)
    return new_f

@entry_exit
def func1():
    print("inside func1()")

@entry_exit
def func2():
    print("inside func2()")

func1()
func2()

Si deseas aprender más sobre temas avanzados en Python, puedes acceder a libro online Python 3 Patterns, Recipes and Idioms - Bruce Heckel & Friends. De ahí he traducido la información para este tutorial.


Pasar argumentos dentro del decorador:

class decorator_without_arguments(object):
     def __init__(self, f):
         """
Si no especifivamos unos argumentos en el decorador,
la función es pasada al constructor como un objeto
         """
         print("Inside __init__()")
         self.f = f

     def __call__(self, *args):
         """
Cuando llamamos al método __call__ disponemos de
los argumentos que hemos pasado a la función.
         """
         print("Inside __call__()")
         self.f(*args)
         print("After self.f(*args)")

 @decorator_without_arguments
 def sayHello (a1, a2, a3, a4):
     print('sayHello arguments:', a1, a2, a3, a4)

 print ( "After decoration" )

 print ("Preparing to call sayHello()")
 sayHello("say", "hello", "argument", "list")
 print ("After first sayHello() call" )
 sayHello("a", "different", "set of", "arguments")
 print("After second sayHello() call")

La salida de este código sería:

Inside __init__()
After decoration
Preparing to call sayHello ()
Inside __call__()
sayHello arguments : say hello argument list
After self.f(*args)
After first sayHello () call
Inside __call__ ()
sayHello arguments : a different set of arguments
After self.f(*args)
After second sayHello() call


Decorador con argumentos

Si has llegado hasta aquí estárás para el siguiente código, que aunque a primera vista parezca difícil, es muy fácil.

class decorator_with_arguments(object):
    # Los argumentos entran como parámetros de la clase
     def __init__ (self, arg1, arg2, arg3):
         # Si hay argumentos en el decorador, la función a
         # ser decorada no es pasada al constructor
         print ( "Inside __init__()" )
         self.arg1 = arg1
         self.arg2 = arg2
         self.arg3 = arg3

     def __call__(self, f):
         # Cuando hay argumentos en el decorador,
         # la función __call__ recibe únicamente
         # el objeto de la función decorada
         print("Inside __call__()")
         def wrapped_f(*args):
             print("Inside wrapped_f()")
             print("Decorator arguments:",
                   self.arg1, self.arg2, self.arg3)
             f(*args)
             print("After f(*args)")
         return wrapped_f

 @decorator_with_arguments("hello", "world", 42)
 def sayHello(a1, a2, a3, a4):
     print ('sayHello arguments:' , a1, a2, a3, a4)

print("After decoration")

print("Preparing to call sayHello()")
sayHello("say", "hello", "argument", "list")
print("after first sayHello() call")
sayHello("a", "different", "set of", "arguments")
print("after second sayHello() call")

El patrón Singleton:

Lo que hacemos con este patrón es crear una clase con una variable que se guardara en una clase interna del objeto. Si el objeto no ha sido inicializado no se ha crea la clase interna.

class SoloUno:
    class __SoloUno: # El objeto interno,
        def __init__(self, arg): # con su argumento
            self.val = arg # se hace uno particular
        def __str__(self):
            return repr(self) + self.val
    instance = None # Se llama sólo si no está incializado
    def __init__(self, arg):  # el objeto interno
        if not SoloUno.instance:
            SoloUno.instance = SoloUno.__SoloUno(arg)
        else:  # Por eso el patrón de Singleton (sólo 1)
            SoloUno.instance.val = arg  # Sirve para hacer objetos
   def __getattr__(self, name):   # de tipos diferentes, por ejemplo
        return getattr(self.instance, name)

x = SoloUno('sausage')
print(x)
y = SoloUno('eggs')
print(y)
z = SoloUno('spam')
print(z) print(x)
print(y)
print('x')
print('y')
print('z')
output = '''
<__main__.__OnlyOne instance at 0076B7AC>sausage
<__main__.__OnlyOne instance at 0076B7AC>eggs
<__main__.__OnlyOne instance at 0076B7AC>spam
<__main__.__OnlyOne instance at 0076B7AC>spam
<__main__.__OnlyOne instance at 0076B7AC>spam
<__main__.SoloUno instance at 0076C54C>
<__main__.SoloUno instance at 0076DAAC>
<__main__.SoloUno instance at 0076AA3C>
'''

Uno de los propósitos más interesantes del patrón Singleton es poder crear objetos únicos con un set de atributos común a todos ellos, información que pueden intercambiar. El siguiente es 'Borg', un ejemplo de Alex Martelli:


class Borg:
    _estado_compartido = {} # Atributos que a los que
    def __init__(self):    # accederán los objetos
        self.__dict__ = self._estado_compartido

class Singleton(Borg):
    def __init__(self, arg):
        Borg.__init__(self)
        self.val = arg
    def __str__(self): 
        return self.val

x = Singleton('sausage')
print(x)
y = Singleton('eggs')
print(y)
z = Singleton('spam')
print(z)
print(x)
print(y)
print('x')
print('y')
print('z')
output = '''
sausage
eggs
spam
spam
spam
<__main__.Singleton instance at 0079EF2C>
<__main__.Singleton instance at 0079E10C>
<__main__.Singleton instance at 00798F9C>
''

Una versión más elegante y reducida del código anterior:


class SingleTon(object):
    __instance = None
    def __new__ (cls , val):
        if SingleTon.__instance is None :
            SingleTon.__instance = object.__new__(cls)
        SingleTon.__instance.val = val
        return SingleTon. __instance
Sort:  

Buen dia man una consulta tienes algun post donde explique como delegar el sp estuve viendo algunos tuyos de hace meses no se si tienes unos nuevos. Gracias de antemano y disculpa si por aqui no era la via para consultarte

Congratulations @mondeja! You have received a personal award!

Happy Birthday - 1 Year
Click on the badge to view your own Board of Honor on SteemitBoard.

For more information about this award, click here

By upvoting this notification, you can help all Steemit users. Learn how here!