====== Ejemplos prácticos Python ======
Bloque perteneciente al curso [[informatica:programacion:python:cursos:introduccion_programacion_python|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 [[https://docs.python.org/3/library/csv.html|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. [[http://pandas.pydata.org/docs/getting_started/index.html#getting-started|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á [[https://pypi.org/project/simplekml/|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:
* [[https://www.tornadoweb.org/en/stable/|Tornado]]: para comunicaciones asíncronas (incorpora su propio servidor web).
==== Flask ====
Ejemplo básico usando el módulo [[https://flask.palletsprojects.com/en/2.2.x/|Flask]]
pip install Flask
from flask import Flask
app = Flask(__name__)
@app.route("/hola")
def hello_world():
return "Hola, mundo"
==== Django ====
[[https://www.djangoproject.com/|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. [[https://docs.python.org/3/library/dbm.html?highlight=dbm#module-dbm|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 ====
[[https://docs.python.org/3/library/sqlite3.html?highlight=sqlite#module-sqlite3|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. [[https://www.sqlalchemy.org/|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 [[https://www.riverbankcomputing.com/software/pyqt/|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 [[https://docs.python.org/3/library/unittest.html|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 ""
* [[https://codewars.com|codewars]]: retos de programación
===== 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.
* https://www.json.org/json-en.html
==== 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"
}
}
}
}
}
'''
* [[https://docs.python.org/3/library/json.html|Tratamiento de JSON en Python (documentación oficial)]]
# 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)
* [[https://jsonviewer.stack.hu/|Online JSON Viewer]]
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?