Entrada / Salida

En este bloque se tratan los casos de estudio relacionados con la entrada / salida de información

  • Generar de informes: Word, Excel...
  • Exportación / importación de información
  • Transformación de datos
  • Exportar información de forma portable: XML, JSON, YAML...

IO-001 - Envío de información por múltiples canales

Problema

Mi aplicación muestra información por pantalla pero he de cambiar mucho código cuando, posteriormente, quiero añadir nuevas funciones en esos mismos puntos nuevas acciones como:

  • Añadir niveles de logging.
  • Volcar esa misma información a un fichero.
  • Enviar esa información a más de una ubicación.

Ejemplo IO-001.P01

Este es el código normal, con un print(...):

1
2
3
4
5
6
7
8
9
# ----------------------------------------------------------------------
def hello():
    """Display a hello world text"""
    print("hello")

# ----------------------------------------------------------------------
if __name__ == '__main__':

    hello()

Ejemplo IO-001.P02

Vemos realmente el problema cuando queremos añadir más localizaciones donde enviar el texto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# ----------------------------------------------------------------------
def hello():
    """Display a hello world text"""
    print("hello")

    with open("my_file.txt", "") as f:
        f.write("hello")

# ----------------------------------------------------------------------
if __name__ == '__main__':

    hello()

Ejemplo IO-001.P03

Y todavía se complica más si queremos añadir niveles de verbosidad:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# ----------------------------------------------------------------------
def hello(verbosity):
    """Display a hello world text"""
    print("hello")

    if verbosity > 0:
        with open("my_file.txt", "") as f:
            f.write("hello")

    if verbosity > 0:
        print("verbosity 1")

# ----------------------------------------------------------------------
if __name__ == '__main__':

    hello(1)

Solución

Usar un objeto estático y global que será el encargado de mostrar la información y que podremos configurar en función de lo que necesitemos.

Cómo

Para ello tenemos que declarar una clase estática global, que siga el patrón Singleton en Python, configurarla y llamarla de forma adecuada:

Nota

El patrón Singleton nos asegura que solo haya corriendo una instancia de un determinad objeto a la vez.

Si se crea otra nueva instancia, internamente no creará un nuevo objeto, sino que “rescatará” de la memoria el primer objeto que se creó y se reutilizará.

Ejemplo IO-001.S01

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# ----------------------------------------------------------------------
class Displayer:
    instance = None

    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = object.__new__(cls, *args, **kwargs)
            cls.__initialized = False
        return cls.instance

    def config(self, **kwargs):
        self.out_file = kwargs.get("out_file", None)
        self.out_screen = kwargs.get("out_screen", True)
        self.verbosity = kwargs.get("verbosity", 0)

        if self.out_file:
            self.out_file_handler = open(self.out_file, "w")

    def display(self, message):
        if self.verbosity > 0:
            self.__display(message)

    def display_verbosity(self, message):
        if self.verbosity > 1:
            self.__display(message)

    def display_more_verbosity(self, message):
        if self.verbosity > 2:
            self.__display(message)

    def __display(self, message):
        if self.out_screen:
            print(message)
        if self.out_file_handler:
            self.out_file_handler.write(message)

    def __init__(self):
        if not self.__initialized:
            self.__initialized = True
            self.out_file = None
            self.out_file_handler = None
            self.out_screen = True
            self.verbosity = 0


# ----------------------------------------------------------------------
def hello():
    """Display a hello world text"""

    # Use displayer
    out = Displayer()
    out.display("hello")

    out.display_verbosity("hello")

    # This will not be displayed by the verbosity level to 1
    out.display_more_verbosity("hello")

# ----------------------------------------------------------------------
if __name__ == '__main__':

    # Config displayer
    d = Displayer()
    d.config(out_screen=True,
             out_file="~/my_log.txt",
             verbosity=1)

    # Call function
    hello(1)