Decoradores condicionales y expresiones ternarias con Python

Decoradores condicionales y expresiones ternarias con Python

Este capítulo va a ser para enseñarte como funcionan los decoradores condicionales y las expresiones ternarias de Python. Para esto, resolveremos juntos los dos ejercicios que os propuse en el capítulo anterior.


Primero, vayamos a ver una solución para mostrar las columnas de las tablas de la forma que te pedí:

  • -ID int. No admite valores nulos. Es clave primaria.
  • -Name char(35) No admite valores nulos.
  • -CountryCode char(3) No admite valores nulos. Es clave externa.
  • -District char(20) No admite valores nulos.
  • -Population int No admite valores nulos.

Para hacer esto, solo hay que modificar el bucle for (líneas 22 - 26), ya que es el que presenta los datos en la consola.

    # Método para mostrar columnas        
    @conexion
    def mostrar_columnas(self, nombre_bd, nombre_tabla):
        # Primero, se verifica si la base de datos existe
        sql = f"SHOW DATABASES LIKE '{nombre_bd}'"
        self.cursor.execute(sql)
        resultado = self.cursor.fetchone()
        # Si la base de datos no existe, muestra un mensaje de error y termina la función
        if not resultado:
            print(f'La base de datos {nombre_bd} no existe.')
            return
        # Establece la base de datos actual
        self.cursor.execute(f"USE {nombre_bd}")
        try:
            # Realiza la consulta para mostrar las columnas de la tabla especificada
            self.cursor.execute(f"SHOW COLUMNS FROM {nombre_tabla}")
            resultado = self.cursor.fetchall()
            
            # Se informa de que se están obteniendo las columnas
            print(f"Aquí tienes el listado de las columnas de la tabla '{nombre_tabla}':")
            # Recorre los resultados y los muestra por pantalla
            for columna in resultado:
                not_null = "No admite valores nulos." if columna[2] == "NO" else ""
                primary_key = "Es clave primaria." if columna[3] == "PRI" else ""
                foreign_key = "Es clave externa." if columna[3] == "MUL" else ""
                print(f"-{columna[0]} ({columna[1]}) {not_null} {primary_key} {foreign_key}")
        except:
            print("Ocurrió un error. Comprueba el nombre de la tabla.")
base_datos.mostrar_columnas("world", "city")

Resultado en la consola

Se abrió la conexión con el servidor.
Aquí tienes el listado de las columnas de la tabla 'city':
-ID (int) No admite valores nulos. Es clave primaria.
-Name (char(35)) No admite valores nulos.
-CountryCode (char(3)) No admite valores nulos. Es clave externa.
-District (char(20)) No admite valores nulos.
-Population (int) No admite valores nulos.
Se cerró la conexión con el servidor.

Seguramente, te hayas complicado bastante más que yo, puesto que no he explicado todavía como usar la expresión ternaria con Python.

Para que entiendas esto, tengo que explicar las expresiones ternarias en Python.

Expresiones ternarias en Python


Las expresiones ternarias de Python, permiten hacer evaluaciones condicionales en una misma línea. Es algo como las lambdas para las funciones normales.

Sintaxis de las expresiones ternarias de Python

La sintaxis de las expresiones ternarias de Python que estoy utilizando, es la siguiente:

variable = expresión_verdadera if expresión_condicional else expresión_falsa

Llevemos esto al código que te he puesto en el método de consultas de columnas.

Tenemos una variable donde almacenar el resultado (not_null).

Después, se pone lo que queremos almacenar. En caso de que la expresión sea verdadera, se guarda en la variable "No admite valores nulos.". La expresión condicional viene a continuación, "if columna[2]" == "NO". Finalmente, viene la expresión false con else, que no es más que un string vacío.

 not_null = "No admite valores nulos." if columna[2] == "NO" else ""

Analicemos mejor los resultados de la tabla. Fíjate en este código en la línea 18. Aquí voy a imprimir toda la lista de tuplas que se almacena en la variable "resultado".

    # Método para mostrar columnas        
    @conexion
    def mostrar_columnas(self, nombre_bd, nombre_tabla):
        # Primero, se verifica si la base de datos existe
        sql = f"SHOW DATABASES LIKE '{nombre_bd}'"
        self.cursor.execute(sql)
        resultado = self.cursor.fetchone()
        # Si la base de datos no existe, muestra un mensaje de error y termina la función
        if not resultado:
            print(f'La base de datos {nombre_bd} no existe.')
            return
        # Establece la base de datos actual
        self.cursor.execute(f"USE {nombre_bd}")
        try:
            # Realiza la consulta para mostrar las columnas de la tabla especificada
            self.cursor.execute(f"SHOW COLUMNS FROM {nombre_tabla}")
            resultado = self.cursor.fetchall()
            print(resultado)
            # Se informa de que se están obteniendo las columnas
            print(f"Aquí tienes el listado de las columnas de la tabla '{nombre_tabla}':")
            # Recorre los resultados y los muestra por pantalla
            for columna in resultado:
                not_null = "No admite valores nulos." if columna[2] == "NO" else ""
                primary_key = "Es clave primaria." if columna[3] == "PRI" else ""
                foreign_key = "Es clave externa." if columna[3] == "MUL" else ""
                print(f"-{columna[0]} ({columna[1]}) {not_null} {primary_key} {foreign_key}")
        except:
            print("Ocurrió un error. Comprueba el nombre de la tabla.")

Al hacer la llamada, aparte de las columnas, me imprime esta parte:

  • El nombre del campo corresponde a columna[0] de cada tupla.
  • El tipo de dato, está en la posición columna[1].
  • La columna[2], está reservada para sacar un string vacío o un 'NO'. Si es 'NO', indica que no admite valores nulos.
  • La columna[3] se reserva para indicar si es clave primaria, foránea o ninguna de estas. Si aparece 'PRI', quiere decir que la columna es clave primaria.
  • La columna[4] sirve para indicar si la columna tiene un valor por defecto para rellenar esa columna en los registros. Por ejemplo, al crear un registro o fila en la tabla, si en 'Population' no indicamos nada, se almacena por defecto el valor 0.
  • La columna[5] es para indicar si la columna es autoincrementable o no.

Por eso mismo, en la evaluación de la variable not_null, se pone columna[2] == 'NO'. Si encuentra un valor igual a 'NO', Nos dirá "No admite valores nulos".

La expresión de si es == a 'PRI' o no, se evalúa con la columna[3].

También, se evalúa 'MUL' en la misma columna en la variable foreign_key, ya que puede aparecer en la misma columna que 'PRI'.

Método decorador para comprobar bases de datos existentes

El siguiente ejercicio, era el de conseguir crear un método decorador para comprobar si existe una base de datos en concreto, puesto que esta comprobación, se necesita para varios métodos de la clase.

    # Decorador para comprobar si existe una base de datos
    def comprueba_bd(funcion_parametro):
        def interno(self, nombre_bd, *args):
            # Verifica si la base de datos existe en el servidor
            sql = f"SHOW DATABASES LIKE '{nombre_bd}'"
            self.cursor.execute(sql)
            resultado = self.cursor.fetchone()
            
            # Si la base de datos no existe, muestra un mensaje de error
            if not resultado:
                print(f'La base de datos {nombre_bd} no existe.')
            	return
            # Ejecuta la función decorada y devuelve el resultado
            return funcion_parametro(self, nombre_bd, *args)
        return interno

Analicemos las partes.

  1. El método interno() requiere del paso explícito del nombre_bd que se le pasa a la llamada del método decorado (funcion_parametro).
  2. Se verifica si el nombre proporcionado de base de datos existe.
  3. Si la base de datos no existe (es decir, si el valor de la variable resultado es None), el método interno muestra un mensaje de error con el nombre de la base de datos indicando que no existe y se finaliza la ejecución del método interno. En este caso, no se llega a ejecutar la función parámetro como hemos visto en los otros métodos decoradores.
  4. Si la base de datos existe, la función interno ejecuta la función decorada funcion_parametro y devuelve el resultado.

Probemos a decorar todos los métodos que sean necesarios y a eliminar el código duplicado (la parte que comprueba si existe la base de datos).

Recuerda, que la conexión siempre debe ir decorada antes que esta comprobación.

Los métodos decorados, de momento, serán los siguientes:

  • crear_tabla()
  • copia_bd()
  • eliminar_tabla()
  • mostrar_tablas()
  • mostrar_columnas()

Básicamente, se decoran métodos con el parámetro nombre_bd, los cuales, requieren del uso de una base de datos.

Algunos como eliminar_bd() o crear_bd(), realmente no necesitan ser decorados, ya que funcionan bien independientemente de si la base de datos existe o no, pero los puedes decorar sin problemas siempre que lo quieras verificar antes de ejecutar un método en vano.

Probemos los métodos recién decorados.

Recuerda, cuando implementes decoradores a los métodos, pruébalos en diferentes circunstancias, por ejemplo, en crear tabla, pon un nombre inexistente de tabla o de base de datos. Ves probando todo lo que pueda salir mal.

Evidentemente, siempre pueden quedar excepciones sin cubrir, pero realmente no es 100% necesario, ya que estamos en fase de desarrollo, los errores de Python son buenos también. Lo importante, es manejar todo lo que el usuario pueda hacer mal en la aplicación final.

No te pierdas nada del curso Máster en Python.

Te dejo aquí el código completo del capítulo por aquí, para que puedas solucionar cualquier error te te pudiera estar ocurriendo.


import mysql.connector
import os
import subprocess
import datetime

#conexion con la base de datos
acceso_bd = {"host" : "localhost",
             "user" : "root",
             "password" : "programacionfacil",
             }

# --> Rutas

#Obtenemos la raíz de la carpeta del proyecto
carpeta_principal = os.path.dirname(__file__)

carpeta_respaldo = os.path.join(carpeta_principal, "respaldo")

class BaseDatos:
    #Conexión y cursor
    def __init__(self, **kwargs):
        self.conector = mysql.connector.connect(**kwargs)
        self.cursor = self.conector.cursor()
        self.host = kwargs["host"]
        self.usuario = kwargs["user"]
        self.contrasena = kwargs["password"]
        self.conexion_cerrada = False
        # Avisa de que se abrió la conexión con el servidor
        print("Se abrió la conexión con el servidor.")
    
    #Decoradora para el reporte de bases de datos en el servidor
    def reporte_bd(funcion_parametro):
        def interno(self, nombre_bd):
            funcion_parametro(self, nombre_bd)
            BaseDatos.mostrar_bd(self)
        return interno
    
    # Decorador para el cierre del cursor y la base de datos
    def conexion(funcion_parametro):
        def interno(self, *args, **kwargs):
            try:
                if self.conexion_cerrada:
                    self.conector = mysql.connector.connect(
                        host = self.host,
                        user = self.usuario,
                        password = self.contrasena
                    )
                    self.cursor = self.conector.cursor()
                    self.conexion_cerrada = False
                    print("Se abrió la conexión con el servidor.")
                # Se llama a la función externa
                funcion_parametro(self, *args, **kwargs)
            except:
              	# Se informa de un error en la llamada
                print("Ocurrió un error con la llamada.")
            finally:
                if self.conexion_cerrada:
                    pass
                else:
                    # Cerramos el cursor y la conexión
                    self.cursor.close()
                    self.conector.close()
                    print("Se cerró la conexión con el servidor.")
                    self.conexion_cerrada = True
        return interno
    
    # Decorador para comprobar si existe una base de datos
    def comprueba_bd(funcion_parametro):
        def interno(self, nombre_bd, *args):
            # Verifica si la base de datos existe en el servidor
            sql = f"SHOW DATABASES LIKE '{nombre_bd}'"
            self.cursor.execute(sql)
            resultado = self.cursor.fetchone()
            
            # Si la base de datos no existe, muestra un mensaje de error
            if not resultado:
                print(f'La base de datos {nombre_bd} no existe.')
                return
            # Ejecuta la función decorada y devuelve el resultado
            return funcion_parametro(self, nombre_bd, *args)
        return interno
    
    
    #Consultas SQL 
    @conexion   
    def consulta(self, sql):
        try:
            self.cursor.execute(sql)
            print("Esta es la salida de la instrucción que has introducido:")
            print(self.cursor.fetchall())
        except:
            print("Ocurrió un error. Revisa la instrucción SQL.")
    
    @conexion
    def mostrar_bd(self):
        try:
            # Se informa de que se están obteniendo las bases de datos
            print("Aquí tienes el listado de las bases de datos del servidor:")
            # Realiza la consulta para mostrar las bases de datos
            self.cursor.execute("SHOW DATABASES")
            resultado = self.cursor.fetchall()
            # Recorre los resultados y los muestra por pantalla
            for bd in resultado:
                print(f"-{bd[0]}.")
        except:
            # Si ocurre una excepción, se avisa en la consola
            print("No se pudieron obtener las bases de datos. Comprueba la conexión con el servidor.")
             
    #Eliminar bases de datos
    @conexion
    @reporte_bd
    @comprueba_bd
    def eliminar_bd(self, nombre_bd):
        # Realiza la consulta para eliminar la base de datos
        self.cursor.execute(f"DROP DATABASE {nombre_bd}")
        print(f"Se eliminó la base de datos {nombre_bd} correctamente.")
    
    #Crear bases de datos
    @conexion
    @reporte_bd
    def crear_bd(self, nombre_bd):
        try:
            self.cursor.execute(f"CREATE DATABASE IF NOT EXISTS {nombre_bd}")
            print(f"Se creó la base de datos {nombre_bd} o ya estaba creada.")
        except:
            print(f"Ocurrió un error al intentar crear la base de datos {nombre_bd}.")
    
    #Crear backups de bases de datos
    @conexion
    @comprueba_bd
    def copia_bd(self, nombre_bd):
        #Obtiene la hora y fecha actuales
        self.fecha_hora = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
            
        #Se crea la copia de seguridad
        with open(f'{carpeta_respaldo}/{nombre_bd}_{self.fecha_hora}.sql', 'w') as out:
            subprocess.Popen(f'"C:/Program Files/MySQL/MySQL Workbench 8.0/"mysqldump --user=root --password={self.contrasena} --databases {nombre_bd}', shell=True, stdout=out)
        print("Se creó la copia correctamente.")
    
    @conexion
    @comprueba_bd
    def crear_tabla(self, nombre_bd, nombre_tabla, columnas):
        try:
            #String para guardar el string con las columnas y tipos de datos
            columnas_string = ""
            #Se itera la lista que se le pasa como argumento (cada diccionario)
            for columna in columnas:
                #formamos el string con nombre, tipo y longitud
                columnas_string += f"{columna['name']} {columna['type']}({columna['length']})"
                #Si es clave primaria, auto_increment o no admite valores nulos, lo añade al string
                if columna['primary_key']:
                    columnas_string += " PRIMARY KEY"
                if columna['auto_increment']:
                    columnas_string += " AUTO_INCREMENT"
                if columna['not_null']:
                    columnas_string += " NOT NULL"
                #Hace un salto de línea después de cada diccionario    
                columnas_string += ",\n"
            #Elimina al final del string el salto de línea y la coma    
            columnas_string = columnas_string[:-2]
            #Le indica que base de datos utilizar
            self.cursor.execute(f"USE {nombre_bd}")
            #Se crea la tabla juntando la instrucción SQL con el string generado
            sql = f"CREATE TABLE {nombre_tabla} ({columnas_string});"
            #Se ejecuta la instrucción
            self.cursor.execute(sql)
            #Se hace efectiva
            self.conector.commit()
            # Se informa de que la creación se ha efectuado correctamente.
            print("Se creó la tabla correctamente.")
        except:
            print("Ocurrió un error al intentar crear la tabla.")
            
    @conexion
    @comprueba_bd    
    def eliminar_tabla(self, nombre_bd, nombre_tabla):
        try:
            self.cursor.execute(f"USE {nombre_bd}")
            self.cursor.execute(f"DROP TABLE {nombre_tabla}")
            print(f"Tabla '{nombre_tabla}' eliminada correctamente de la base de datos {nombre_bd}.")
        except:
            print(f"No se pudo eliminar la tabla '{nombre_tabla}' de la base de datos '{nombre_bd}'.")
            
    # Método para mostrar las tablas de una base de datos
    @conexion
    @comprueba_bd  
    def mostrar_tablas(self, nombre_bd):
        # Se selecciona la base de datos
        self.cursor.execute(f"USE {nombre_bd};")
        # Se informa de que se están obteniendo las tablas
        print("Aquí tienes el listado de las tablas de la base de datos:")
        # Realiza la consulta para mostrar las tablas de la base de datos actual
        self.cursor.execute("SHOW TABLES")
        resultado = self.cursor.fetchall()
        # Recorre los resultados y los muestra por pantalla
        for tabla in resultado:
            print(f"-{tabla[0]}.")
            
    @conexion
    @comprueba_bd
    def mostrar_columnas(self, nombre_bd, nombre_tabla):
        # Establece la base de datos actual
        self.cursor.execute(f"USE {nombre_bd}")
        try:
            # Realiza la consulta para mostrar las columnas de la tabla especificada
            self.cursor.execute(f"SHOW COLUMNS FROM {nombre_tabla}")
            resultado = self.cursor.fetchall()
            
            # Se informa de que se están obteniendo las columnas
            print(f"Aquí tienes el listado de las columnas de la tabla '{nombre_tabla}':")
            # Recorre los resultados y los muestra por pantalla
            for columna in resultado:
                not_null = "No admite valores nulos." if columna[2] == "NO" else "Admite valores nulos."
                primary_key = "Es clave primaria." if columna[3] == "PRI" else ""
                foreign_key = "Es clave externa." if columna[3] == "MUL" else ""
                auto_increment = "Es autoincrementable." if columna[5] == "auto_increment" else ""
                print(f"-{columna[0]} ({columna[1]}) {not_null} {primary_key} {foreign_key} {auto_increment}")
        except:
            print("Ocurrió un error. Comprueba el nombre de la tabla.")

Un comentario en «Decoradores condicionales y expresiones ternarias con Python»

  1. Muchas gracias por este estupendo curso. Lo estoy usando para aprender sql y estoy aprendiendo muchas cosas que desconocía de python. Mi duda es:
    Por que me aparece una «b» precediendo al tipo de dato (int, char) al mostrar el contenido de una tabla.

    [(‘ID’, b’int’, ‘NO’, ‘PRI’, None, ‘auto_increment’), (‘Name’, b’char(35)’, ‘NO’, », b», »), (‘CountryCode’, b’char(3)’, ‘NO’, ‘MUL’, b», »), (‘District’, b’char(20)’, ‘NO’, », b», »), (‘Population’, b’int’, ‘NO’, », b’0′, »)]

    Un saludo.

Deja una respuesta

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

logo css Entrada anterior Guía de selectores CSS para principiantes
curso Java Entrada siguiente Sobrecarga de constructores y métodos en Java