Tabla de Contenidos

Ejemplos prácticos Python

Bloque perteneciente al curso Introducción a la programación con Python.

Tratamiento de ficheros CSV

Un fichero CSV es un fichero de texto cuyo contenido está separado por comas (CSV - Comma Separated Values).

Lectura fichero CSV

index,Organization id,Name,Website,Country, Description,Founded,Industry,Number of employees
1,abcd,Ferrell LLC, https://web.net,Papua New Guinea,Horizontal empowering knowledgebase,1990,Plastics,3498

Dentro de la biblioteca estándar de Python, hay un apartado para la lectura y escritura de ficheros CSV.

import csv
 
# Para ver las variantes de CSV que admite esta biblioteca
csv.list_dialects()
 
# Lista vacía en la que se irán añadiendo todas las filas
organizaciones = []
 
with open('fichero.csv', newline='') as csvfile:
    reader1 = csv.reader(csvfile, delimiter=',', quotechar='"')
    for row in reader1:
        organizaciones.append(row)
 
orgs_paises = {}
 
# Nos saltamos la primera línea porque son los encabezados
for r in organizaciones[1:]:
    pais = r[4]
    if pais in orgs_paises:
        orgs_paises[pais] += 1
    else:
        orgs_paises[pais] = 1
 
# Mostramos los países y cuántas veces se repiten
orgs_paises

Escritura fichero CSV

import csv
 
with open('paises.csv', 'w', newline='') as csvfile:
    writer1 = csv.writer(csvfile)
    writer1.writerow(['Pais', 'Num organizaciones'])
    for t in orgs_paises.items()
        writer1.writerow(t)

Manipulación de CSV mediante diccionarios

Forma más cómoda de manejar ficheros CSV.

import csv
 
with open('organizations.csv', newline='') as csvfile:
    reader = csv.DictReader(csvfile)
    for d in reader:
        print(f"Empresa {d["Name"]}, con {d["Number of employees"]} empleados")

Pandas

Biblioteca para el análisis de datos. Más información.

# Importamos y creamos un alias 
import pandas as pd
 
df1 = pd.read_csv("people.csv")
 
# Muestra una serie de operaciones y conteos básicos:
df1.describe()
 
# Mostrando los valores de cierto campo:
df1["First Name"]

KML

Un KML es un fichero XML con información cartográfica.

Entre los módulos de Python que podemos usar generar ficheros KML, está simplekml

import simplekml
 
kml = simplekml.Kml()
 
kml.newpoint(name="Secret place", coords=[{-8.77, 42.32}]) # longitud, latitud
mkl.save("secret_place.kml")

Entornos virtuales de desarrollo (venv)

pip freeze > requirements.txt

Instalamos los módulos necesarios para el proyecto:

pip install -r requirements.txt

Lanzar el entorno virtual en Windows:

.venv\Scripts\activate

Lanzar el entorno virtual en Linux:

source .venv/bin/activate

Cuando copiemos lo que hayamos hecho en un entorno virtual, deberíamos excluir .venv ya que es propio de la máquina en la que se ejecuta.

Desarrollo web

Alguna opción más:

Flask

Ejemplo básico usando el módulo Flask

pip install Flask
from flask import Flask
 
app = Flask(__name__)
 
@app.route("/hola")
def hello_world():
    return "Hola, mundo"

Django

Django es un Framework para desarrollar aplicaciones web de forma rápida. Es un “todo en uno”.

pip install Django

Usa ORM (Object Relational Mapping) para que el usuario no tenga que crear las tablas, se encarga el framework

Bases de datos

Bases de datos DBM

DBM son bases de datos de Unix. Información sobre el módulo ''dbm'' de Python

Escritura:

import dbm
 
with dbm.open("ejemplo-dbm", "c") as db:
    db["nombre"] = "Pepito"
    db["apellido"] = "Grillo"
    db["edad"] = str(31)

En este tipo de bases de datos, los datos deben guardarse como strings

Lectura:

import dbm
 
with dbm.open("ejemplo-dbm", "c") as db:
    for k in db.keys():
        print(f"Clave {k} -> valor {db[k]}")

Bases de datos SQLite

Información del módulo ''sqlite3'' de Python

Creación de base de datos y una tabla:

import sqlite3
 
con = sqlite3.connect("tutorial.db")
cur = con.cursor()
cur.execute("CREATE TABLE movie(title, year, score)")
con.close

Inserción de datos en la tabla previamente creada:

con = sqlite3.connect("tutorial.db")
cur = con.cursor()
cur.execute("""
    INSERT INTO movie VALUES
        ('Monty Python and the Holy Grail', 1975, 8.2),
        ('And Now for Something Completely Different', 1971, 7.5)
""")
 
con.commit()
con.close()

Consulta de los datos:

con = sqlite3.connect("tutorial.db")
cur = con.cursor()
cur.execute("""
    SELECT title, score FROM movie
""")
 
entradas = res.fetchall()
 
for t in entradas:
    print(f"Película: {t[0]}, calificación: {t[1]}")
 
con.commit()
con.close()

Para poder interactuar con un fichero sqlite podemos usar el cliente para línea de comandos sqlite3.

SQLAlchemy

Biblioteca de abstracción de bases de datos. Es un ORM. Web oficial

Necesitamos igualmente el soporte para la base de datos que vayamos a utilizar.

pip install SQLAlchemy
form sqlalchemy import create_engine, text
 
# Conexión SQLite
engine = create_engine("sqlite+pysqlite:///tutorial.db")
 
with engine.connect() as conn:
    result = conn.execute(text("SELECT * FROM movie"))
    print(result.all())

Para trabajar con bases de datos MySQL, primero instalamos la dependencia:

pip install mysql
form sqlalchemy import create_engine, text
 
# Conexión MySQL
engine = create_engine("mysql+pymysql:///usuario:contraseña@servidor:puerto/base_datos")
 
with engine.connect() as conn:
    result = conn.execute(text("SELECT * FROM movie"))
    print(result.all())

Interfaz gráfica

Usando PyQt

https://pythonpyqt.com/

Aplicación cronómetro

requirements.txt:

altgraph==0.17.3
macholib==1.16.2
pyinstaller==5.10.1
pyinstaller-hooks-contrib==2023.2
PyQt6==6.5.0
PyQt6-Qt6==6.5.0
PyQt6-sip==13.5.1

app.py:

import sys
 
from PyQt6.QtWidgets import QApplication, QWidget, QHBoxLayout, QPushButton
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = QWidget()    
    layout = QHBoxLayout()
    btn1 = QPushButton("Púlsame")
    btn2 = QPushButton("Púlsame también")
    layout.addWidget(btn1)
    layout.addWidget(btn2)
    w.setLayout(layout)
    w.resize(250, 150)
    w.move(300, 300)
    w.setWindowTitle('Hola Mundo')
    w.show()
 
    sys.exit(app.exec())

cronometro.py:

from datetime import datetime
import sys
from PyQt6.QtWidgets import QWidget, QApplication, QVBoxLayout, QPushButton, QLabel, QMessageBox
from PyQt6.QtCore import Qt, QTimer
from PyQt6 import QtGui
 
class Cronometro(QWidget):
    def __init__(self):
        super(Cronometro, self).__init__()
        self.enMarcha = False
        self.tiempoInicial = None
        self.tiempoTranscurrido = None
        self.iniciarGUI()
 
    # Construcción de la interfaz gráfica
    def iniciarGUI(self):
        self.setWindowTitle("Cronómetro")
        self.setWindowIcon(QtGui.QIcon('icon.png'))
        self.setGeometry(100, 100, 300, 300) # Posición y tamaño iniciales
        vbox = QVBoxLayout() # Disposición vertical de los elementos
 
        # Etiqueta para mostrar el tiempo
        self.timeLabel = QLabel("00:00:00")
        self.timeLabel.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignCenter) # Centrar el texto de la etiqueta
        self.timeLabel.setStyleSheet("font-size: 50px; font-weight: bold") # Estilo de la etiqueta (tamaño de la fuente)
 
        # Botón para iniciar/parar el cronómetro
        self.startStopButton = QPushButton("Start") 
        self.startStopButton.setStyleSheet("background-color: green; color: white; font-size: 20px")
        self.startStopButton.clicked.connect(self.startStop) # Conectar la señal clicked del botón con el método startStop
        self.startStopButton.setShortcut("Space")
 
        vbox.addWidget(self.timeLabel)
        vbox.addWidget(self.startStopButton)
        self.setLayout(vbox)
 
 
    def startStop(self):
        self.enMarcha = not self.enMarcha # Invierte el estado del cronómetro
        if self.enMarcha:
            if self.tiempoTranscurrido:
                self.tiempoInicial = datetime.now() - self.tiempoTranscurrido            
            else:
                self.tiempoInicial = datetime.now()
            self.temporizador = QTimer()
            self.temporizador.timeout.connect(self.actualizarTiempo)
            self.temporizador.start(1000) # cada 1000 ms genera un evento timeout
            self.startStopButton.setText("Stop")
            self.startStopButton.setStyleSheet("background-color: red; color: white; font-size: 20px")
        else:
            self.temporizador.stop()
            self.startStopButton.setText("Start")
            self.startStopButton.setStyleSheet("background-color: green; color: white; font-size: 20px")
        self.startStopButton.setShortcut("Space")
 
 
    def actualizarTiempo(self):
        tiempoActual = datetime.now()
        self.tiempoTranscurrido = tiempoActual - self.tiempoInicial
        horas, resto = divmod(self.tiempoTranscurrido.seconds, 3600)
        minutos, segundos = divmod(resto, 60)
        self.timeLabel.setText(f"{horas:02}:{minutos:02}:{segundos:02}")
 
 
    def closeEvent(self, event):
        reply = QMessageBox.question(self, 'Salir', '¿Está usted seguro de que quiere salir?',
        QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No)
        if reply == QMessageBox.StandardButton.Yes:
            event.accept() # el evento de salida se acepta y la aplicación termina
        else:
            event.ignore() # como si nada hubiera pasado
 
 
if __name__ == '__main__':
    # Creación de la aplicación, encargada de gestionar su ciclo de vida
    app = QApplication(sys.argv)
    app.setApplicationName("Cronómetro")
    app.setApplicationDisplayName("Cronómetro")
    # Creación de la instancia de la ventana del cronómetro
    cronometro = Cronometro()
    # Mostrar ventana
    cronometro.show()
    # Proceso de eventos de la aplicación y terminación de la misma
    sys.exit(app.exec())

Testing: Pruebas unitarias

Usaremos el módulo unittest para las pruebas unitarias en Python.

Los tests unitarios están pensados para probar módulos aislados.

Partimos de un programa:

# Programa para resolver ecuaciones de segundo grado con soluciones reales
from math import sqrt
 
# Cálculo de las raíces
def segundo_grado(a: float, b: float, c: float) -> tuple:
 
    """
    Calcula las soluciones reales de una ecuación de segundo grado.
    Devuelve una tupla con las 2 soluciones reales o lanza una excepción 
    en caso de no tenerlas.
    """
 
    d = b ** 2 - 4 * a *c
    if d > 0:
        # Ahora podemos ejecutar las siguientes operaciones sin temor a que dé error de dominio
        x1 = (-b + sqrt(d)) / (2 * a)
        x2 = (-b - sqrt(d)) / (2 * a)
        return x1, x2       
    else:
        # Para el caso de la raíz cuadrada negativa, mostramos un aviso
        raise ValueError("La ecuación {a}x^2 + {b}x + {c} = 0 no tiene soluciones reales")
 
 
# Punto de entrada al programa: se ejecuta si el programa se llama directamente
if __name__ == '__main__':
 
    # Toma de datos
    print("ax^2 + bx + c = 0")
    a = float(input("Coeficiente a: "))
    b = float(input("Coeficiente b: "))
    c = float(input("Coeficiente c: "))
 
    x1, x2 = segundo_grado(a, b, c)
 
    # Tercer paso: mostrar resultados
    print(f"Soluciones: {x1} y {x2}")

Si ahora queremos probar el anterior código, crearemos un fichero nuevo test_segundo_grado.py:

import unittest
 
from segundo_grado import segundo_grado
 
class TestSegundoGrado(unittest.TestCase):
 
    def test_ecuacion1(self):
        self.assertEqual(segundo_grado(1, 0, -1), (1, -1))
 
    def test_ecuacion2(self):
        self.assertEqual(segundo_grado(1, 2, 1), (-1, -1))
 
    def test_ecuacion3(self):
        self.assertEqual(segundo_grado(1, 2, -3), (1, -3))        
 
    def test_ecuacion4(self):
        self.assertAlmostEqual(segundo_grado(1, -2, 0), (2, 0))        
 
    # Para probar algo que falle:
    def test_ecuacion5(self):
        with self.assertRaises(ValueError) as err:
            segundo_grado(1, 0, 1)
            self.assertTrue(str(err).endswith("no tiene soluciones reales"))
 
    def test_ecuacion6(self):
        self.assertAlmostEqual(segundo_grado(2, 4, -6), (1, -3))
 
    def test_ecuacion7(self):
        self.assertAlmostEqual(segundo_grado(1, -1, -1), (1.618033988749895, -0.6180339887498949))
 
    def test_ecuacion8(self):
 
if __name__ == '__main__':
    unittest.main()        

Si hacemos modificaciones en el código, los tests nos aseguran que todo siga funcionando correctamente o que el cambio que hemos hecho hace que no pasen los test. De esa manera, podremos detectar si algo hemos hecho mal.

Además, al hacer tests, estamos dejando por escrito las pruebas que realizaremos en nuestro código.

Para lanzar la batería de tests desde la línea de comandos:

python -m unittest test_segundo_grado.py

Conversión a números romanos

Esta vez crearemos primero los tests y luego crearemos el código :

TDD (Test Driven Development): técnica de desarrollo en la que primero se define el test y luego el código que debe satisfacer el test.

Fichero test_romanos.py:

import unittest
 
from romanos import roman
 
class TestRomanos(unittest.TestCase):
 
    def test_1(self):
        self.assertEqual(roman(1), "I")
 
    def test_2(self):
        self.assertEqual(roman(2), "II")
 
    def test_2(self):
        self.assertEqual(roman(2), "II")
 
    def test_3(self):
        self.assertEqual(roman(3), "III")
 
    def test_4(self):
        self.assertEqual(roman(4), "IV")
 
    def test_5(self):
        self.assertEqual(roman(5), "V")
 
    def test_6(self):
        self.assertEqual(roman(6), "VI")
 
    def test_7(self):
        self.assertEqual(roman(7), "VII")
 
    def test_9(self):
        self.assertEqual(roman(9), "IX")
 
    def test_10(self):
        self.assertEqual(roman(10), "X")
 
    def test_13(self):
        self.assertEqual(roman(13), "XIII")
 
    def test_14(self):
        self.assertEqual(roman(14), "XIV")
 
    def test_17(self):
        self.assertEqual(roman(17), "XVII")
 
    def test_19(self):
        self.assertEqual(roman(19), "XIX")
 
    def test_20(self):
        self.assertEqual(roman(20), "XX")
 
    def test_27(self):
        self.assertEqual(roman(27), "XVII")
 
    def test_33(self):
        self.assertEqual(roman(33), "XXXIII")
 
    def test_40(self):
        self.assertEqual(roman(40), "XL")
 
    def test_49(self):
        self.assertEqual(roman(49), "XLIX")
 
    def test_50(self):
        self.assertEqual(roman(50), "L")
 
    def test_62(self):
        self.assertEqual(roman(62), "LXII")
 
    def test_90(self):
        self.assertEqual(roman(90), "XC")
 
    def test_97(self):
        self.assertEqual(roman(90), "XCVII")
 
    def test_100(self):
        self.assertEqual(roman(100), "C")
 
    def test_333(self):
        self.assertEqual(roman(333), "CCCXXXIII")
 
    def test_400(self):
        self.assertEqual(roman(400), "CD")
 
    def test_500(self):
        self.assertEqual(roman(500), "D")
 
    def test_666(self):
        self.assertEqual(roman(666), "DCLXVI")
 
    def test_900(self):
        self.assertEqual(roman(900), "CM")
 
    def test_1000(self):
        self.assertEqual(roman(1000), "M")
 
    def test_1666(self):
        self.assertEqual(roman(1666), "MDCLXVI")
 
    def test_3999(self):
        self.assertEqual(roman(3999), "MMMCMXCIX")
 
if __name__ == '__main__':
    unittest.main()

Creamos el programa romanos.py:

"""
Romani ite domum!
 
TDD - Test Driven Development
 
1. Escribir un test (que inicialmente falle)
2. Escribir el código que haga pasar el test
3. Refactorizar el código: optimización, limpieza, etc
(Y vuelta a empezar...)
"""
 
def roman(n: int) -> str:
 
    cantidades = [
        (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), 
        (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), (9, "IX"), 
        (5, "V"), (4, "IV"), (1, "I")
    ]
 
    conv = ""
 
    for cant, letras in cantidades:
        while n >= cant.
            n -= cant
            conv += letras
 
    return conv

Opción con recursividad:

def roman2(n: int) -> str:
 
    if n >= 1000:
        return "M" + roman2(n - 1000)
    elif n >= 900:
        return "CM" + roman2(n - 900)
    elif n >= 500:
        return "D" + roman2(n - 500)    
    elif n >= 400:
        return "CD" + roman2(n - 400)    
    elif n >= 100:
        return "C" + roman2(n - 100)            
    elif n >= 90:
        return "XC" + roman2(n - 90)            
    elif n >= 50:
        return "L" + roman2(n - 50)            
    elif n >= 40:
        return "XL" + roman2(n - 40)            
    elif n >= 10:
        return "XL" + roman2(n - 10)            
    elif n >= 9:
        return "XL" + roman2(n - 9)            
    elif n >= 5:
        return "V" + roman2(n - 5)            
    elif n >= 4:
        return "IV" + roman2(n - 4)            
    elif n >= 1:
        return "I" + roman2(n - 1)                    
    else:
        return ""

JSON

JSON (JavaScript Object Notation) formato para el intercambio de información.

JSON no son más que objetos (que son equivalentes a los diccionarios de Python) y listas.

Se trata de texto.

JSON en Python

Aspecto de un JSON, que lo representamos como string:

datos_json = '''
{
    "glossary": {
        "title": "example glossary",
        "GlossDiv": {
            "title": "S",
            "GlossList": {
                "GlossEntry": {
                    "ID": "SGML",
                    "SortAs": "SGML",
                    "GlossTerm": "Standard Generalized Markup Language",
                    "Acronym": "SGML",
                    "Abbrev": "ISO 8879:1986",
                    "GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
                        "GlossSeeAlso": [
                            "GML",
                            "XML"
                        ]
                    },
                    "GlossSee": "markup"
                }
            }
        }
    }
}
'''
# Biblioteca de Python para el tratamiento de JSON
import json
 
# Cargamos a partir de un string un objeto JSON/diccionario
datos = json.loads(datos_json)
 
type(datos) # dict
 
datos.keys() # dict_keys(['glossary'])
 
glossary = datos['glossary'] # dict
 
glossary.keys() # dict_keys(['title', 'GlossDiv'])
 
paises_capitales = {
    "España": "Madrid",
    "Portugal": "Lisboa",
    "Italia": "Roma",
    "Francia": "Paris",
    "Alemania": "Berlin"
}
 
type(paises_capitales) # dict
 
# Si queremos pasar de un diccionario a string en formato JSON
json.dumps(paises_capitales)

Si procesamos un JSON inválido:

mal_json = '''
{"mal": "json"
'''
 
try:
    json.loads(mal_json)
except json.JSONDecodeError as error:
    print("Error cargando JSON")

disculpa, Jairo, sabes hasta qué día tenemos disponible el acceso al campus virtual?