100% encontró este documento útil (1 voto)
778 vistas173 páginas

Python Avanzado v0.1.4

Este documento presenta un curso avanzado de programación en Python. Comienza explicando las plataformas de desarrollo recomendadas como PyCharm. Luego, guía al lector en la instalación de Python y PyCharm. El resto del documento cubre temas como clases y objetos, módulos, manejo de archivos, Pandas, NumPy, bases de datos y desarrollo web, proporcionando ejemplos para cada tema.

Cargado por

Adamas Ben Arie
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
100% encontró este documento útil (1 voto)
778 vistas173 páginas

Python Avanzado v0.1.4

Este documento presenta un curso avanzado de programación en Python. Comienza explicando las plataformas de desarrollo recomendadas como PyCharm. Luego, guía al lector en la instalación de Python y PyCharm. El resto del documento cubre temas como clases y objetos, módulos, manejo de archivos, Pandas, NumPy, bases de datos y desarrollo web, proporcionando ejemplos para cada tema.

Cargado por

Adamas Ben Arie
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 173

Master Big Data y Data Science

Aplicaciones al comercio, empresa y finanzas

Programación Python
(avanzado)

Javier Domínguez Gómez


GnuPG fingerprint: 94AD 19F4 9005 EEB2 3384 C20F 5BDC C668 D664 8E2B

v 0.1.4
Tabla de contenido
Introducción 6

Plataformas de desarrollo 6

Comprobación del entorno de trabajo adecuado 7

Instalación de Python 3 7

Instalación de PyCharm 10

Simple programa de ejemplo 16

Clases y objetos 23

Programación estructurada Vs. POO 24

Clases y objetos 30

Atributos y métodos de clase 31

Métodos especiales 35

Objetos dentro de objetos 36

Encapsulación de atributos 38

Herencia 40

Métodos de las colecciones 44

Métodos en cadenas de texto 45

Métodos en listas 48

Métodos en conjuntos 50

Métodos en diccionarios 53

Módulos y paquetes 54

Módulos 54

Collections 56

Datetime 58

2
Math 61

Random 64

Paquetes 67

Manejo de ficheros 68

Creación 68

Apertura o lectura 69

Modificación 70

Manejo del puntero 71

Ficheros y objetos con pickle 73

Pandas 74

Instalación de Pandas 74

Importación de Pandas 77

Ordenación de los datos 79

Instalación de Matplotlib 80

Visualización con Matplotlib 84

Ejemplo Standard & Poor's 500 89

Ejemplo COVID-19 en España por comunidades autónomas 91

Ejemplo esperanza de vida frente a renta per cápita por países 92

NumPy 101

Importación de NumPy 101

Ejemplo Índice de Masa Corporal (IMC) 102

Ejemplo cálculo áreas de triángulos 104

Arrays de dos dimensiones en NumPy 105

Cálculo estadístico con NumPy 106

Generación de datos random con NumPy 108

3
Bases de datos 110

MongoDB 110

Crear un nuevo proyecto Mongo en la nube 110

Instalación de librerías necesarias 117

Conexión y creación de una base de datos MongoDB 120

Crear una colección 121

Insertar datos (documentos) 121

Ejecutar consultas en una colección 122

Modificar datos de un documento 123

Modificar datos de varios documentos 123

SQL Server 124

Instalación de SQL Server 124

Instalación Azure Data Studio 128

Crear una base de datos 132

Crear una tabla e insertar datos en ella 134

Creación de un login y credenciales 135

Instalación del driver Microsoft ODBC 18 136

MacOS 136

GNU/Linux 136

Instalación de librerías necesarias 137

Conexión con la base de datos 139

Servicio web 141

Instalación de librerías necesarias 141

Desarrollo de una API Rest 144

Extracción de datos de una API 146

4
Twitter como fuente de datos 150

Instalación de Tweepy 151

Uso de la API 155

Consultas y extracción de datos 156

Aplicación de vuelos comerciales 157

Obtención de datos de tráfico aéreo 158

Obtener datos de tráfico aéreo en Python 161

Trazar avión en el mapa 164

Crear aplicación de seguimiento de vuelos real-time 167

5
Introducción
Una vez cursado el módulo de fundamentos de Python, el alumno podrá
continuar su aprendizaje en un modo más avanzado. Para ello se podrán
realizar varios ejercicios en cada sección de modo que el aprendizaje sea
gradual y efectivo, pues el alumno comenzará a programar desde el primer
momento. Al final del módulo se introducirán algunos conceptos básicos y
librerías utilizadas en el tratamiento de datos y cálculo numérico como son
Pandas y NumPy.

Plataformas de desarrollo
Para iniciarse por primera vez en Python se suele recomendar utilizar algún
entorno de desarrollo como Anaconda, que incorpora la aplicación web
Jupiter Notebook, utilizado anteriormente en el módulo de fundamentos de
Python. El uso de Jupiter Notebook nos puede venir muy bien para dar
nuestros primeros pasos y realizar pruebas con sencillos fragmentos de código
de una manera muy fácil y sencilla, pero para seguir avanzando en el
conocimiento de este lenguaje y adentrarnos en técnicas más avanzadas es
altamente recomendable usar algunos de los editores de texto avanzados o
IDE, tales como PyCharm, Atom, Sublime o Visual Studio Code.

Cualquiera de ellos nos permitirá programar en Python con mayor facilidad, ya


que disponen de varias herramientas que nos servirán para comprobar la
sintaxis, detectar posibles errores en el código automáticamente o tener
nuestro código ordenado y estructurado en diferentes archivos y carpetas.

Para la realización de este módulo de Python avanzado usaremos


principalmente el IDE PyCharm, de modo que todos los alumnos podrán
seguir sin problema los ejemplos descritos en esta documentación, así como
las explicaciones del profesor en clase.

El alumno deberá traer un ordenador portátil a clase. Independientemente del


sistema operativo que se use (Windows, Mac o GNU/Linux), será necesario
tener instalado Python 3 (preferiblemente versiones 3.9 o superior) en el
sistema operativo. Sin estas plataformas o software no será posible realizar los
ejercicios que iremos viendo en clase, por lo que se trata de un requisito
obligatorio.

6
Comprobación del entorno de trabajo adecuado
Para asegurarnos de que el alumno tiene instalado en su ordenador todos los
recursos necesarios para seguir las clases desde el primer momento será
necesario realizar las siguientes comprobaciones.

Nota: Los siguientes pasos están orientados al sistema operativo Windows. Si


tu sistema operativo es Mac el proceso es prácticamente igual, cambia muy
poco y se pueden seguir los pasos de una forma muy similar. Si tu sistema es
GNU/Linux, probablemente ya sabes instalar Python desde la terminal. Si no,
me preguntáis.

Instalación de Python 3

1. En primer lugar, debemos descargar Python en su versión más reciente


desde la página oficial: https://www.python.org/downloads/

2. Ejecutar el instalador previamente descargado. Hay que asegurarse de


tener activados los dos checkbox de abajo, “Install launcher for all users

7
(recommended)” y “Add Python 3.10 to PATH”, y finalmente seleccionar
la opción “Customize installation”.

3. En esta pantalla seleccionaremos todos los checkbox y pulsamos el


botón “Next”.

8
4. En esta pantalla basta con seleccionar todos los checkbox indicados en
la siguiente imagen y pulsar el botón “Install”.

5. La instalación puede demorar unos minutos. Cuando esta finalice


veremos una pantalla similar a la siguiente imagen. Pulsamos el botón
“Close”.

9
Instalación de PyCharm

6. Debemos descargar PyCharm Community Edition (no descarguéis la


Professional) en su versión más reciente desde la página oficial:
https://www.jetbrains.com/es-es/pycharm/download/

7. Ejecutar el instalador previamente descargado y pulsar el botón “Next”.

10
8. Podemos seleccionar la ruta de instalación que queramos, pero
recomiendo dejar la ruta que viene por defecto, así será más fácil
localizar las carpetas de instalación en caso de duda o problemas.
Pulsamos el botón “Next”.

11
9. Basta con activar los checkbox que se indican en la siguiente imagen y
pulsamos el botón “Next”.

12
10. Pulsamos el botón “Install”.

13
11. La instalación puede demorar unos minutos. Cuando esta finalice
veremos una pantalla similar a la siguiente imagen. Seleccionamos la
opción “Reboot now” y pulsamos el botón “Finish”.

14
12. Una vez reiniciado el PC, si vamos a la barra de búsqueda de Windows
y escribimos “PyCharm Comunity Edition” veremos que nos aparece
como aplicación instalada. Pulsamos sobre ella y pulsamos sobre la
opción “Abrir” tal y como se muestra en la siguiente imagen.

15
Simple programa de ejemplo

13. Una vez que tenemos abierto PyCharm, seleccionamos en el menú de la


izquierda la opción “Projects” y luego pulsamos sobre “New Project”,
como se muestra en la siguiente imagen.

16
14. En “Location” escribiremos el nombre de nuestro primer proyecto, por
ejemplo “prueba_entorno”, para crear una simple aplicación de prueba.
Tiene que quedar de forma similar a como se muestra en la imagen, es
decir, tiene que aparecer una ruta como
“C:\Users\tu_usuario\PycharmProjects\prueba_entorno”.

También deberemos seleccionar la opción “Previously configured


interpreter” y pulsar sobre el botón con tres puntos “...” tal y como se
muestra en la siguiente imagen.

17
15. En esta pantalla debemos seleccionar del menú de la izquierda la
opción “System interpreter” y en el campo “Interpreter” debemos
seleccionar el ejecutable que se encuentra en “C:\Program
Files\Python310\python.exe”. Finalmente pulsamos el botón “OK”.

18
16. Ahora nos aparecerá en el combo desplegable que se muestra en la
siguiente imagen el intérprete “Python 3.10”, nos aseguramos que esté
seleccionado y activamos el checkbox “Create a main.py welcome
script”. Finalmente pulsamos el botón “Create”.

19
Create a main.py welcome script

17. En este momento se empezarán a descargar e indexar todos los


paquetes de Python. Dependiendo de la velocidad de vuestra conexión
a internet esto puede tardar más o menos.

20
18. También veremos por la parte inferior de la ventana una serie de
indicaciones que nos confirman que estamos usando Python 3.10 y
alguna barra de progreso en la que podemos ver cómo se indexan
algunos archivos del proyecto. Esto no debería preocuparte demasiado,
es mera información. Pero sí es importante que en la esquina inferior
derecha aparezca la versión 3.10 de Python (o superior).

19. En el menú superior vamos a seleccionar la opción “Run” y vamos a ver


cómo podemos configurar algunas cosas en el proyecto seleccionando
la opción “Edit Configurations…” del menú, tal y como se muestra en la
siguiente imagen.

20. En esta ventana, a priori no tenemos que configurar nada, solo la vamos
a inspeccionar para ver qué nos permite configurar. Por ejemplo, en el

21
campo “Script path” se configura la ruta donde se encuentra nuestro
script de Python que usaremos como “lanzador” o ejecutable principal.
en el campo “Python interpreter” aparece la versión de Python que
vamos a utilizar en este proyecto. Aquí debería aparecer Python 3.10,
pues ya lo hemos configurado anteriormente. Se pueden configurar
muchas otras opciones, pero de momento vamos a cerrar esta ventana
pulsando el botón “OK”.

21. El propio PyCharm nos ha generado un archivo llamado “main.py” con


un fragmento muy simple de código. Para ejecutarlo y comprobar que
todo nuestro entorno de programación en Python es adecuado
pulsaremos el botón que tiene una forma de triángulo verde (“Run”) tal y
como se muestra en la siguiente imagen.

22
22. Si todo ha ido bien, en la consola de la parte inferior veremos el
resultado de la ejecución de este script, y debe mostrar un mensaje que
diga “Hi, PyCharm”.

Clases y objetos
Con todo lo que hemos aprendido en el módulo de “Fundamentos de
programación en Python” ya podemos crear bastantes programas. Sin
embargo, no dejan de ser instrucciones estructuradas. Esto significa que
cuando queremos solucionar un problema tenemos que pensar de arriba a
abajo, y lo único que nos da un poco de juego son las funciones, las listas y
los diccionarios. Así era la programación en el pasado, bastante aburrida, con
mucho código, mucha gestión de recursos y muy difícil de mantener. Hasta
que poco a poco fue tomando forma un paradigma de programación conocido
como Programación Orientada a Objetos (POO).

Este paradigma o modelo de solución de problemas resultó muy útil para


satisfacer las necesidades de un mundo cada vez más tecnificado, en el que

23
cada vez más y más sectores se estaban informatizando y era necesario crear
una forma más sencilla de poder trasladar los problemas del mundo real a la
programación. La mejora fue muy importante, ya que las tediosas estructuras
no solo se podían replicar fácilmente en clases y objetos mucho mejor
ordenados, si no que estos además permitían manejar sus propios atributos y
funciones internas, llamadas métodos.

En esta nueva sección vamos a aprender cómo trabajar con objetos,


definiendo y manejando nuestras propias clases, descubriendo la herencia y
repasando algunos métodos nativos de las colecciones, haciéndolos si cabe
aún más potentes.

Programación estructurada Vs. POO


En este punto plantearemos un caso de estudio y trataremos de solucionarlo
mediante programación estructurada y luego con programación orientada a
objetos. Es importante ver la diferencia entre ambos paradigmas.

El caso de estudio será el siguiente: Nos piden crear un registro para manejar
los clientes de una empresa, con su nombre, sus apellidos y su DNI. El
programa debe permitirnos mostrar los datos de los clientes o eliminarlos a
partir de su DNI. En un primer momento podremos pensar que lo más
razonable es trabajar con ficheros o bases de datos, pero todavía no hemos
llegado a esa parte, de momento trabajaremos con almacenamiento de
información en memoria, es decir durante la ejecución del programa.

Lo primero que debemos hacer es plasmar de alguna manera la información


de los clientes, y para ello deberemos usar una combinación de dos
colecciones como son las listas y los diccionarios. Para comenzar a trabajar en
esta aplicación vamos a crear un archivo nuevo llamado clientes.py y
empezaremos por crear una variable llamada clientes que tendrá por valor una
lista. Esta lista contendrá dos elementos, y estos elementos serán diccionarios
con tres índices, nombre, apellidos y dni. Los cumplimentemos con unos datos
de ejemplo. Finalmente imprimimos la lista de diccionarios clientes para ver
que se han registrado correctamente:

clientes = [
{'nombre': 'Richard', 'apellidos': 'Stallman', 'dni': '01234567A'},
{'nombre': 'Aaron', 'apellidos': 'Swartz', 'dni': '98765432Z'}
]

print(clientes)

Tras ejecutar (“Run”) nuestro programa obtendremos la siguiente salida:

24
[{'nombre': 'Richard', 'apellidos': 'Stallman', 'dni': '01234567A'},
{'nombre': 'Aaron', 'apellidos': 'Swartz', 'dni': '98765432Z'}]

Ahora que tenemos un par de clientes, la primera funcionalidad que debemos


implementar en nuestro programa es la de mostrar la información de un cliente
en base a su DNI. Para ello, primero borraremos el print() que habíamos
puesto para mostrar el contenido de la lista clientes y a continuación
crearemos una función que necesite recibir como argumentos la lista de
clientes y el DNI del cliente a localizar dentro de la lista, por ejemplo:

def mostrar_cliente(clientes, dni):


for c in clientes:
if dni == c['dni']:
print('{c[“nombre”]} {c[“apellidos”]}')
return # Salimos de la iteración.

print('Cliente no encontrado.')

Ahora solo queda invocar la función de la siguiente manera y ejecutar el


programa:

mostrar_cliente(clientes, '01234567A')

Salida:

Richard Stallman

Muestra el nombre y apellidos del cliente que coincide con el DNI 01234567A.
Vamos a modificar el DNI que estamos poniendo a la hora de invocar la
función mostrar_cliente poniendo un DNI que no exista en nuestra
lista clientes, por ejemplo:

mostrar_cliente(clientes, '00000000B')

Salida:

Cliente no encontrado.

El bucle for completó todas las iteraciones buscando algún cliente con el
DNI 00000000B y como no se encontró ningún resultado se muestra por pantalla
el mensaje correspondiente. Se puede decir que ya tenemos una aplicación
con una funcionalidad o método muy rudimentario que se encarga de buscar
clientes dentro de una lista. Hagamos una nueva funcionalidad que se
encargue de borrar un cliente de la lista, para ello haremos una nueva función
llamada borrar_cliente que reciba también dos argumentos, la lista de clientes
y el DNI del cliente que se quiere borrar, por ejemplo:

25
def borrar_cliente(clientes, dni):
for i,c in enumerate(clientes):
if dni == c['dni']:
del clientes[i]
print(f'{c} --> Borrado')
return # Si se encuentra el cliente se sale de la
iteración.

print('Cliente no encontrado.')

En esta ocasión recorreremos la lista con un bucle for en el que iremos


registrando no solo cada elemento iterable mediante la variable local c si no
también el índice que ocupa en la lista mediante la variable i que obtendremos
al recorrer la lista dentro del método enumerate() de Python. Por cada iteración
se comprueba si el DNI proporcionado como argumento coincide con el DNI
de cada cliente en cada iteración. Si coinciden se ejecutará la sentencia del,
que eliminará ese elemento (diccionario) de l a lista de clientes, imprimirá un
mensaje e interrumpirá la iteración.

Después de la definición de la función borrar_cliente vamos a añadir


un print() para imprimir la lista de clientes antes de eliminar uno, a
continuación una llamada a la función borrar_cliente con un DNI de uno de los
clientes, y finalmente otro print() para imprimir la lista de clientes de nuevo y
poder ver que se ha borrado el cliente que coincidía con el DNI:

print(clientes)

borrar_cliente(clientes, '01234567A')

print(clientes)

Al ejecutar el programa tendremos el siguiente resultado:

[{'nombre': 'Richard', 'apellidos': 'Stallman', 'dni': '01234567A'},


{'nombre': 'Aaron', 'apellidos': 'Swartz', 'dni': '98765432Z'}]
{'nombre': 'Richard', 'apellidos': 'Stallman', 'dni': '01234567A'} → Borrado
[{'nombre': 'Aaron', 'apellidos': 'Swartz', 'dni': '98765432Z'}]

Ya tendríamos un programa con dos funcionalidades, las de mostrar y eliminar


clientes de una lista. Pero podríamos crear más funcionalidades como la de
editar los datos de un cliente o añadir más campos a los diccionarios. Con
esto nos basta para ver un ejemplo de cómo sería un programa de gestión de
clientes hecho con programación tradicional y estructurada, y funciona bien,
atiende a las necesidades del programa tal y como nos lo han pedido. Lo malo
de este tipo de programación es que a la larga se hace muy confuso porque se
generaría mucho código y esto es difícil de mantener. Además, como hemos
visto, tendríamos que estar moviendo constantemente la lista de clientes de un

26
lado a otro para poder manejarla y ejecutar acciones sobre ella, esto no es
muy práctico.

Vamos a ver la misma implementación de este programa utilizando el


paradigma de la programación orientada a objetos. Vamos a crear un archivo
nuevo llamado clientes_poo.py con el siguiente código:

class Cliente:

def __init__(self, dni, nombre, apellidos):


self.dni = dni
self.nombre = nombre
self.apellidos = apellidos

def __str__(self):
return f'{self.nombre} {self.apellidos}'

class Empresa:

def __init__(self, clientes=[]):


self.clientes = clientes

def mostrar_cliente(self, dni=None):


for c in self.clientes:
if c.dni == dni:
print(c)
return
print('Cliente no encontrado')

def borrar_cliente(self, dni=None):


for i, c in enumerate(self.clientes):
if c.dni == dni:
del(self.clientes[i])
print(f'{c} --> Borrado')
return
print('Cliente no encontrado')

No es necesario que se comprenda en este momento todo el contenido del


código, se irá viendo poco a poco e iremos haciendo uso de cada parte con la
correspondiente explicación.

Aquí podemos encontrar un objeto nuevo llamado clase, se identifica con la


palabra reservada class seguida del nombre que tendrá la clase. Los nombres
de las clases se suelen escribir con la primera letra en mayúscula. en este caso
hemos creado dos clases, una Cliente y otra Empresa. Ambas clases tienen en
su cuerpo una función interna llamada __init__(). Se trata de una función
especial, es el constructor de la clase, es decir, donde se va a otorgar valores
a los atributos de la clase. En el caso de la clase Cliente existen tres atributos,
que son el dni, nombre y apellidos. Para indicar que son atributos de la clase

27
deben llevar el prefijo self.. En el caso de la clase Empresa solo hay un
atributo clientes. Si este no se especifica tendrá como valor por defecto una
lista vacía.
En lugar de trabajar con funciones, como era el caso de la programación
estructurada, aquí cambia la sintaxis, cada clase tendrá sus propios métodos.
Lo primero que tenemos que hacer a continuación del código anterior es crear
un cliente en memoria, esto se realiza de la siguiente manera:

javier = Cliente(nombre='Javier', apellidos='Dominguez', dni='00000000A')

Hemos creado una variable llamada javier que tiene como valor un objeto o
instancia de la clase Cliente, y además se le ha indicado que el
atributo nombre debe tener por valor Javier, el atributo apellidos el
valor Dominguez y el atributo dni el valor 00000000A.

Vamos a crear otro cliente pero esta vez sin especificar el nombre de los
atributos, solo respetando el orden en el que la clase Cliente espera recibir los
argumentos, que son dni, nombre y apellidos:

beatriz = Cliente('11111111B', 'Beatriz', 'Gimenez')

Con estas dos nuevas líneas habremos creado dos instancias u objetos de la
clase Cliente, cada una con sus atributos personalizados.

Ahora vamos a crear una instancia u objeto de la clase Empresa, esta clase
necesita recibir como argumento una lista, lo haremos de la siguiente manera:

empresa = Empresa(clientes=[javier, beatriz])

Vamos a añadir una línea más para imprimir el atributo clientes de la instancia
de la clase Empresa que hemos llamado empresa:

print(empresa.clientes)

Si guardamos el contenido del programa y lo ejecutamos tendremos la


siguiente salida (los códigos hexadecimales que hacen referencia a la dirección
de memoria usada pueden variar):

[<__main__.Cliente object at 0x7fad555e9940>,


<__main__.Cliente object at 0x7fad555e9978>]

Lo que se imprime por pantalla es el atributo clientes del objeto empresa, es


decir, una lista de dos elementos de tipo object, son objetos de una clase, en
este caso concreto de la clase Cliente. Pero de esta manera no podremos ver
más que la dirección de memoria hexadecimal en la que fueron alojados
sendos objetos, ¿cómo podríamos acceder a su atributos? Como por ejemplo

28
el nombre, apellidos o el dni. La clase Empresa tiene un método
llamado mostrar_cliente que recibe por parámetro el dni de un cliente.

Para hacer uso de un método de una clase debemos poner el nombre del
objeto seguido de un punto (.) y el nombre del método con paréntesis y los
valores que correspondan dentro de los paréntesis, por ejemplo:

empresa.mostrar_cliente(dni='00000000A')

En este caso no hace falta meterlo dentro de la función print() ya que el


propio método hace uso de la función print() para imprimir por pantalla los
datos del cliente, véase un ejemplo de ejecución:

[<__main__.Cliente object at 0x7f3b32aaf9b0>,


<__main__.Cliente object at 0x7f3b32aaf9e8>]
Javier Dominguez

Ahora vamos a borrar un cliente, para ello escribiremos el nombre del


objeto empresa, seguido de un punto y el nombre del método de esa clase que
queremos invocar, en este caso el método borrar_cliente(), entre los
paréntesis debemos especificar el DNI del cliente que queremos borrar. Y
finalmente imprimimos el atributo clientes del objeto emrpesas, para que nos
imprima la lista actual de clientes y poder verificar que ya no está más el
cliente recién borrado:

empresa.borrar_cliente(dni='11111111B')
print(empresa.clientes)

Salida:

[<__main__.Cliente object at 0x7f3b32aaf9b0>,


<__main__.Cliente object at 0x7f3b32aaf9e8>]
Javier Dominguez
Beatriz Gimenez --> Borrado
[<__main__.Cliente object at 0x7f3b32aaf9b0>]

En este punto puede que no tengamos muy claro que está sucediendo por
detrás durante la ejecución del programa, pero podemos notar una sintaxis
más clara y auto explicativa que nos ayuda a comprender el programa. Tanto la
empresa como los clientes tienen su propia clase con sus atributos y sus
funciones. Podemos rellenar la lista de clientes de la empresa con objetos de
la clase cliente, cada uno con sus propios atributos y podemos hacer que
interaccionen unos objetos con otros entre clases. En resumen podríamos
decir que la programación orientada a objetos se basa en determinar las

29
entidades con nombres propios en lugar de crear estructuras y diccionarios
para representar la información.

Clases y objetos
En cualquier lenguaje de programación si hablamos de objetos es necesario
hablar también de clases. De hecho sin clases no habría objetos, ya que las
clases son los moldes de los objetos, en cierto modo como lo son un molde de
galletas y las propias galletas. Para ver algunos ejemplos vamos a crear un
nuevo archivo llamado clases_y_objetos.py y vamos a crear la siguiente clase
llamada Galleta que no contendrá nada, es una clase vacía, para ello usamos
la instrucción pass:

class Galleta:
pass

A continuación vamos a escribir estas dos líneas para crear dos objetos de
esta clase:

galleta_1 = Galleta()
galleta_2 = Galleta()

Se podría decir que hemos creado dos galletas galleta_1 y galleta_2 a partir
del molde Galleta. Cada vez que creamos un objeto de una clase lo que
estamos haciendo es instanciar un objeto. El concepto de instancia es muy
importante, ya que hace referencia a algo que existe en la memoria del
ordenador, pero solo mientras el programa se está ejecutando. Cuando el
programa finaliza toda esta información se libera de la memoria y desaparece.
Es lo contrario a una base de datos, en cuyo caso la información permanecerá
almacenada en la base de datos e incluso si el programa finaliza, es lo que se
denomina como persistencia de datos. Hablaremos de ello más adelante.

Podemos saber la clase de un objeto gracias a la función integrada type() a la


que debemos pasarle un objeto como argumento, por ejemplo:

print(type(galleta_1))

Salida:

<class '__main__.Galleta'>

Como podemos ver, a través de la función integrada type() podemos acceder


a la información que nos dice de qué tipo es este objeto, y en este caos nos
indica que se trata de un objeto de la clase Galleta. Pero podríamos usar el

30
método type() con otro tipo de objetos que ya conocemos, y siempre nos
devolverá el tipo de dato u objeto que se trata, veamos varios ejemplos:

print(type(10))
print(type(3.14))
print(type('Hola'))
print(type([]))
print(type({}))
print(type(True))

def hola():
pass

print(type(hola))

Salida:

<class '__main__.Galleta'>
<class 'int'>
<class 'float'>
<class 'str'>
<class 'list'>
<class 'dict'>
<class 'bool'>
<class 'function'>

Si nos fijamos en cada caso nos devuelve el nombre de la clase a la que


pertenece, y es que en Python en realidad todo son objetos de diferentes
clases. Cuando definimos el valor 10, o el valor 'Hola' en realidad estamos
creando objetos de las clases int y str. Esa es la razón de que algunas de las
clases que manejamos en Python tengan sus propios métodos. Todas las
clases en Python tienen un método en común, pero algunas clases como las
cadenas de carácteres, las listas o los diccionarios tienen su propios métodos
que podremos utilizar para manipularlas de forma más cómoda. Todos esos
métodos propios de estas clases vienen definidos en la propia definición de
clase de cada tipo de dato en el lenguaje Python.

Atributos y métodos de clase

Para estudiar este punto seguiremos con el ejemplo del molde de galletas y las
galletas que salen de él, no todas serán iguales. Siguiendo con la metáfora,
todas las galletas compartirán la misma forma y tamaño, pero pueden hacerse
de diferentes colores, sabores o ingredientes. He ahí que sus atributos tengan
diferentes valores y que cada galleta sea única.

La gracia de la programación orientada a objetos es que los objetos puedan


tener sus propios atributos, una especie de variables internas a las que nos
podremos referir con un puntito después del nombre del objeto y a

31
continuación el nombre del atributo. Estos atributos se pueden definir fuera de
la clase y se crearán automáticamente solo para esa instancia. Para poder
verlo vamos a crear un nuevo archivo llamado galletas.py con el siguiente
código:

class Galleta:
pass

g = Galleta()
g.sabor = 'salada'
g.color = 'amarillo'

print(f'Esta galleta es de color {g.color} y sabe {g.sabor}.')

Salida:

Esta galleta es de color amarillo y sabe salada

Al objeto g de la clase Galleta acabamos de asignar dos atributos que no tenía


cuando fue creado. No es una manera muy recomendable de hacerlo, ya que
si creamos un nuevo objeto de la clase Galleta este no tendría esos atributos
por defecto y tendríamos que gestionar si queremos que todas las galletas los
tengan o no, aunque tengan diferentes valores. Para ello es mejor definir los
atributos en el cuerpo de la clase, de modo que todos los objetos creados de
esta clase tendrán los mismos atributos, por ejemplo:

class Galleta:
sabor = 'salada'
color = 'amarillo'

g1 = Galleta()
g2 = Galleta()

print(f'La galleta 1 es de color {g1.color} y sabe {g1.sabor}.')


print(f'La galleta 2 es de color {g2.color} y sabe {g2.sabor}.')

Salida:

La galleta 1 es de color amarillo y sabe salada


La galleta 2 es de color amarillo y sabe salada

Como podemos ver, no es necesario asignar atributos cada vez que se crea un
objeto de la clase Galleta, ya se definen en la propia clase. Lo malo de este
método es que todas tendrán los mismos valores por defecto, por lo que sí
saldrían todas las galletas iguales.

Para que cada objeto tenga sus propios valores de atributos lo mejor es
definirlos en el momento que se crea el objeto, para ello es necesario conocer

32
dos conceptos nuevos de las clases, uno es el método especial
llamado __init__(), y el otro es la palabra reservada self. Vamos a desarrollar
de nuevo la clase introduciendo estos dos nuevos conceptos:

class Galleta:

def __init__(self, sabor, color):


self.sabor = sabor
self.color = color

El método especial __init()__ también se conoce como constructor de la


clase, es el método que se va a ejecutar siempre que instanciamos un objeto
de la misma, sin que sea necesario invocarlo. La palabra reservada self la
tienen todos los métodos de todas las clases y hace referencia al propio objeto
y sirve para diferenciar entre el ámbito de la clase y el de un método. Es un
requisito implícito en todos los métodos ya que por defecto al llamar a un
método se pasa automáticamente al propio objeto. Esto ocurre de forma
transparente en la llamada, pero es obligatorio en la definición.
Ahora debemos crear dos objetos de la clase Galleta, pero esta vez
deberemos indicar entre paréntesis los atributos que espera recibir
obligatoriamente el constructor de la clase, que en este caso son sabor y color:

g1 = Galleta('salada', 'amarilla')
g2 = Galleta('dulce', 'verde')

Añadimos dos líneas para imprimir un mensaje por pantalla donde se puedan
ver los atributos de las instancias g1 y g2, para acceder al dato escribiremos a
continuación del nombre del objeto un punto y el nombre del atributo de ese
objeto:

print(f'La galleta 1 es de color {g1.color} y sabe {g1.sabor}.')


print(f'La galleta 2 es de color {g2.color} y sabe {g2.sabor}.')

Al ejecutar el programa obtendremos la siguiente salida:

La galleta 1 es de color amarilla y sabe salada


La galleta 2 es de color verde y sabe dulce

Vamos a crear nuestros propios métodos de clase, por ejemplo un método


llamado chocolatear() y otro método que se llame tiene_chocolate() con el
siguiente código:

class Galleta:

def __init__(self, sabor, color):


self.sabor = sabor
self.color = color

33
self.chocolate = False

def chocolatear(self):
self.chocolate = True

def tiene_chocolate(self):
if self.chocolate:
print('Si tiene chocolate')
else:
print('No tiene chocolate')

Se ha tenido que añadir un nuevo atributo llamado chocolate al constructor con


un valor por defecto False. Si en algún momento del programa se invoca al
método chocolatear() el valor del atributo chocolate se cambiará a True, si no
permanecerá el valor por defecto.

Y si se invoca al método tiene_chocolate() se comprobará el valor del


atributo chocolate antes de imprimir un mensaje por pantalla diciendo si la
galleta tiene o no tiene chocolate. Veamos un ejemplo de las líneas de código
con algunas invocaciones y el resultado de su ejecución:

g1 = Galleta('salada', 'amarilla')
g2 = Galleta('dulce', 'verde')

print('\nEstado actual de las galletas')


g1.tiene_chocolate()
g2.tiene_chocolate()

print('\nAñado chocolate a la primera galleta ...')


g1.chocolatear()

print('\nEstado actual de las galletas')


g1.tiene_chocolate()
g2.tiene_chocolate()

Salida:

Estado actual de las galletas


No tiene chocolate
No tiene chocolate

Añado chocolate a la primera galleta ...

Estado actual de las galletas


Si tiene chocolate
No tiene chocolate

34
Como se puede comprobar, ahora tenemos clases, objetos, atributos y
métodos de clase que nos sirven para poder tener un control sobre el estado
de las cosas de una manera mucho más sencilla.

Métodos especiales

Aparte del método __init__() existen muchos otros métodos especiales en


Python. Para poder ver algunos ejemplos vamos a crear un nuevo archivo
llamado peliculas.py con el siguiente código en el que volveremos a ver el
constructor de la clase con tres atributos, titulo, duracion y lanzamiento:

class Pelicula:

# Constructor de clase.
def __init__(self, titulo, duracion, lanzamiento):
self.titulo = titulo
self.duracion = duracion
self.lanzamiento = lanzamiento
print('Se ha creado la película {self.titulo}.')

Ahora crearemos un objeto de esta clase pasándole como argumentos los


valores que espera recibir el constructor para sus atributos:

p = Pelicula('El padrino', 175, 1972)

Si ejecutamos el programa obtendremos la siguiente salida:

Se ha creado la película El padrino

Ya conocemos el método que hace de constructor de la clase, pero veamos


otro método especial que hace de destructor, es un método
llamado __del__() y únicamente se ejecutará cuando se borre una instancia:

# Destructor de la clase
def __del__(self):
print(f'Se está borrando la película {self.titulo}.')

Ahora creamos el objeto p de la clase Pelicula y a continuación borramos el


objeto mediante el método integrado del():

p = Pelicula('El padrino', 175, 1972)

del(p)

Al ejecutar el programa obtendremos la siguiente salida por pantalla:

Se ha creado la película El padrino


Se está borrando la película El padrino

35
Otro método especial es el método llamado __str__() que servirá para aquellas
ocasiones en las que queremos imprimir un objeto en su totalidad, en cuyo
caso ya no se mostrará una cadena con la dirección de memoria en la que está
almacenado el objeto, si no un mensaje personalizado. Por ejemplo:

# Redefinimos el método string


def __str__(self):
return f'{self.titulo} lanzada en {self.lanzamiento} con una
duración de {self.duracion} minutos.'

Ahora eliminamos la línea que escribimos anteriormente con la función


integrada del() y ponemos una línea nueva invocando a la función
integrada print() a la que le pasaremos el nombre de nuestro objeto p, por
ejemplo:

p = Pelicula('El padrino', 175, 1972)

print(p)

Al ejecutar el programa obtendremos la siguiente salida por pantalla.

Se ha creado la película El padrino


El padrino lanzada en 1972 con una duración de 175 minutos
Se está borrando la película El padrino

Si nos fijamos bien, se imprimen tres mensajes, el primero es el que se ejecuta


en el constructor, el segundo es nuestra instrucción de imprimir el objeto en
formato string y el tercero es el que se ejecuta al destruir el objeto. ¿Por qué se
ejecuta el tercer mensaje? Porque aunque no indiquemos en el código del
programa que queremos destruir el objeto, este se destruirá al finalizar el
programa, por lo tanto siempre se ejecutará. Existen muchos otros métodos
especiales, pero estos tres son los más utilizados.

Objetos dentro de objetos

En Python podemos crear objetos que tengan atributos cuyo valor pueden ser
objetos de otras clases. Al principio de esta sección hemos visto un ejemplo
en el que usábamos la clase Cliente y la clase Empresa. En este ejemplo el
objeto de la clase Empresa contenía una lista de objetos de la clase Cliente.
Ahora vamos a hacer un catálogo para poder manejar las películas del ejemplo
anterior. Modificamos ligeramente la clase Pelicula que teníamos definida en el
archivo peliculas.py del punto anterior. Eliminemos el método especial
destructor y cambiemos el mensaje que devuelve el método
especial __str__(), por ejemplo:

36
class Pelicula:
# Constructor de clase.
def __init__(self, titulo, duracion, lanzamiento):
self.titulo = titulo
self.duracion = duracion
self.lanzamiento = lanzamiento
print(f'Se ha creado la película {self.titulo}.')

# Redefinimos el método string


def __str__(self):
return f'{self.titulo} ({self.lanzamiento})'

Hagamos a continuación una nueva clase llamada Catalogo, esta clase tendrá
un atributo llamado peliculas y tendrá por valor una lista vacía. Además tendrá
un método constructor que inicializa la lista de películas vacía si no se le
especifica ninguna lista o con una ya existente si se especificara como
argumento a la hora de instanciar el objeto de la clase Catalogo. También
tendrá dos métodos, el método agregar() que simplemente hace un append a la
lista peliculas de la película que se le pase como argumento, y el
método mostrar(), que simplemente hace un print() del objeto película.

class Catalogo:
peliculas = []

def __init__(self, peliculas=[]):


self.peliculas = peliculas

def agregar(self, p):


self.peliculas.append(p)

def mostrar(self):
for p in self.peliculas:
print(p)

Ahora vamos a crear una película instanciando un objeto de la clase Pelicula,


para ello añadiremos a nuestro código la siguiente línea:
p = Pelicula('El padrino', 175, 1972)

Además crearemos una instancia de la clase Catalogo pasándole como


argumento una lista con nuestra películ p anteriormente creada:

c = Catalogo([p1])

Esta es una manera de crear una instancia u objeto de la clase Catalogo y


meter una instancia u objeto de la clase Película dentro. Pero también
podemos hacerlo de una forma en la que no sea necesario crear la instancia
previamente, si no directamente, por ejemplo, vamos a añadir una segunda
película al catálogo c invocando al método agregar() de nuestro catálogo:

37
c.agregar(Pelicula('El padrino II', 202, 1974))

Finalmente vamos invocar al método mostrar() de la clase Catalogo y


ejecutaremos el programa para ver que ha registrado correctamente las dos
películas:

c.mostrar()

Salida:

Se ha creado la película El padrino


Se ha creado la película El padrino II
El padrino (1972)
El padrino II (1974)

Podemos mejorar nuestro programa añadiendo métodos adicionales a la


clase Catalogo por ejemplo para borrar películas o modificar las que ya existen.

Encapsulación de atributos

Hasta ahora hemos visto cómo en Python se puede acceder a los atributos y
métodos de una clase de una manera muy sencilla. Esto es por que tal y como
los hemos visto tienen un acceso público, pero en algunas ocasiones podría
interesarnos que no fuera así, y que estos no se puedan ejecutar desde fuera,
en cuyo caso tendrían un acceso privado, que permanezcan encapsulados.

La encapsulación es una funcionalidad que tienen muchos lenguajes de


programación para impedir acceso externo a atributos y métodos. Pero Python
no ofrece esta funcionalidad de base, aún así, se puede simular un
comportamiento parecido precediendo el nombre de estos atributos o
métodos con dos guiones bajos (__). Para poder ver algún ejemplo vamos a
crear un nuevo archivo llamado encapsulacion.py en el que vamos a crear una
clase Ejemplo con un atributo privado llamado __atributo_privado y un método
privado llamado __metodo_privado():

class Ejemplo:
__atributo_privado = 'Soy un atributo inalcanzable desde fuera.'

def __metodo_privado(self):
print('Soy un método inalcanzable desde fuera.')

Ahora vamos a crear una instancia de esta clase y vamos a intentar acceder a
su atributo __atributo_privado desde fuera:

e = Ejemplo()

38
print(e.__atributo_privado)

Si ejecutamos el programa ahora obtendremos el siguiente error:

Traceback (most recent call last):


File "encapsulacion.py", line 10, in <module>
print(e.__atributo_privado)
AttributeError: 'Ejemplo' object has no attribute '__atributo_privado'

El error indica que no existe el atributo _atributo_privado, pero en realidad no


es así, si existe, solo que solo se puede invocar o utilizar dentro de la clase, no
desde un objeto ya instanciado, es decir, desde fuera. Borremos esta última
línea en la que invocamos desde fuera al atributo y probemos ahora a invocar
al método _metodo_privado() desde fuera también:

e.__metodo_privado()

Al ejecutarlo obtenemos este otro error:

Traceback (most recent call last):


File "encapsulacion.py", line 10, in <module>
e.__metodo_privado()
AttributeError: 'Ejemplo' object has no attribute '_metodo_privado'

El error es del mismo tipo, indica que no existe el método _metodo_privado(),


pero si existe, solo que es privado y también es solo accesible desde dentro
de la clase.

Para poder hacer uso de los atributos y métodos privados desde fuera
tendremos que crear un nuevo método público que haga de puente y que sea
él el que tiene acceso a los elementos privados desde dentro de la clase, por
ejemplo:

def mostrar_atributo(self):
return self.__atributo_privado

Eliminamos la anterior línea en la que intentábamos acceder al atributo o el


método privado y escribimos la siguiente línea en la que llamamos al método
público mostrar_atributo():

print(e.mostrar_atributo())

Si ejecutamos de nuevo el programa obtendremos el valor del atributo privado


correctamente:

Soy un atributo inalcanzable desde fuera.

39
Aplicando la misma técnica al método podremos acceder también al método
privado creando una nuevo método público mostrar_metodo() que haga de
puente, por ejemplo:

def mostrar_metodo(self):
return self.__metodo_privado()

Ahora invocamos a este nuevo método público mediante la siguiente línea:

e.mostrar_metodo()

Al ejecutar el programa de nuevo obtendremos la información del método


privado __metodo_privado() pasando por el método público mostrar_metodo():

Soy un método inalcanzable desde fuera.

Herencia

Una de las funcionalidades clásicas de la Programación Orientada a Objetos


(POO) es la Herencia, la capacidad que tiene una clase de heredar atributos y
métodos de otra, además de agregar los suyos propios o modificar los
heredados. De ahí la relación de Clases madre y Clases hijas, donde a las
primeras se las suele llamar Superclases y a la segundas Subclases. En esta
sección vamos a aprender cómo desarrollar la herencia de clases, cómo se
gestionan los atributos y métodos heredados, y otras peculiaridades.

Para verlo en un ejemplo vamos a crear una aplicación para la gestión de una
tienda en la que se venderán diferentes productos. Nuestro programa estará
compuesto por varios archivos con código fuente Python en el que vamos a ir
añadiendo diferente código. Empecemos por crear un archivo
llamado producto.py, aquí definiremos la clase Producto donde definiremos
todos los campos posibles relativos a los tipos de producto que podamos
crear, tales como alimentos o libros:

class Producto:
def __init__(self, referencia, tipo, nombre, pvp, descripcion,
productor=None, distribuidor=None, autor=None):
self.referencia = referencia
self.tipo = tipo
self.nombre = nombre
self.pvp = pvp
self.descripcion = descripcion
self.productor = productor
self.distribuidor = distribuidor
self.autor = autor

40
Este sería un buen comienzo para crear productos, de momento una clase
llamada Producto que solo tiene un método constructor __init__() en el que se
definirá el valor de unos atributos obligatorios,
como referencia, tipo, nombre, pvp y descripcion, y luego otros atributos
opcionales, como productor, distribuidor y autor.

Los atributos obligatorios son aquellos que tendremos que especificar siempre
que definamos una instancia u objetos de la clase, en este caso porque son
todos los atributos que todos los productos tendrán en común. Sin embargo
los opcionales podremos especificarlos o no, dependerá del tipo de producto
que queremos instanciar. En los casos en los que no especifiquemos estos
atributos opcionales se le asignará un valor por defecto None.
Vamos a crear un producto instanciando un objeto de esta clase, por ejemplo:

adorno = Producto('000A', 'Adorno', 'Jarrón', 15, 'Jarrón de porcelana con


dibujos')

Si queremos imprimir por pantalla el tipo y la descripción del adorno podremos


hacerlo llamando a los atributos tipo y descripcion de la siguiente manera:

print(adorno.tipo)
print(adorno.descripcion)

Salida:

Adorno
Jarrón de porcelana con dibujos

De momento tenemos algo que podría servirnos, pero no tiene mucho sentido
mezclar tantos atributos tan diferentes como isbn y productor. Además, al crear
un producto de cualquier tipo tendremos que recorrer y establecer valor a
todos los atributos. Esto es poco eficiente, por lo que necesitaremos
establecer de alguna manera una jerarquía y poner orden. La herencia de
clases nos puede servir en estos casos, para ello tendremos que dividir el
código en una Superclase y diferentes Subclases. En una Superclase
agrupamos todos los atributos que sean comunes para todos los productos,
por ejemplo en la clase Producto:

class Producto:
def __init__(self, referencia, nombre, pvp, descripcion):
self.referencia = referencia
self.nombre = nombre
self.pvp = pvp
self.descripcion = descripcion

A esta clase Producto podemos añadirle el método especial __str__() para que
devuelva una descripción del producto, por ejemplo:

41
def __str__(self):
return f"""\
REFERENCIA\t{self.referencia}
NOMBRE\t\t{self.nombre}
PVP\t\t{self.pvp}
DESCRIPCIÓN\t{self.descripcion}"""

Luego tendríamos que hacer tres Subclases, por


ejemplo Adorno, Libro y Alimento, y cada clase con su atributos, pero
inicialmente hagamos las clases sin contenido, solo con la palabra
reservada pass. Empecemos por la subclase Adorno. Para indicarle que es una
subclase de la clase Producto tenemos que añadirle el nombre de la clase
madre entre paréntesis:

class Adorno(Producto):
pass

Ahora crearemos un objeto o instancia de la clase Adorno, automáticamente


heredará todos los atributos y métodos de la clase madre Producto, veamos
algún ejemplo:

ad = Adorno('00000', 'Jarrón', 15.50, 'Jarrón de porcelana con dibujos')


print(ad)

Salida:

REFERENCIA 00000
NOMBRE Jarrón
PVP 15.5
DESCRIPCIÓN Jarrón de porcelana con dibujos

Vamos a crear la clase Alimento, esta clase a diferencia de la anterior si tiene un


par de atributos propios, estos los definiremos sin constructor. Al estar fuera
de un método de clase no es necesario indicar el prefijo self. Los definiremos
con valores vacíos de la siguiente manera:

class Alimento(Producto):
productor = ''
distribuidor = ''

Creamos una instancia de la clase Alimento pasándole solo los argumentos


obligatorios:

al = Alimento('00001', 'Aceite de Oliva', 5, 'Botella de aceite de oliva


virgen extra')

42
Añadimos valor a sus dos atributos e imprimimos el objeto:

al.productor = 'La aceitera'


al.distribuidor = 'Distribuciones S.A.'

print(al)

Salida:

REFERENCIA 00001
NOMBRE Aceite de Oliva
PVP 5
DESCRIPCIÓN Botella de aceite de oliva virgen extra

Si nos fijamos bien se imprimen solo los datos que se indican en la función
especial __str__() de la clase madre Producto, y este método especial no
incluye los atributos propios de cada subclase. Para solucionar esto podremos
redefinir el método especial __str__() en cada subclase, por ejemplo en el
caso de la subclase Alimento lo haríamos de la siguiente manera:

def __str__(self):
return f"""\
REFERENCIA\t{self.referencia}
NOMBRE\t\t{self.nombre}
PVP\t\t{self.pvp}
DESCRIPCIÓN\t{self.descripcion}
PRODUCTOR\t{self.productor}
DISTRIBUIDOR\t{self.distribuidor}"""

Si ejecutamos el programa de nuevo veremos que el método


especial __str__() de la subclase ha sobrescrito el de la superclase:

REFERENCIA 00001
NOMBRE Aceite de Oliva
PVP 5
DESCRIPCIÓN Botella de aceite de oliva virgen extra
PRODUCTOR La aceitera
DISTRIBUIDOR Distribuciones S.A.

Por último vamos a crear la subclase Libro que herede de la


superclase Producto. La vamos a crear basándonos en la clase anterior, con
sus propios atributos y su propio método especial __str__():

class Libro(Producto):
isbn = ''
autor = ''

def __str__(self):

43
return f"""\
REFERENCIA\t{self.referencia}
NOMBRE\t\t{self.nombre}
PVP\t\t{self.pvp}
DESCRIPCIÓN\t{self.descripcion}
PRODUCTOR\t{self.isbn}
AUTOR\t{self.autor}"""

Creamos un nuevo objeto o instancia de la subclase Libro, añadimos valor a


sus dos atributos propios e imprimimos el objeto:

li = Libro('00002', 'El enemigo conoce el sistema', 17.90, 'Libro sobre redes


de híper vigilancia')
li.isbn = '8417636390'
li.autor = 'Marta Peirano'

Salida:

REFERENCIA 00002
NOMBRE El enemigo conoce el sistema
PVP 17.9
DESCRIPCIÓN Libro sobre redes de híper vigilancia
PRODUCTOR 8417636390
AUTOR Marta Peirano

Métodos de las colecciones


En este punto revisaremos algunos de los métodos más utilizados en los tipos
de datos y colecciones que ya hemos visto anteriormente. Existen muchos
más métodos de los que se tratarán en este punto, pero se tratarán aquellos
que son más relevantes o más usados por la mayoría de los usuarios.

Para que el estudio de estos métodos sea más ágil y eficaz trabajaremos
desde la consola de Python en vez de escribir los ejemplos en un archivo y
ejecutar el programa desde PyCharm. De este modo se podrá experimentar en
tiempo real lo que sucede con los datos y al invocar los métodos que vayamos
utilizando.

Para acceder a la consola de Python basta con abrir un terminal (“Símbolo del
sistema” en Windows o “Terminal” en Mac y GNU/Linux) y escribir
simplemente python3, por ejemplo:

python3
Python 3.8.10 (default, Jun 22 2022, 20:18:18)
Type "help", "copyright", "credits" or "license" for more information.
>>>

44
Tras los tres carácteres >>> ya podemos empezar a escribir código Python. Si
quisiéramos salir bastaría con escribir el método exit() y pulsar intro:

>>> exit()

A continuación se pondrán algunos breves ejemplos de cada tipo de dato o


colección.

Métodos en cadenas de texto


Todos los caracteres alfabéticos en mayúsculas:

>>> 'Hola Mundo'.upper()


'HOLA MUNDO'

Todos los caracteres alfabéticos a minúsculas:

>>> 'Hola Mundo'.lower()


'hola mundo'

Primera letra del texto en mayúscula:

>>> 'hola mundo'.capitalize()


'Hola mundo'

Primera letra de cada palabra en mayúscula:

>>> 'hola mundo'.title()


'Hola Mundo'

Contabilizar el número de veces que aparece una subcadena o carácter dentro


de una cadena:

>>> 'Hola mundo'.count('o')


2
>>> 'Hola mundo'.count('mundo')
1

Buscar los índices de aparición de una subcadena, es decir, el lugar en el que


aparecen:

>>> 'Hola mundo'.find('mundo')


5

Buscar el índice de la última aparición de una subcadena:

45
>>> 'Hola mundo mundo mundo'.rfind('mundo')
17

Comprobar si una cadena de texto está compuesta únicamente por números:

>>> c = '100'
>>> c.isdigit()
True

Comprobar si una cadena de texto está compuesta por carácteres


alfanuméricos:

>>> c = 'abcd1234'
>>> c.isalnum()
True

Comprobar si una cadena de texto está compuesta únicamente por letras:

>>> c = 'abcd'
>>> c.isalpha()
True

Aquí podemos ver cómo devuelve un valor False porque el carácter “espacio”
que hay entre la palabra Hola y mundo no es una letra.

>>> c = 'Hola mundo'


>>> c.isalpha()
False

Comprobar si todos los carácteres son letras minúsculas:

>>> c = 'hola mundo'


>>> c.islower()
True

Comprobar si todos los carácteres son letras mayúsculas:

>>> c = 'HOLA MUNDO'


>>> c.isupper()
True

Comprobar si la primera letra de cada palabra es mayúscula:

>>> c = 'Hola Mundo'


>>> c.istitle()
True

Comprobar si una cadena está compuesta por espacios o tabulaciones:

46
>>> c = ' '
>>> c.isspace()
True

Comprobar si una cadena comienza por un carácter o subcadena concreta:

>>> c ='Hola mundo'


>>> c.startswith('H')
True
>>> c.startswith('Hola')
True

Comprobar si una cadena termina con un carácter o subcadena concreta:

>>> c.endswith('o')
True
>>> c.endswith('mundo')
True

Separar una cadena en una lista de subcadenas a partir un caracter que haga
de delimitador, por ejemplo el espacio:

>>> c ='Hola mundo mundo'


>>> c.split()
['Hola', 'mundo', 'mundo']

El mismo ejemplo pero con el carácter ; como delimitador:

>>> c ='aaa;bbb;ccc'
>>> c.split(';')
['aaa', 'bbb', 'ccc']

Añadir un carácter o subcadena entre cada carácter de una cadena, por


ejemplo una coma o un guión bajo:

>>> ','.join(c)
'H,o,l,a, ,m,u,n,d,o'
>>> '_'.join(c)
'H_o_l_a_ _m_u_n_d_o'

Eliminar todos los carácteres o subcadenas que aparezcan al inicio y al final de


una cadena, por ejemplo espacios:

>>> c =' Hola mundo '


>>> c.strip()
'Hola mundo'

O el mismo ejemplo con guiones medios:

>>> c ='--------Hola mundo---'

47
>>> c.strip('-')
'Hola mundo'

Reemplazar un carácter o una subcadena de una cadena, por ejemplo,


cambiar la letra o por ceros, o la palabra mundo por Javier:

>>> c ='Hola mundo'


>>> c.replace('o', '0')
'H0la mund0'
>>> c.replace('mundo', 'Javier')
'Hola Javier'

También podemos eliminar un caracter o una subcadena un número de veces:

>>> c ='Hola mundo mundo mundo mundo mundo'


>>> c.replace(' mundo', '', 3)
'Hola mundo mundo'

Métodos en listas
Añadir elementos a una lista:

>>> l = [1, 2, 3]
>>> l.append(4)
>>> l
[1, 2, 3, 4]

Eliminar todos los elementos de una lista:

>>> l = [1, 2, 3]
>>> l.clear()
>>> l
[]

Unir los elementos de dos listas en una:

>>> l1 = [1, 2, 3]
>>> l2 = [4, 5, 6]
>>> l1.extend(l2)
>>> l1
[1, 2, 3, 4, 5, 6]

Contar cuántas veces aparece un elemento en una lista:

>>> l = ['Hola', 'mundo', 'mundo']


>>> l.count('mundo')
2

48
Mostrar la posición del índice en la que aparece por primera vez un elemento
en una lista.

>>> l = ['Hola', 'mundo', 'mundo']


>>> l.index('Hola')
0
>>> l.index('mundo')
1

Insertar un elemento dentro de una lista en una posición indicada:

>>> l = [5, 10, 15, 25]


>>> l.insert(3, 20)
>>> l
[5, 10, 15, 20, 25]

Sacar un elemento en una posición indicada de una lista. Si no se indica


ninguna posición sacará el último elemento.

>>> l = [10, 20, 30, 40, 50]


>>> l.pop()
50
>>> l [10, 20, 30, 40]
>>> l.pop(1)
20
>>> l
[10, 30, 40]

Borrar un elemento de la lista indicando el propio elemento. Si hay varios solo


borra el primero:

>>> l = ['uno', 'dos', 'tres']


>>> l
['uno', 'dos', 'tres']
>>> l.remove('dos')
>>> l
['uno', 'tres']

Invertir el orden de los elementos de una lista:

>>> l = [1, 2, 3, 4, 5]
>>> l.reverse()
>>> l
[5, 4, 3, 2, 1]
>>> l = ['uno', 'dos', 'tres']
>>> l.reverse()
>>> l
['tres', 'dos', 'uno']

49
Ordenar elementos de una lista:

>>> l = [3, -15, 27, -9, 0]


>>> l.sort()
>>> l
[-15, -9, 0, 3, 27]
>>> l = ['bbb', 'eee', 'ttt', 'ccc']
>>> l.sort()
>>> l
['bbb', 'ccc', 'eee', 'ttt']

Métodos en conjuntos
Añadir elementos a un conjunto:

>>> c = set()
>>> c.add(1)
>>> c.add(2)
>>> c.add(3)
>>> c
{1, 2, 3}

Descartar o borrar un elemento específico de un conjunto:

>>> c = {1, 2, 3}
>>> c
{1, 2, 3}
>>> c.discard(2)
>>> c
{1, 3}
>>> c = {'uno', 'dos', 'tres'}
>>> c
{'dos', 'uno', 'tres'}
>>> c.discard('dos')
>>> c
{'uno', 'tres'}

Hacer una copia de un conjunto existente.

>>> c1 = {1, 2, 3}
>>> c2 = c1.copy()
>>> c2.discard(2)
>>> c1
{1, 2, 3}
>>> c2
{1, 3}

Los conjuntos tienen un método integrado propio copy(), no confundir con el

50
método copy() del módulo copy que importamos para hacer copias de objetos
de clases.
Vaciar o eliminar por completo todos los elementos de un conjunto:

>>> c = {1, 2, 3}
>>> c {1, 2, 3}
>>> c.clear()
>>> c
set()

Comprobar que un conjunto es disjunto de otro, es decir, que no hay ningún


elemento en común con otro conjunto:

>>> c1 = {1, 2, 3}
>>> c2 = {3, 4, 5}
>>> c3 = {-1, 99}
>>> c1.isdisjoint(c2)
False
>>> c1.isdisjoint(c3)
True

Comprobar si un conjunto es un subconjunto de otro:

>>> c1 = {1, 2, 3}
>>> c2 = {1, 2, 3, 4}
>>> c1.issubset(c2)
True

Comprobar si un conjunto es un superconjunto de un subconjunto:

>>> c1 = {1, 2, 3}
>>> c2 = {1, 2, 3, 4}
>>> c2.issuperset(c1)
True

Unión de dos conjuntos. Si hay elementos repetidos estos no se añaden varias


veces:

>>> c1 = {1, 2, 3, 4, 5}
>>> c2 = {3, 4, 5, 6, 7}
>>> c1.union(c2)
{1, 2, 3, 4, 5, 6, 7}

Pero esto no actualiza el valor de ningún conjunto, solo muestra por pantalla el
resultado de la unión. Si vemos lo que contienen los conjuntos c1 y c2 veremos
que no han mutado:

>>> c1
{1, 2, 3, 4, 5}
>>> c2

51
{3, 4, 5, 6, 7}

Para que se actualice el valor del primer conjunto con la unión de ambos
conjuntos como valor se ha de usar el método update() de la siguiente manera:

>>> c1 = {1, 2, 3, 4, 5}
>>> c2 = {3, 4, 5, 6, 7}
>>> c1.update(c2)
>>> c1
{1, 2, 3, 4, 5, 6, 7}

Encontrar elementos que no son comunes o que son distintos entre dos
conjuntos:

>>> c1 = {1, 2, 3}
>>> c2 = {3, 4, 5}
>>> c1.difference(c2)
{1, 2}
>>> c2.difference(c1)
{4, 5}

Actualizar los elementos de un conjunto con los no comunes de un segundo


conjunto:

>>> c1 = {1, 2, 3}
>>> c2 = {3, 4, 5}
>>> c1.difference_update(c2)
>>> c1
{1, 2}

Encontrar los elementos comunes entre dos conjuntos:

>>> c1 = {1, 2, 3}
>>> c2 = {3, 4, 5}
>>> c1.intersection(c2)
{3}

Al igual que antes con el método difference() este solo devuelve un resultado,
pero no actualiza el valor de ningún conjunto:

>>> c1 {1, 2, 3}
>>> c2 {3, 4, 5}

Actualizar los elementos de un conjunto con los elementos comunes de un


segundo conjunto:

>>> c1 = {1, 2, 3}
>>> c2 = {3, 4, 5}
>>> c1.intersection_update(c2)

52
>>> c1
{3}

Métodos en diccionarios
Obtener un valor por defecto cuando queremos acceder a una clave que no
existe en un diccionario:

>>> colores = {'amarillo': 'yellow', 'azul': 'blue', 'verde': 'green'}


>>> colores.get('amarillo', 'No se encuentra')
'yellow'
>>> colores.get('negro', 'No se encuentra')
'No se encuentra'

Obtener una lista con las claves de un diccionario:

>>> colores = {'amarillo': 'yellow', 'azul': 'blue', 'verde': 'green'}


>>> colores.keys()
dict_keys(['amarillo', 'azul', 'verde'])

Obtener una lista con los valores de las claves de un diccionario:

>>> colores = {'amarillo': 'yellow', 'azul': 'blue', 'verde': 'green'}


>>> colores.values()
dict_values(['yellow', 'blue', 'green'])

Obtener una lista de tuplas con la clave y valor de cada elemento de un


diccionario:

>>> colores = {'amarillo': 'yellow', 'azul': 'blue', 'verde': 'green'}


>>> colores.items()
dict_items([('amarillo', 'yellow'), ('azul', 'blue'), ('verde', 'green')])

Sustraer o eliminar una clave y su valor de un diccionario:

>>> colores = {'amarillo': 'yellow', 'azul': 'blue', 'verde': 'green'}


>>> colores.pop('amarillo')
'yellow'
>>> colores {'azul': 'blue', 'verde': 'green'}

Si quisiéramos extraer de un diccionario un elemento o registro que no existe,


por ejemplo negro, podríamos añadir al método pop() un texto a mostrar en
este caso:

>>> colores = {'amarillo': 'yellow', 'azul': 'blue', 'verde': 'green'}


>>> colores.pop('negro', 'No se encuentra')
'No se encuentra'

53
Vaciar o eliminar todos los elementos de un diccionario:

>>> colores = {'amarillo': 'yellow', 'azul': 'blue', 'verde': 'green'}


>>> colores {'amarillo': 'yellow', 'azul': 'blue', 'verde': 'green'}
>>> colores.clear()
>>> colores {}

Módulos y paquetes
Los módulos son archivos que contienen definiciones y declaraciones en
lenguaje Python. De esta manera es posible importarlos en otros scripts o
programas y reutilizar estas funcionalidades y a la vez conseguiremos crear
una jerarquía mucho más práctica en nuestros proyectos conteniendo los
módulos en paquetes.

En esta unidad volveremos a usar PyCharm, vamos a ver cómo podemos


crear nuestros propios módulos y paquetes en Python, así como su manejo, y
un repaso por algunos de los módulos standard más útiles.

Módulos
Vamos a crear un módulo que tendrá la única funcionalidad de saludar
mediante un mensaje por pantalla. Para ello crearemos un archivo
llamado saludos.py con la siguiente función:

def saludar():
print('Hola, te estoy saludando desde la función saludar del módulo
saludos')

Ahora crearemos en el mismo directorio otro archivo


llamado prueba_modulo.py donde importaremos el módulo saludos de la
siguiente manera:

import saludos

Una vez que ya tenemos importado el módulo saludos ya podemos hacer uso
de sus funciones, pero no podemos hacerlo del mismo modo que cuando
teníamos funciones definidas en nuestro archivo principal, en estos casos hay
que hacerlo anteponiendo el nombre del módulo y un punto (.), por ejemplo:

saludos.saludar()

54
Si ejecutamos nuestros programa test.py obtendremos la siguiente salida por
pantalla:

Hola, te estoy saludando desde la función saludar del módulo saludos

Para no tener que anteponer el nombre del módulo cada vez que queramos
invocar a una de sus funciones se puede importar la función o funciones
concretas de un módulo de la siguiente manera:

from saludos import saludar

saludar()

Salida:

Hola, te estoy saludando desde la función saludar del módulo saludos

Si tuviéramos muchas funciones en el módulo saludos y quisiéramos


importarlas todas podríamos cambiar el nombre de la función por un asterisco
(*):

from saludos import *

Pero esto podría no interesarnos cuando hay muchas funciones y solo


queremos usar unas pocas.

También podemos reutilizar clases definidas en el módulo saludos, por


ejemplo:

class Saludo():
def __init__(self):
print('Hola, te estoy saludando desde el __init__() de la clase
Saludo')

Ahora en el archivo test.py podríamos importar el módulo entero y hacer uso


de la clase Saludo de la siguiente manera:

import saludos
saludos.Saludo()

Salida:

Hola, te estoy saludando desde el __init__() de la clase Saludo

También podríamos importar solo la clase Saludo tal y como hemos visto antes:

from saludos import Saludo

55
Saludo()

Salida:

Hola, te estoy saludando desde el __init__() de la clase Saludo

A continuación veremos algunos de los módulos integrados en Python más


utilizados.

Collections

El módulo integrado collections de una serie de colecciones muy interesantes


que podremos utilizar para multitud de acciones bastante recurrentes. Para
poder hacer uso de sus colecciones podemos importar la colección en
cuestión de la siguiente manera:

from collections import Counter

Por ejemplo, la colección Counter nos devolverá un diccionario con el número


de veces que se repite cada uno de los elementos de una lista. Vamos a crear
un archivo llamado test_collections.py para ver algunos ejemplos:

l = [1, 2, 4, 3, 3, 5, 1, 3, 1, 1, 6]

print(Counter(l))

Salida:

Counter({1: 4, 3: 3, 2: 1, 4: 1, 5: 1, 6: 1})

En este resultado se puede observar que el número 1 aparece cuatro veces, el


número 3 tres veces, el número 2 una vez, etc.

Otro ejemplo en el uso de la colección Counter es contar las veces que aparece
un carácter en una cadena de caracteres o string:

p = 'Hola mundo!'
print(Counter(p))

Salida:

Counter({'o': 2, 'H': 1, 'l': 1, 'a': 1, ' ': 1, 'm': 1, 'u': 1, 'n': 1, 'd':
1, '!': 1})

En este caso el programa nos dice que el carácter 'o' aparece dos veces, el
carácter 'H' una vez, el caracter 'l' una vez, etc.

56
Podría darse el caso en el que tenemos una cadena de caracteres con una
varias palabras separadas por un espacio, por ejemplo:

s = 'rojo verde azul rojo morado rojo blanco blanco'

En este caso queremos saber cuántas veces aparece cada palabra, pero hay
que precisar que esto no es una lista de palabras, sino una cadena de
carácteres. Para resolverlo primero podemos pasar esta cadena de caracteres
compuesta de palabras separadas por espacio a una lista, y para ello
podemos usar la función integrada split(), al que si no le pasamos ningún
argumento tomará el carácter espacio por defecto:

print(s.split())

Salida:

['rojo', 'verde', 'azul', 'rojo', 'morado', 'rojo', 'blanco', 'blanco']

Ahora que ya tenemos cada palabra como un elemento de una lista ya


podemos utilizar la colección Counter del mismo modo que antes:

print(Counter(s.split()))

Salida:

Counter({'rojo': 3, 'blanco': 2, 'verde': 1, 'azul': 1, 'morado': 1})

De este modo obtendremos cuántas veces aparece cada palabra. Podríamos


aprovechar este contador de elementos para utilizar otra función integrada
llamada most_common(), a la que le hemos de pasar como argumento el número
de elementos que queremos que nos diga que son los más comunes, por
ejemplo 1:

n = [10, 20, 30, 40, 10, 20, 30, 10, 20, 10]

c = Counter(n)

print(c.most_common(1))

Salida:

[(10, 4)]

Nos dice que el elemento más común es el número 10, que aparece cuatro
veces. Si a la función integrada most_common() le pasamos como argumento el
número 2 nos devolverá los dos elementos más comunes:

57
print(c.most_common(2))

Salida:

[(10, 4), (20, 3)]

Otra colección del módulo collections muy interesante es OrderedDict. Los


diccionarios son colecciones de datos que muestran sus elementos o índices
desordenados. Con esta colección podremos mostrar los índices ordenados
por la posición en la que se van añadiendo. Por ejemplo:

from collections import OrderedDict

d = {'perro': 'dog', 'gato': 'cat', 'loro': 'parrot'}

print(OrderedDict(d))

Salida:

OrderedDict([('perro', 'dog'), ('gato', 'cat'), ('loro', 'parrot')])

De este modo no se altera el orden de los índices, siempre será el que se


definió en su creación y los que se vayan añadiendo si la ocasión lo requiere.
Una manera de comprobar esta ordenación es la siguiente:

d1 = {'perro': 'dog', 'gato': 'cat'}


d2 = {'gato': 'cat', 'perro': 'dog'}

print(d1 == d2)
print(OrderedDict(d1) == OrderedDict(d2))

Salida:

True
False

Datetime

Uno de los módulos más interesantes sin duda es datetime, que nos servirá
para manejar y trabajar con información relacionada con las fechas. Para
trabajar sobre este punto vamos a crear un nuevo archivo
llamado test_datetime.py y vamos a comenzar importando este módulo de la
siguiente manera:

import datetime

58
Ahora vamos a crear un objeto de tipo datetime en el que haremos uso del
subpaquete datetime y su método now():

dt = datetime.datetime.now()

print(dt)

Salida:

2022-10-11 14:10:50.879112

De esta manera la variable dt nos devuelve la fecha y hora actual. También


podemos acceder a cada uno de los atributos del objeto dt, como solo el año,
el mes, día, hora, minuto, segundo o microsegundos, de la siguiente manera:

print(dt.year)
print(dt.month)
print(dt.day)
print(dt.hour)
print(dt.minute)
print(dt.second)
print(dt.microsecond)

Salida:

2022
10
11
14
25
45
315492

Ahora que ya sabemos cómo podemos acceder a cada uno de los elementos
de los que se compone una fecha y hora podremos darle el formato que se
prefiera, por ejemplo:

print(f'{dt.year}/{dt.month}/{dt.day}')
print(f'{dt.hour}:{dt.minute}:{dt.second}.{dt.microsecond}')

Salida:

2022/10/11 15:16:11 307892

También podemos crear una fecha manualmente tal y como se muestra en el


siguiente ejemplo:

dt = datetime.datetime(2000, 1, 1, 0, 0)

59
print(dt)

Salida:

2000-01-01 00:00:00

En este tipo de dato datetime no se pueden sobrescribir los datos mediante la


asignación de un nuevo valor de forma standard, ya que los datos se
almacenan en una tupla, y las tuplas son inmutables. Por ejemplo, si queremos
cambiar el año asignando un nuevo valor al atributo year de la siguiente forma
nos daría el siguiente error:

dt.year = 3000

Salida:

Traceback (most recent call last):


File "test_datetime.py", line 5, in <module>
dt.year = 3000
AttributeError: attribute 'year' of 'datetime.date' objects is not writable

Pero el módulo datetime dispone de un método propio llamado replace() para


realizar este cambio, se haría de la siguiente manera:

dt = dt.replace(year=3000)

print(dt)

Salida:

3000-01-01 00:00:00

En el módulo datetime existe un método llamado isoformat() que convierte el


dato de tipo fecha a un standard ISO, por ejemplo:

dt = datetime.datetime.now()

print(dt.isoformat())

Salida:

2022-10-11T19:46:08.409881

Otro método para darle formato personalizado a la fecha y hora es el


método strftime(), por ejemplo:

print(dt.strftime('%A %d %B %Y %I:%M'))

60
Salida:

Monday 07 October 2019 07:52

%A es el día de la semana escrito en inglés, %d es el número de día del


mes, %B es el nombre del mes en inglés, %Y es el año con 4 cifras, %I es la hora
(en formato 12h, para formato 24h es %H) y %M son los minutos.
Si queremos que las fechas se muestren en español primero habría que
importar al inicio del código una librería llamada locale y configurar el lenguaje
en el que trabajará Python de la siguiente manera:

import locale

locale.setlocale(locale.LC_ALL, 'es_ES')

Si ahora volvemos a ejecutar el mismo programa aparecerá con el idioma


cambiado:

domingo 11 septiembre 2022 19:58

Se pueden usar diferentes códigos de idioma, por ejemplo en Chino:

locale.setlocale(locale.LC_ALL, 'zh_CN')

python3 test_datetime.py
星期一 11 十月 2022 20:01

Math

El módulo math integra una serie de funciones y métodos que nos servirán para
realizar algunas operaciones matemáticas de forma más sencilla. Al igual que
otros módulos será necesario importarlo al inicio de nuestro código, así que
vamos a hacerlo en un nuevo archivo llamado test_math.py:

import math

Una vez importado el módulo math ya tendremos disponibles todos sus


métodos. Veamos un ejemplo en el que trataremos de redondear un número
decimal. Si utilizamos el método round() que viene integrado en Python se
redondeará a la baja todos aquellos números decimales que sus decimales
sean menores de 5, por ejemplo:

print(round(1.4))

Salida:

61
1

Pero se redondeará todos aquellos números decimales que sus decimales


sean igual o mayor que 5, véase el ejemplo:

print(round(1.5))

Salida:

Gracias al método floor() del módulo math podremos forzar que el redondeo
sea siempre a la baja, por ejemplo:

print(math.floor(1.3))
print(math.floor(1.5))
print(math.floor(1.9))

Salida:

1
1
1

Sin embargo, si lo que se quiere es forzar un redondeo al alza debemos utilizar


el método ceil() del módulo math, por ejemplo:

print(math.ceil(1.00001))
print(math.ceil(1.3))
print(math.ceil(1.8))

Salida:

2
2
2

Otra funcionalidad interesante del módulo math es el método fsum(), que es un


sumatorio de una lista de números y que devuelve el resultado en
formato float, por ejemplo:

numeros = [1, 2, 3, 4, 5]

print(math.fsum(numeros))

Salida:

62
15.0

Si bien es cierto que existe un método integrado de Python llamado sum() que
ya hace un sumatorio de una lista de números, esta no es igual de eficaz, ya
que si se suman números enteros y flotantes tiene un comportamiento extraño,
por ejemplo:

numeros = [0.9999999, 1, 2, 3]

print(sum(numeros))
print(math.fsum(numeros))

Salida:

6.999999900000001
6.9999999

Como se puede ver el comportamiento en el caso de usar el método sum() no


siempre es estable, en cambio el método fsum() que integra el módulo math lo
resuelve correctamente.

También es interesante es el método trunc(), que trunca un número decimal y


devuelve la parte entera, por ejemplo:

print(math.trunc(3.14159265359))

Salida:

Ya sabíamos hacer potencias en Python con el doble asterisco (**) como


operador, pero el módulo math integra un método llamado pow() al que se le
han de pasar dos argumentos, el primero es la base y el segundo el
exponente, por ejemplo:

print(math.pow(2, 3))
print(math.pow(5, 4))

python3 test_math.py
8.0
625.0

También tenemos el método sqrt() que nos permitirá realizar raíces cuadradas,
por ejemplo:

print(math.sqrt(9))

63
Salida:

3.0

Además de métodos también tiene algunos atributos como las constantes del
número pi o el número e:

print(math.pi)
print(math.e)

Salida:

3.141592653589793
2.718281828459045

En realidad el módulo math tiene una gran cantidad de funcionalidades y


atributos, pero en este documento solo se explican las más utilizadas.

Random

El módulo random es un módulo que contiene varias herramientas o


funcionalidades para trabajar y generar números aleatorios. Se utiliza mucho
en el desarrollo de videojuegos o en desarrollos en los que se necesita cierto
grado de seguridad. Veamos algunos ejemplos en un nuevo archivo
llamado test_random.py. Lo primero que hay que hacer es importar el
módulo random de la siguiente manera:

import random

Ahora que ya tenemos el módulo importado podremos empezar a generar un


número aleatorio de forma sencilla con el siguiente ejemplo:

print(random.random())
print(random.random())
print(random.random())

Salida:

0.06823749155608883
0.9070119606268106
0.4445508707984924

De esta forma podremos generar números flotantes aleatorios menores o


iguales que uno y mayores de cero. Si quisiéramos números random entre un
rango de dos números podríamos usar el método uniform() y solo tendríamos

64
que pasar estos dos números como argumentos, por ejemplo números random
entre uno y diez:

print(random.uniform(1, 10))
print(random.uniform(1, 10))
print(random.uniform(1, 10))

Salida:

2.382198826548743
4.8697236381240865
3.8781201434396864

Otra manera de generar números enteros random entre cero y un número es


utilizando el método randrange(), por ejemplo, entre cero y diez:

print(random.randrange(10))
print(random.randrange(10))
print(random.randrange(10))

Salida:

8
1
4

También podemos pasarle dos números como argumentos para que devuelva
un número aleatorio entre esos números, por ejemplo:

print(random.randrange(0, 100))
print(random.randrange(0, 100))
print(random.randrange(0, 100))

Salida:

95
52
91

Y si añadimos un número 2 como tercer argumento solo nos sacará números


random pares entre cero y diez:

print(random.randrange(0, 100, 2))


print(random.randrange(0, 100, 2))
print(random.randrange(0, 100, 2))

Salida:

65
94
46
32

Además de poder usar el módulo random con números también podremos


usarlo con algunas colecciones como las cadenas de texto, en la que
podremos escoger una letra de forma aleatoria. Esto se consigue con el
método choice(), por ejemplo:

cadena = 'Hola mundo!'

print(random.choice(cadena))
print(random.choice(cadena))
print(random.choice(cadena))

Salida:

n
u
o

El método choice() también nos vale para listas, en este caso se obtendría de
forma aleatoria cualquiera de los elementos de la lista, por ejemplo:

lista = [1, 2, 3, 4, 5]

print(random.choice(lista))
print(random.choice(lista))
print(random.choice(lista))

Salida:

4
5
3

Además del método choice() también podremos usar un método


llamado shuffle() para desordenar los elementos de una lista y que
permanezcan guardados de ese modo en la lista de origen, mezcla los
elementos de forma aleatoria, por ejemplo:

lista = [1, 2, 3, 4, 5]

print(lista)

random.shuffle(lista)

print(lista)

66
Salida:

[1, 2, 3, 4, 5]
[3, 4, 1, 2, 5]

Por último también podremos usar un método llamado sample() al que le


podremos pasar una lista como argumento, y también un número de
elementos que queremos que nos devuelva de forma aleatoria, por ejemplo, si
quisiéramos que nos devolviese tres elementos de la lista de forma aleatoria
podríamos hacerlo de la siguiente manera:

lista = [1, 2, 3, 4, 5]

print(random.sample(lista, 3))
print(random.sample(lista, 3))
print(random.sample(lista, 3))

Salida:

[4, 5, 1]
[4, 2, 5]
[3, 4, 2]

Existen infinidad de ejemplos y un montón de métodos disponibles más en el


módulo random, estos solo son los más utilizados o los más conocidos.

Paquetes
Utilizar paquetes nos ofrece varias ventajas. En primer lugar nos permite
unificar distintos módulos bajo un mismo número de paquetes. Así podemos
utilizar jerarquías de módulos o submódulos y también subpaquetes. Por otra
parte nos permiten distribuir y manejar fácilmente nuestro código como si
fueran librerías instalables de Python. De este modo se pueden utilizar como
módulos standard desde el intérprete sin cargarlos previamente.

Para crear un paquete primero vamos a crear un nuevo directorio que tendrá
por nombre el nombre del paquete, en este ejemplo lo llamaremos
simplemente paquete. Dentro de este nuevo directorio vamos a crear un nuevo
archivo llamado __init__.py sin ningún contenido, el archivo vacío. Por último
vamos a copiar dentro del directorio paquete el archivo saludos.py que hicimos
anteriormente.

Ahora fuera de la carpeta paquete trabajaremos sobre el archivo test.py que


tenemos del punto anterior. En este archivo test.py vamos a importar el
paquete y sus módulos y clases de la siguiente manera:

67
from paquete.saludos import *

Si queremos utilizar la función saludar() de nuestro módulo saludos podemos


hacerlo del siguiente modo:

saludar()

Al ejecutar el programa obtendremos la siguiente salida por pantalla:

Hola, te estoy saludando desde la función saludar del módulo saludos

Y si queremos acceder a la clase Saludo debemos hacerlo de la siguiente


forma:

Saludo()

Salida:

Hola, te estoy saludando desde el __init__() de la clase Saludo

Manejo de ficheros
Hasta ahora todo lo que hemos visto son pequeños programas o scripts que
funcionan almacenando información como variables, constantes u objetos en
tiempo de ejecución, es decir, que solo existen mientras el programa se está
ejecutando. Pero hemos llegado a un punto en el que probablemente nos
interese almacenar algunos de los datos con los que hemos aprendido a
trabajar en algún fichero, de modo que al cerrar o apagar el programa estos
queden persistentemente en un archivo que luego podría volver a cargarse al
ejecutar el programa de nuevo, de ese modo no se perdería la información.

Python nos permite realizar las siguientes operaciones con ficheros:

● Creación
● Apertura o lectura
● Modificación
● Cierre

Creación

Comenzamos con la creación de un programa que escriba una línea de texto


en un archivo, de modo que si el archivo no existe lo cree. Para ello será

68
necesario crear una nuevo archivo llamado test_ficheros.py con el siguiente
código:

texto = 'Esta es una línea de texto.\nY esta es otra línea de texto.\n'

fichero = open('fichero.txt', 'w')

fichero.write(texto)
fichero.close()

En este ejemplo hemos creado una variable llamada texto con el las líneas de
texto que vamos a escribir en el fichero, y otra variable llamada fichero a la que
le hemos asignado como valor un objeto de tipo open() al que le hemos
pasado dos argumentos, el primero es el nombre del fichero con el que vamos
a trabajar, y el segundo argumento es la modalidad que vamos a utilizar, que
en este caso es w de escritura en inglés (write). Esta modalidad creará el
archivo si no existe y escribirá el texto dentro. Si el archivo existiera lo
sobrescribirá.

A continuación se invoca a al método write() del objeto fichero al que le


pasaremos como argumento nuestra variable texto, de este modo se escribirá
en el archivo que hemos especificado. Finalmente invocamos al
método close() del objeto fichero para cerrar el archivo una vez hemos
terminado de escribir en él.

Si ejecutamos el programa veremos que no hay salida por pantalla alguna,


pero si listamos los archivos que se encuentran en el directorio actual
podremos ver que se ha creado un archivo nuevo llamado fichero.txt, el cual
podremos abrir para ver qué contiene, y podremos comprobar que en su
interior aparece nuestras líneas de texto de la variable texto.

Apertura o lectura

Para abrir un fichero ya existente y leer su contenido se puede hacer de la


siguiente manera:

fichero = open('fichero.txt', 'r')

Con esta modalidad 'r' estaríamos abriendo el fichero en modo lectura (read).
Ahora podríamos almacenar en una variable llamada texto el contenido del
fichero, cerrar el fichero e imprimir el contenido de la variable texto de la
siguiente manera:

texto = fichero.read()

fichero.close()

69
print(texto)

Salida:

Esta es una línea de texto.


Y esta es otra línea de texto.

Como se puede ver, estaríamos almacenando en la variable texto el contenido


del fichero. Podemos hacer la prueba de editar "a mano" el contenido del
fichero y volver a ejecutar el programa, igualmente se leerán todas las líneas
del fichero.

Una manera de leer un archivo línea a línea es almacenando cada línea en una
lista. Esto se puede hacer con un método llamado readlines() que tienen los
objetos de tipo archivo, por ejemplo:

fichero = open('fichero.txt', 'r')


lineas = fichero.readlines()

print(lineas)

fichero.close()

Salida:

['Esta es una línea de texto.\n', 'Y esta es otra línea de texto.\n']

Modificación

Además de abrir un fichero y escribir en él un texto o leer, también podemos


abrir un fichero existente y añadir nuevas líneas. Esto se consigue mediante
una modalidad 'a' que añade líneas al final (append), por ejemplo:

fichero = open('fichero.txt', 'a')

fichero.write('Esta es una línea nueva.\n')


fichero.close()

Esta modalidad no solo sirve para añadir líneas al final del archivo, si no que
también lo crea si este no existe. Si abrimos el fichero fichero.txt veremos que
ha añadido al final de este una nueva línea con un texto.

Existe una manera más óptima de leer el contenido de un fichero, mediante la


sentencia with, veamos un ejemplo:

with open('fichero.txt', 'r') as fichero:

70
for linea in fichero:
print(linea.rstrip('\n'))

Salida:

Esta es una línea de texto.


Y esta es otra línea de texto.
Esta es una línea nueva.

Manejo del puntero

Cuando abrimos un fichero, el puntero es la posición en la que estaremos


posicionados para comenzar a leer, escribir o modificar. Dependiendo de la
modalidad que estemos empleando, el puntero estará por defecto al inicio del
fichero (archivo nuevo) o al final (añadir líneas nuevas). Pero existe un método
llamado seek() que nos permitirá mover el puntero a una posición que nosotros
queramos, por ejemplo, al décimo caracter de la primera línea:

fichero = open('fichero.txt', 'r')

fichero.seek(10)

texto = fichero.read()

fichero.close()

print(texto)

Salida:

a línea de texto.
Y esta es otra línea de texto.
Esta es una línea nueva.

El propio método read() que usamos para leer el contenido del fichero desde la
posición del fichero también tiene la posibilidad de recibir un argumento para
indicarle el número de carácteres que queremos leer o desplazar el puntero,
por ejemplo:

fichero = open('fichero.txt', 'r')


texto = fichero.read(6)

fichero.close()

print(texto)

Salida:

71
Esta e

Existe una modalidad que nos permite leer el archivo y además escribir en él,
pero ubicando el puntero en la primera posición, esta modalidad se define
mediante 'r+' de la siguiente manera:

fichero = open('fichero.txt', 'r+')

fichero.write('Incluyo esta línea al principio del fichero.\n')

texto = fichero.read()

fichero.close()

print(texto)

Esta modalidad lo que hace realmente es sobrescribir los primeros carácteres


que se encuentre desde la primera posición del puntero con los carácteres de
la nueva línea de texto que estamos añadiendo. Si abrimos el archivo después
de ejecutar nuestro programa Python veremos el cambio.

Si quisiéramos modificar el contenido de una línea en especial, por ejemplo de


la tercera línea, podríamos hacerlo de la siguiente manera:

fichero = open('fichero.txt', 'r+')


lineas = fichero.readlines()
lineas[2] = 'Línea modificada.'

fichero.seek(0)
fichero.writelines(lineas)
fichero.seek(0)

texto = fichero.read()

fichero.close()

print(texto)

Si abrimos el archivo de texto para ver los cambios veremos que se ha


posicionado en la tercera línea y la ha modificado sobreescribiendo los
caracteres existentes por los nuevos:

Esta es una línea de texto.


Y esta es otra línea de texto.
Línea modificada.a nueva.

72
Ficheros y objetos con pickle
Pickle es un módulo de Python que nos permite trabajar con ficheros binarios
en los que podremos guardar objetos y estructuras de datos complejas como
colecciones, y luego poder recuperarlos para trabajar con ellos. Lo primero que
hay que hacer para comenzar a utilizarlo es importar el módulo pickle en un
nuevo archivo llamado test_pickle.py:

import pickle

A continuación crearemos una lista con unos números y al igual que antes
creamos una variable llamada fichero que tendrá por valor el objeto de un
fichero abierto al que llamaremos lista.bin y lo haremos en modalidad de
escritura binaria 'wb', por ejemplo:

lista = [1, 2, 3, 4, 5]

fichero = open('lista.bin', 'wb')

Ahora hacemos un volcado de la lista al fichero binario mediante una llamada


al método dump() del módulo pickle y finalmente cerramos el archivo de la
siguiente manera:

pickle.dump(lista, fichero)

fichero.close()

Si ejecutamos el programa veremos que se ha creado un nuevo


archivo lista.bin. Si tratamos de abrirlo con un editor de texto veremos
carácteres extraños. Esto es porque es un archivo binario, no de texto plano,
pero el contenido del fichero es correcto, contiene nuestra lista.
Ahora veremos cómo podemos hacer para leer el fichero una vez lo hemos
generado y cómo poder recuperar nuestra lista. Primero abrimos el fichero en
modo lectura binaria ('rb') y luego creamos una variable llamada lista que
tenga por valor una llamada al método load() del módulo pickle al que le
pasamos el fichero como argumento. Cerramos el fichero e imprimimos el
contenido de lista:

fichero = open('lista.bin', 'rb')

lista = pickle.load(fichero)

fichero.close()

print(lista)

73
Salida:

[1, 2, 3, 4, 5]

Como se puede comprobar hemos recuperado la lista que teníamos guardada


en un archivo binario. De este modo podríamos almacenar cualquier tipo de
objeto, sea una lista o un diccionario o un objeto de una clase, y tendríamos
persistencia de datos.

Pandas
Pandas es una librería de Python creada específicamente para el análisis de
datos. Tiene un elemento clave denominada Dataframe, que no es más que
una serie de datos en una tabla donde podremos ver los registros en filas y
columnas ordenados por un índice. Cada una de las columnas puede tener un
tipo de dato diferente, por ejemplo datos de tipo entero, float, strings, objetos,
etc.

Instalación de Pandas

Si estamos utilizando Jupyter Notebook (software que viene con Anaconda


Navigator) no es necesario instalar Pandas, pues este ya viene instalado por
defecto. En ese caso podrías saltarte esta sección. Solo debemos aplicar los
siguiente pasos si decidimos no usar Jupyter Notebook y seguir con
PyCharm.

23. Para instalar Pandas desde PyCharm hacemos click sobre “Python
3.10”, que aparece en la esquina inferior derecha de la ventana.

24. En la ventana que emerge podremos ver en la opción “Python


Interpreter” la versión que estamos utilizando en este proyecto, que en
este caso es “Python 3.10”, y en la ventana de abajo se muestran

74
listados los paquetes que actualmente tenemos instalados y sus
versiones.

25. Para instalar un paquete nuevo hemos de hacer click sobre el botón “+”
tal y como se muestra en la siguiente imagen:

26. En la barra de búsqueda escribimos “pandas”, y nos aparecerán varios


paquetes en los que coincide el literal que buscamos. Hemos de
seleccionar el primero, el que se llama solo “pandas” y pulsamos el
botón “Install Package”.

75
27. Cuando se termine de instalar deberemos ver un mensaje que dice
“Package ‘pandas’ installed successfully” tal y como se muestra en la
siguiente imagen. Podemos cerrar esta ventana:

76
28. Ahora que volvemos a la ventana anterior, podemos ver que se han
instalado con éxito varios paquetes, además del paquete “pandas”. Esto
sucede porque existen dependencias entre paquetes y cuando instalas
uno muy concreto se suelen instalar otros adicionales necesarios para
que todo funcione correctamente. Finalmente pulsamos el botón “OK”.

Importación de Pandas

Por convención la importación de la librería Pandas en nuestro código se


realiza de la siguiente manera:

import pandas as pd

A partir de este momento podremos utilizar el alias pd para invocar cualquier


método de la librería Pandas. Por ejemplo, para importar un fichero CSV lo
haríamos de la siguiente manera:

df = pd.read_csv(

77
r'file.csv',
index_col=0,
nrows=5,
encoding='ISO-8859-1',
delimiter=';'
)

En este ejemplo se han utilizado algunos parámetros del método read-csv,


donde:

● r'file.csv' es el archivo CSV con el que se va a trabajar. La r del inicio


indica que la cadena de texto que hay dentro de las comillas se va a
tratar en crudo (raw). Este parámetro es obligatorio.
● index_col es el índice de la columna que queremos utilizar como índice.
Es opcional y si no se utiliza el dataframe mostrará como índice los
números del índice de cada registro.
● nrows es el número de registros que queremos leer del fichero. Es
opcional.
● encoding es el tipo de codificación de los datos. Es opcional.
● delimiter es el delimitador de datos empleado en el CSV. Es opcional,
pero si no se utiliza no delimitará los datos de cada columna.

Para ver un ejemplo real usaremos el dataset llamado Info_pais.csv de la


carpeta datasets con los siguiente atributos:

df = pd.read_csv(
r'datasets/Info_pais.csv',
encoding='ISO-8859-1',
delimiter=';',
decimal=','
)

Una vez definido el dataframe y almacenado en la variable df, si no estamos


haciendo uso del atributo nrows, podremos mostrar una cabecera con los 5
primeros elementos utilizando el método head() de la siguiente manera:

df.head()

Nota: Únicamente si no estamos usando Jupyter Notebook y en su lugar


estamos usando PyCharm u otro IDE similar, debemos añadir englobar
df.head() en un print(), quedando de la siguiente manera:

print(df.head())

78
A este método head() se le puede indicar dentro de los paréntesis el número de
registros que se quieren mostrar, por ejemplo para mostrar solo los 10 primeros
registros se haría de la siguiente manera:

df.head(10)

Ordenación de los datos

Ahora que ya tenemos importada la librería pandas e instanciado un dataframe


df podremos tratar o manipular la información de diferentes maneras. En este
caso vamos a ordenar los datos en base a una columna, por ejemplo la
columna Esperanza de vida, y lo haremos en orden ascendente de la siguiente
manera:

df_order = df.sort_values(
'Esperanza de vida',
ascending=True

79
)

El atributo ascending es opcional, si no se utiliza siempre es ascendente, pero


conviene ponerlo con valor True o False, según nos interese. En este ejemplo
probaremos a poner el valor True.

Si ahora mostramos la cabecera con los 5 primeros registros veremos que


están ordenados de menor a mayor según los datos de la columna Esperanza de
vida:

Instalación de Matplotlib

Al igual que el paquete Pandas, si estamos utilizando Jupyter Notebook


(software que viene con Anaconda Navigator) no es necesario instalar
Matplotlib, pues este ya viene instalado por defecto.

Solo debemos aplicar los siguiente pasos si decidimos no usar Jupyter


Notebook y seguir con PyCharm.

29. Para instalar Matplotlib desde PyCharm hacemos click sobre “Python
3.10”, que aparece en la esquina inferior derecha de la ventana.

30. En la ventana que emerge podremos ver en la opción “Python


Interpreter” la versión que estamos utilizando en este proyecto, que en
este caso es “Python 3.10”, y en la ventana de abajo se muestran

80
listados los paquetes que actualmente tenemos instalados y sus
versiones.

31. Para instalar un paquete nuevo hemos de hacer click sobre el botón “+”
tal y como se muestra en la siguiente imagen:

32. En la barra de búsqueda escribimos “matplotlib”, y nos aparecerán


varios paquetes en los que coincide el literal que buscamos. Hemos de
seleccionar el primero, el que se llama solo “matplotlib” y pulsamos el
botón “Install Package”.

81
33. La instalación de Matplotlib puede demorar un poco más de tiempo,
pues tiene bastantes dependencias. Cuando se termine de instalar
deberemos ver un mensaje que dice “Package ‘matplotlib’ installed
successfully” tal y como se muestra en la siguiente imagen. Podemos
cerrar esta ventana:

82
34. Ahora que volvemos a la ventana anterior, podemos ver que se han
instalado con éxito varios paquetes, además del paquete “matplotlib”.
Esto sucede porque existen dependencias entre paquetes y cuando
instalas uno muy concreto se suelen instalar otros adicionales
necesarios para que todo funcione correctamente. Finalmente pulsamos
el botón “OK”.

83
Visualización con Matplotlib

Matplotlib es una librería de visualización que tiene una gran variedad de


gráficos y que es fácilmente configurable. Más información acerca de los
gráficos disponibles en https://matplotlib.org/gallery/index.html. Para utilizar
esta librería debemos importarla de la siguiente manera:

import matplotlib.pyplot as plt

A partir de este momento podremos utilizar en nuestro código el alias plt para
acceder a todos los métodos de esta librería. Veamos un ejemplo en el que
cargaremos un array de datos para el eje x, llamado por ejemplo year, y otro
array de datos para el eje y, llamado por ejemplo value:

year = [2020, 2021, 2022]


value = [5, 6, 9]

Finalmente podremos generar un gráfico de líneas mediante el método plot()


de la librería matplotlib, al que le tendremos que pasar como argumentos

84
primero el array de datos que queremos utilizar en el eje x y segundo el array
que usaremos para el eje y:

plt.plot(year, value)

Nota: Únicamente si no estamos usando Jupyter Notebook y en su lugar


estamos usando PyCharm u otro IDE similar, debemos añadir la siguiente
línea:

plt.show()

Si quisiéramos generar otro tipo de gráfico con los mismos datos podríamos
utilizar por ejemplo el método scatter() al que también hay que pasarle como
argumentos los datos de los ejes x e y:

plt.scatter(year, value)

85
Esta sería una manera de crear gráficos muy sencillos a partir de un par de
listas con datos, pero normalmente se suelen utilizar fuentes de datos más
grandes y complejas como son los dataframes que hemos visto anteriormente.
Para visualizar con la librería matplotlib la información de un dataframe
podemos hacerlo de la siguiente manera. Primero importamos el dataframe, en
este caso uno llamado hum_temp.csv con algunos datos random relativos a
humedad y temperatura:

import matplotlib.pyplot as plt


import pandas as pd

df = pd.read_csv(
r'../datasets/hum_temp.csv',
encoding='ISO-8859-1',
delimiter=';'
)

Comprobamos que se ha cargado correctamente:

df

86
A continuación usamos el método plot() de la instancia plt al que le
pasaremos el nombre del dataframe, en este caso df y entre corchetes el
nombre de la columna que queremos utilizar para representarlo en el eje y, si
no se especifica el eje x en este se utilizarán los valores del índice de los
registros de datos:

plt.plot(df['temperature'])

Como se puede ver, los datos de la columna temperature aparecen en el eje y y


el índice de los datos en el eje x.

Veamos una manera mejor de representar los datos de este dataframe, en este
caso usaremos el método plot() con el dataframe df de la siguiente manera:

df['humidity'].plot()

87
Si eliminamos el nombre de la columna entre corchetes y especificamos
únicamente el nombre del dataframe df podremos ver en el mismo gráfico
todos los registros de cada columna en líneas diferentes, cada una con un color
distinto.

df.plot()

88
Ejemplo Standard & Poor's 500

Veamos otro ejemplo en el que vamos a trabajar con otro dataset. En este caso
vamos a ver la evolución de la cotización de un índice bursátil como el
“Standard & Poor's 500”.

Primero importamos pandas y matplotlib.pyplot y a continuación creamos un


dataframe llamado df_sp500 en el que importamos el archivo CSV
SP500_data.csv de la siguiente manera:

import matplotlib.pyplot as plt


import pandas as pd

df_sp500 = pd.read_csv(
r'../datasets/SP500_data.csv',
encoding='ISO-8859-1',
delimiter=','
)

Si imprimimos la cabecera podremos visualizar los 5 primeros registros:

df_sp500.head()

Para visualizar este dataframe primero importamos la librería matplotlib de la


siguiente manera:

import matplotlib.pyplot as plt

Ahora vamos a representar la columna del cierre bursátil de cada día, columna
Close:

df_sp500['Close'].plot()

89
En este gráfico podremos ver la evolución del índice bursátil, pero si nos
fijamos en el eje x ha representado el índice de cada registro. Para poder
representar en el eje x la fecha del dato debemos especificar que el índice del
dataframe df_sp500 ha de ser la columna Date, de la siguiente forma:

df_sp500.index = df_sp500['Date']

Si volvemos a mostrar la cabecera veremos que ahora se han insertado en el


índice los valores del campo Date:

df_sp500.head()

volvemos a representar el gráfico de nuevo con la instrucción de antes:

df_sp500['Close'].plot()

90
Ahora vemos que en el eje x se representan los valores del campo o columna
Date.

Ejemplo COVID-19 en España por comunidades autónomas

En este otro ejemplo vamos a trabajar con un dataset que he obtenido de este
site. Se tratan de los casos detectados de COVID-19 por comunidades
autónomas en España.

Primero importamos pandas y matplotlib.pyplot y a continuación creamos un


dataframe llamado df_covid19_ccaas en el que importamos el archivo CSV
datos_ccaas.csv de la siguiente manera:

import matplotlib.pyplot as plt


import pandas as pd

df_covid19_ccaas = pd.read_csv(
r'../datasets/datos_ccaas.csv',
encoding='ISO-8859-1',
delimiter=','
)

Si imprimimos la cabecera podremos visualizar los 5 primeros registros:

df_covid19_ccaas.head()

91
Estableceremos tal y como hemos visto en el ejemplo anterior el campo fecha
como índice y los datos de la columna num_casos en el eje y de la siguiente
manera:

df_covid19_ccaas.index = df_covid19_ccaas['fecha']
df_covid19_ccaas['num_casos'].plot()

Ejemplo esperanza de vida frente a renta per cápita por países

En este ejemplo vamos a utilizar el caso de uso que vimos anteriormente en el


que teníamos la esperanza de vida frente a la renta per cápita por países.
Vamos a ver si existe una correlación entre estas dos variables.

import matplotlib.pyplot as plt


import pandas as pd

df = pd.read_csv(
r'datasets/Info_pais.csv',
encoding='ISO-8859-1',

92
delimiter=';',
decimal=','
)

Ahora pondremos los datos en orden ascendente según los valores de la


columna Poblacion de la siguiente manera:

df_order = df.sort_values(
'Poblacion',
ascending=False
)

df_order.head()

Es el momento de crear el gráfico mediante el método scatter().


Representaremos en el eje x los datos de la columna Renta per capita y en el
eje y los datos de la columna Esperanza de vida.

plt.scatter(df_order['Renta per capita'], df_order['Esperanza de vida']);

93
De momento se aprecia un gráfico que nos da una idea aproximada de cómo
se ven los datos. Podemos añadir un título y etiquetas a los ejes x e y del
gráfico de la siguiente manera:

plt.scatter(
df_order['Renta per capita'],
df_order['Esperanza de vida']
)
plt.title('Renta per cápita vs Esperanza de vida')
plt.xlabel('Renta per cápita')
plt.ylabel('Esperanza de vida');

En el gráfico que acabamos de generar los puntos aparecen uniformes. Vamos


a configurar el gráfico para que sean proporcionales tanto en tamaño como en
color para cada país. Para ello debemos crear una nueva columna llamada por
ejemplo df_order['Poblacion_normalizada'] que normalice frente al máximo de
población, por lo que le asignaremos como valores el valor de la columna
Población dividido entre el valor máximo de esta columna Población, de este
modo lo estaríamos escalando o normalizando.

df_order['Poblacion_normalizada'] =
df_order['Poblacion']/max(df_order['Poblacion'])

Así, el país que tenga la población más alta quedaría escalado a 1 y el resto de
países quedarían normalizados en base a este valor máximo.

Existen grandes diferencias en el número de habitantes entre unos países y


otros, por ejemplo China con 1200 millones y otros que podrían tener 50000.

94
Para evitar que un país con esta gran cantidad de habitantes inunde el gráfico
es recomendable que en vez de dividir la población de cada país entre el
máximo de población, hacer la división del máximo entre 10000, para no tener
un factor tan elevado.

df_order['Poblacion_normalizada'] =
df_order['Poblacion']/(max(df_order['Poblacion'])/10000)

df_order.head()

Ahora podremos usar esta nueva columna con los datos escalados para crear
una visualización de los datos mucho más potente.

Es el momento de generar un nueva visualización utilizando la nueva columna


de datos normalizados que hemos generado, por ejemplo con el siguiente
código:

plt.scatter(
df_order['Renta per capita'],
df_order['Esperanza de vida'],
s=df_order['Poblacion_normalizada']
)
plt.title('Renta per cápita vs Esperanza de vida')
plt.xlabel('Renta per capita')
plt.ylabel('Esperanza de vida');

Para modificar el tamaño hemos utilizado el parámetro s (size) y como valor


usamos la columna Poblacion_normalizada.

95
El problema que vemos es que la visualización es muy pequeña, pero podemos
mejorar esto añadiendo a nuestro código lo siguiente para aumentar las
pulgadas de nuestro gráfico:

plt.scatter(
df_order['Renta per capita'],
df_order['Esperanza de vida'],
s=df_order['Poblacion_normalizada']
)
plt.title('Renta per cápita vs Esperanza de vida')
plt.xlabel('Renta per cápita')
plt.ylabel('Esperanza de vida')

fig = plt.gcf()
fig.set_size_inches(14.5, 10);

96
De este modo se ve mucho más grande. Si fuera necesario se pueden cambiar
los valores de la función set_size_inches() por otros más adecuados para
ajustar el tamaño.

Si nos fijamos bien, ahora se representa cada burbuja de cada país de un


tamaño diferente, dependiendo del valor que tenga en la nueva columna que
hemos generado con los datos normalizados.

Ahora vamos a modificar el color. Para ello debemos añadir el atributo c (color),
a continuación del atributo s (size), y como valor vamos a usar de nuevo la
columna Poblacion_normalizada. Quedaría del siguiente modo:

plt.scatter(
df_order['Renta per capita'],
df_order['Esperanza de vida'],
s=df_order['Poblacion_normalizada'],
c=df_order['Poblacion_normalizada']
)
plt.title('Renta per cápita vs Esperanza de vida')
plt.xlabel('Renta per cápita')
plt.ylabel('Esperanza de vida')

fig = plt.gcf()

97
fig.set_size_inches(14.5, 10);

En esta nueva visualización vemos que ya no solo se representa cada país en


un tamaño diferente, sino que también en un color en función de la población.

También podremos añadir la etiqueta del nombre del país dentro de cada
burbuja utilizando el método annotate(), por ejemplo añadiendolo solo a los 10
primeros países con mayor población, pero antes es necesario arreglar los
índices utilizando el método reset_index y pasándole los parámetros drop con
valor True y inplace con valor True también. A continuación el código:

plt.scatter(
df_order['Renta per capita'],
df_order['Esperanza de vida'],
s=df_order['Poblacion_normalizada'],
c=df_order['Poblacion_normalizada']
)
plt.title('Renta per cápita vs Esperanza de vida')
plt.xlabel('Renta per cápita')
plt.ylabel('Esperanza de vida')

fig = plt.gcf()
fig.set_size_inches(14.5, 10);

98
df_order.reset_index(drop=True, inplace=True)

for i in range(0, 10):


plt.annotate(
df_order['País'][i],
(
df_order['Renta per capita'][i],
df_order['Esperanza de vida'][i]
)
)

Por último, podemos añadir en la generación del scatter una propiedad llamada
alpha con valor 0.5 para añadir transparencia a los círculos, así podremos ver
los datos que se puedan ocultar a sobreponerse unos encima de otros.

plt.scatter(
df_order['Renta per capita'],
df_order['Esperanza de vida'],
s=df_order['Poblacion_normalizada'],
c=df_order['Poblacion_normalizada'],
alpha=0.5
)
plt.title('Renta per cápita vs Esperanza de vida')

99
plt.xlabel('Renta per cápita')
plt.ylabel('Esperanza de vida')

fig = plt.gcf()
fig.set_size_inches(14.5, 10);

df_order.reset_index(drop=True, inplace=True)

for i in range(0, 10):


plt.annotate(
df_order['País'][i],
(
df_order['Renta per capita'][i],
df_order['Esperanza de vida'][i]
)
)

De este modo hemos creado una visualización de los datos muy potente y de
una manera muy sencilla. La conclusión que podemos sacar de este gráfico es
que efectivamente existe una correlación entre la renta per cápita y la
esperanza de vida según el país. Podemos ver que conforme la renta per
cápita aumenta la esperanza de vida también aumenta. También podemos
deducir que el número de población no afecta a la esperanza de vida, ya que
en la visualización que hemos creado se pueden ver países con una gran

100
cantidad de población que no están entre los valores más bajos en cuanto a
esperanza de vida se refiere.

NumPy
NumPy es una librería de Python enfocada en el cálculo numérico que nos
permite realizar operaciones de una manera sencilla y rápida. Su objeto base
es un vector de números denominado Array. Es una alternativa a las listas que
hemos visto hasta ahora y nos va a permitir realizar una serie de funciones muy
potentes. A diferencia de las listas, donde se opera de forma independiente en
cada uno de los elementos, en los Arrays las operaciones se van a realizar
sobre todo el Array simultáneamente.

Importación de NumPy

Por convención la importación de la librería NumPy en nuestro código se


realiza de la siguiente manera:

import numpy as np

A continuación se muestra una diferencia entre listas y Arrays de NumPy a la


hora de realizar una suma de elementos, por ejemplo, tenemos estas dos
listas:

a = [1, 2, 3]
b = [4, 5, 6]

Si sumamos las dos listas obtenemos lo siguiente:

a + b

Salida:

[1, 2, 3, 4, 5, 6]

Como se puede ver, no suma los elementos, solo concatena la lista b a


continuación de la lista a. Veamos cómo se comparta una suma de Arrays con
el mismo ejemplo:

a = np.array([1, 2, 3])

101
b = np.array([4, 5, 6])

a + b

Salida:

array([5, 7, 9])

En este caso se han sumado cada uno de los elementos del Array a con los
elementos del array b.

Otra gran diferencia respecto a las listas tradicionales es que en un Array solo
se admite un tipo de dato, normalmente numérico. Además NumPy nos va a
servir como base de cálculo para otras librerías como Pandas o SciKit Learn.

Ejemplo Índice de Masa Corporal (IMC)

En este ejemplo vamos a calcular el índice de masa corporal sobre los valores
peso y altura de tres personas.

altura = [1.7, 1.65, 1.82]


peso = [67, 55, 72]

El cálculo que hay que hacer para obtener el IMC es dividir el peso entre el
cuadrado de la altura:

peso / altura**2

Si quisiéramos calcular el IMC mediante listas tradicionales podríamos hacerlo


fácilmente usando la función zip(), iterar a través de un bucle for e ir
añadiendo los resultados mediante compresión de listas del siguiente modo:

[p / a**2 for p, a in zip(peso, altura)]

Salida:

[23.18339100346021, 20.202020202020204, 21.736505252988767]

El cálculo está bien hecho, pero de este modo es más complicado, entre otras
cosas porque el cálculo se va haciendo secuencialmente elemento a elemento
entre las listas, y en este caso no es mucho problema ya que son listas de solo

102
3 elementos, pero podría ser un problema al trabajar con listas de miles de
elementos. Se realizar el mismo cálculo de una manera mucho más eficiente
usando los Arrays de NumPy, veamos el ejemplo:

np_altura = np.array([1.7, 1.65, 1.82])


np_peso = np.array([67, 55, 72])

imc = np_peso / np_altura**2


imc

Salida:

array([23.183391 , 20.2020202 , 21.73650525])

Además este tipo de objetos Array de NumPy son iterables del mismo modo
que lo son algunas colecciones estándar de Python como las listas, tienen
algunas propiedades como los slices que nos permiten navegar dentro del
Array y acceder a determinados elementos ubicados en ciertas posiciones.

Otro uso interesante de los Arrays es que podemos evaluar todos los
elementos del Array con una simple operación, como por ejemplo saber qué
valores son mayores que 21:

imc > 21

Salida:

array([ True, False, True])

En este caso el primer resultado es True puesto que se cumple que 23.183391
es mayor que 21, el segundo es False porque 20.2020202 no es mayor que 21 y
el tercero es True puesto que 21.73650525 si es mayor que 21.

Si quisiéramos obtener solo los elementos del array que cumplen la condición
anterior podremos hacerlo de la siguiente manera:

imc[imc > 21]

Salida:

array([23.183391 , 21.73650525])

103
Ejemplo cálculo áreas de triángulos

En este ejemplo vamos a calcular masivamente con Arrays de NumPy el área


de varios triángulos y quedarnos solo con aquellas áreas que sean > 6.5. Para
calcular el área de un triángulo hay que multiplicar la base por la altura y
dividirlo entre dos.

base * altura / 2

Para ello tenemos los siguiente dos Arrays:

bases_tri = np.array([2, 2.37, 3.05, 1.75, 4, 2.81])


alturas_tri = np.array([1.21, 2.6, 4.4, 7.03, 4.01, 5.25])

Ahora multiplicamos las bases por las alturas y lo dividimos entre 2 de la


siguiente manera:

areas_tri = bases_tri * alturas_tri / 2


areas_tri

Salida:

array([1.21 , 3.081 , 6.71 , 6.15125, 8.02 , 7.37625])

Como solo nos interesan las áreas que son > 6.5 podremos obtener los valores
que cumplan la condición de la siguiente manera:

areas_tri[areas_tri > 6.5]

Salida:

array([6.71 , 8.02 , 7.37625])

También podemos hacer una conjunción de condiciones, por ejemplo para


obtener solo las áreas que son > 6.5 y también < 8, para ello usaremos el AND
lógico mediante el símbolo & de la siguiente manera:

areas_tri[(areas_tri > 6.5) & (areas_tri < 8)]

104
Salida:

array([6.71 , 7.37625])

Arrays de dos dimensiones en NumPy

Con NumPy también podremos crear un Array de dos dimensiones. En realidad


es una matriz de m filas por n columnas.

La sintaxis para crearla es la siguiente:

nombre_array = np.array([[valores_fila_1],
[valores_fila_2],
[valores_fila_m]])

Veamos cómo crear un array bidimensional como este:

a = np.array([[2, 7, 8], [4, 8, 10]])


a

Salida:

array([[ 2, 7, 8],
[ 4, 8, 10]])

105
Si quisiéramos obtener el valor 10 de nuestro array de dos dimensiones
tendríamos que especificar el índice de la fila seguido del índice de la columna,
teniendo en cuenta que los índices siempre empiezan por el número 0. En este
ejemplo el valor 10 se encuentra en la fila con índice 1 y la columna con índice
2.

a[1, 2]

Salida:

10

Ahora supongamos que queremos obtener todas las filas pero solo los valores
de las columnas primera y segunda. En este caso tendríamos que especificar
en primer lugar que queremos todas las filas mediante los dos puntos :, y a
continuación un slice 0:2 para indicar solo las columnas desde el índice 0 hasta
el 1, ya que en un slice el número que se indica al final no se muestra, es
donde se para.

a[:, 0:2]

Salida:

array([[2, 7],
[4, 8]])

Cálculo estadístico con NumPy

Para realizar cálculo estadístico NumPy nos ofrece una gran variedad de
funciones muy útiles y potentes. En esta sección veremos algunas de ellas que
nos pueden servir para solucionar algunos cálculos que hemos visto en puntos
anteriores de una manera mucho más sencilla y rápida.

Por ejemplo, supongamos que tenemos el siguiente Array con varios registros
de temperaturas de una ciudad:

temperaturas = np.array([12, 13.5, 13, 14, 13.2, 14.8, 15, 15.16, 16, 16.2,
15.7, 17, 17.2, 16.8, 14, 14.2, 14.7, 16, 17.5])

106
Podríamos calcular la media o promedio usando la función mean(array) de la
siguiente manera:

np.mean(temperaturas)

Salida:

15.050526315789472

La mediana usando la función median(array):

np.median(temperaturas)

Salida:

15.0

Podremos obtener el valor mínimo con la función min(array).

np.min(temperaturas)

Salida:

12.0

Y el valor máximo con la función max(array).

np.max(temperaturas)

Salida:

17.5

Para calcular la varianza podremos usar la función var(array) de la siguiente


manera:

np.var(temperaturas)

107
Salida:

2.2893207756232683

La desviación estándar podremos obtenerla usando la función std(array) de la


siguiente manera:

np.std(temperaturas)

Salida:

1.5130501563475245

También tenemos la función percentile(array, k) a la que tendremos que


pasarle como argumentos el array y también el percentil que queremos
obtener, por ejemplo el 90%:

np.percentile(temperaturas, 90)

Salida:

17.04

Esto lo que quiere decir es que en el array de temperaturas no se supera la


temperatura 17.04 en el 90% de los casos, o dicho de otro modo, solo el 10% de
los datos supera la temperatura de 17.04.

Generación de datos random con NumPy

Una cualidad de NumPy muy interesante es que nos permite generar datos
random tomando como partida diferentes parámetros estadísticos, como por
ejemplo una media y una desviación estándar, y generar un Array de valores
con dicha distribución estadística. La función es random.normal() necesita que le
pasemos como argumentos una media, la desviación estándar y un número de
muestras. Su sintaxis es la siguiente:

nombre_array = np.random.normal(
media,
desviacion_estandar,
numero_muestras
)

108
Por ejemplo, si quisiéramos

array_gauss = np.random.normal(2, 0.5, 1000)

Como resultado tendremos un Array de 1000 elementos con la distribución


estadística que hemos insertado. Podemos comprobar la media y la desviación
estándar para ver que los datos se aproximan mucho:

media = np.mean(array_gauss)
media

Salida:

2.00020348250122

desviacion = np.std(array_gauss)
desviacion

Salida:

0.496490256867947

Si visualizamos los datos se ven de la siguiente manera:

import matplotlib.pyplot as plt


import scipy.stats as stats

plt.scatter(
array_gauss,
stats.norm.pdf(array_gauss, media, desviacion)
)

109
Bases de datos
En Python también podremos conectarnos a diferentes bases de datos, en las
que podremos realizar consultas y también podremos escribir o modificar
contenido. En este punto veremos cómo conectarnos a algunas de las más
utilizadas en la industria, como son MongoDB y SQL Server.

MongoDB

Se trata de una base de datos NoSQL orientada a documentos. En lugar de


guardar los datos en tablas, tal y como se hace en las bases de datos
relacionales, MongoDB guarda estructuras de datos BSON (una especificación
similar a JSON) con un esquema dinámico, haciendo que la integración de los
datos en ciertas aplicaciones sea más fácil y rápida.

Crear un nuevo proyecto Mongo en la nube


Para poder practicar con algunos ejemplos necesitaremos tener disponible una
base de datos MongoDB. Aunque podemos instalar MongoDB en nuestro Pc,
vamos a crear una instancia en la nube, de ese modo aprenderemos también
el proceso de creación desde cero y cómo podremos conectarnos a través de
internet.

1. Primero iremos a la web https://www.mongodb.com/cloud/atlas/register


donde hemos de registrarnos.

110
2. Pulsamos el botón “New Project”.

3. Al proyecto lo llamaremos “TestProject” y pulsamos el botón “Next”.

4. Pulsamos el botón “Create Project”.

111
5. Ahora pulsamos el botón “Build a Database”.

6. En esta pantalla pulsamos el botón “Create” en la opción “Starting at


FREE”.

112
7. Seleccionamos la opción “FREE Shared”.

8. Ahora seleccionamos la región (país) recomendado.

113
9. Pulsamos el botón “Create Cluster”.

10. Tenemos que pulsar en “Username and Password” para crear los
datos de acceso.

11. En el campo “Username” ponemos “testuser” y en el campo


“Password” ponemos “testpass”. Si se tratara de una base de datos en
un entorno productivo es altamente recomendable usar un usuario y
password más seguras, pero estas credenciales nos valdrán para hacer
una prueba rápida. Pulsamos el botón “Create User”.

114
12. Para poder establecer comunicación entre nuestro PC y la base de
datos MongoDB que estamos creando en la nube será necesario añadir
la dirección IP pública que estamos utilizando en este momento, así que
pulsamos el botón “Add My Current IP Address”.

13. Pulsamos el botón “Finish and close”.

115
14. Ahora pulsamos el botón “Connect”.

15. Aquí debemos pulsar sobre la opción “Connect your application”.

16. En el combo “DRIVER” seleccionamos “Python” y en el combo


“VERSION” seleccionamos “3.6 or later”. Marcamos el check “Include
full driver code example” y lo que se nos muestra a continuación es un
ejemplo de cómo debería ser el código Python para conectarnos a

116
nuestra base de datos MongoDB recién creada en la nube. Podemos
copiar este fragmento de código para usarlo más adelante.

Instalación de librerías necesarias


Para poder conectarnos a una base de datos MongoDB desde Python
necesitamos instalar tres librerías: pymongo, dnspython y certifi.

1. Para instalar pymongo desde PyCharm hacemos click sobre “Python


3.10”, que aparece en la esquina inferior derecha de la ventana.

2. En la ventana que emerge podremos ver en la opción “Python


Interpreter” la versión que estamos utilizando en este proyecto, que en
este caso es “Python 3.10”, y en la ventana de abajo se muestran
listados los paquetes que actualmente tenemos instalados y sus
versiones.

117
3. Para instalar un paquete nuevo hemos de hacer click sobre el botón “+”
tal y como se muestra en la siguiente imagen:

4. En la barra de búsqueda escribimos “pymongo”, y nos aparecerán


varios paquetes en los que coincide el literal que buscamos. Hemos de
seleccionar el primero, el que se llama solo “pymongo” y pulsamos el
botón “Install Package”.

118
5. Cuando se termine de instalar deberemos ver un mensaje que dice
“Package ‘pymongo’ installed successfully” tal y como se muestra en la
siguiente imagen. Podemos cerrar esta ventana:

6. Por último, hay que instalar los paquetes “dnspython” y “certifi”


siguiendo los mismos pasos que hemos realizado en la instalación del
paquete “pymongo”.

119
Conexión y creación de una base de datos MongoDB
Podremos conectarnos a MongoDB desde Python mediante el siguiente
fragmento de código.

import certifi
from pymongo import MongoClient

user = 'testuser'
password = 'testpass'
cluster = 'cluster0.h5a1riz.mongodb.net'
params = '?retryWrites=true&w=majority'
ca = certifi.where()

def get_database(database_name):
connection_string = f'mongodb+srv://{user}:{password}@{cluster}/{params}'
client = MongoClient(connection_string, tlsCAFile=ca)

return client[database_name]

if __name__ == '__main__':
database = get_database('user_shopping_list')

Los datos de las variables user, password, cluster y params los obtenemos del
último punto de la sección “Crear un nuevo proyecto Mongo en la nube” de
esta documentación.

En la función get_database usaremos la variable connection_string para crear la


cadena de conexión a Mongo. En este ejemplo crearemos una nueva base de
datos llamada user_shopping_list pasándole este nombre como parámetro a la
función get_database.

En nuestro dashboard de Mongo en la nube todavía no aparece la base de


datos. Es normal, no aparecerá hasta que creemos una nueva colección en la
base de datos e insertemos algunos datos (documentos).

120
Crear una colección
Para crear una colección dentro de nuestra nueva base de datos basta con
añadir la variable collection al final del código anterior, de modo que quede
así:

if __name__ == '__main__':
database = get_database('user_shopping_list')
collection = database['user_1_items']

Para este ejemplo hemos llamado user_1_items a la colección.

Insertar datos (documentos)


Ahora es el momento de insertar algunos datos en nuestra colección
user_1_items de la base de datos user_shopping_list. Para ello añadiremos al
final del código anterior los siguientes datos:

item_1 = {
'_id': 'U1IT00001',
'item_name': 'Blender',
'max_discount': '10%',
'batch_number': 'RR450020FRG',
'price': 340,
'category': 'kitchen appliance'
}

item_2 = {
'_id': 'U1IT00002',
'item_name': 'Egg',
'category': 'food',
'quantity': 12,
'price': 36,
'item_description': 'brown country eggs'
}

collection.insert_many([item_1, item_2])

Si ejecutamos el código de nuestro programa y todo va bien veremos el


siguiente mensaje en la terminal:

Process finished with exit code 0

121
Ahora ya podremos ir al Dashboard de nuestra cuenta de MongoDB en la
nube, y veremos que se ha creado correctamente la base de datos, la
colección y los datos:

Si pinchamos en la colección “user_shopping_list” del menú izquierdo


aparecerán los dos documentos o registros que hemos insertado.

Ejecutar consultas en una colección


Una vez que tenemos una colección en MongoDB podemos iterar a través de
sus documentos con un simple bucle for haciendo uso del método find() de
la colección de la siguiente manera:

for item in collection.find():


print(item)

Obtendremos la siguiente salida:

122
{'_id': 'U1IT00001', 'item_name': 'Blender', 'max_discount': '10%',
'batch_number': 'RR450020FRG', 'price': 340, 'category': 'kitchen
appliance'}
{'_id': 'U1IT00002', 'item_name': 'Egg', 'category': 'food', 'quantity':
12, 'price': 36, 'item_description': 'brown country eggs'}

Modificar datos de un documento


Para modificar datos de un documento primero tendremos que hacer una
consulta (query) en la colección actual y luego modificar los nuevos valores
que queramos cambiar de la siguiente manera:

query = {'_id': 'U1IT00002'}


new_values = {
'$set': {
'price': 35
}
}

collection.update_one(query, new_values)

En este caso hemos buscado una colección que en el campo _id tenga el valor
U1IT00002. Luego hemos establecido un nuevo valor 35 para el campo price de
este documento utilizando el método update_one() de la colección.

Modificar datos de varios documentos


Modificar datos de varios documentos se hace de una forma similar. Primero
hay que realizar una consulta que pueda devolver varios documentos, por
ejemplo con una expresión regular como la siguiente:

query = {
'_id': {'$regex': '^U1IT'}
}

Esta expresión regular en la consulta devolverá todos los documentos en los


que el campo _id comience por la cadena de carácteres U1IT.

123
A continuación estableceremos un nuevo valor para el campo price en todos
los documentos que devuelva la consulta anterior.

new_values = {
'$set': {
'price': 7
}
}

Finalmente ejecutamos el método update_many() de la colección:

collection.update_many(query, new_values)

SQL Server

Instalación de SQL Server


Para hacer esta parte del módulo nos podremos conectar o bien a una base de
datos SQL Server ya existente en algún servidor de la red o bien a una base de
datos local. En este último caso hemos de descargar e instalar en nuestro PC
el software SQL Server.

1. Descargamos el instalador de SQL Server desde la página oficial de


Microsoft:
https://www.microsoft.com/es-es/sql-server/sql-server-downloads. Aquí
tendremos que buscar la opción “Desarrollador” y pulsar sobre el botón
“Descargar ahora”.

124
2. Tendremos que descargar en nuestro PC un archivo instalador “.exe”.
Una vez descargado lo ejecutamos. En la primera ventana
seleccionaremos la opción “Básica”.

3. En la siguiente ventana hemos de aceptar el acuerdo de licencia.

4. Después pulsaremos sobre el botón “Instalar” y comenzará la


instalación.

125
5. El proceso de instalación puede demorar unos minutos.

126
6. Cuando la instalación termine pulsaremos el botón “Cerrar”.

7. Ahora debemos activar el protocolo TCP/IP del servicio SQL Server que
acabamos de instalar en nuestro PC local. Para ello, en la barra de
búsqueda de Windows escribimos “Administrador de configuración de
SQL Server” y pulsamos en “Abrir”.

127
8. Una vez abierto el Administrador de configuración de SQL Server
desplegamos el “Configurador de red de SQL Server” y pinchamos
sobre “Protocolos de MSSQLSERVER”. En la pantalla derecha hacemos
click con el botón derecho del ratón sobre “TCP/IP” y seleccionamos la
opción “Habilitar”.

9. Los cambios no tendrán efecto hasta que se reinicie el servicio SQL


Server. Para reiniciarlo debemos pinchar sobre la opción “Servicios de
SQL Server”, en la pantalla derecha hacer click con el botón derecho del
ratón sobre “SQL Server (MSSQLSERVER)” y pinchar sobre la opción
“Reiniciar”.

Instalación Azure Data Studio


1. Descargar Azure Data Studio desde la web oficial de Microsoft:
https://learn.microsoft.com/en-us/sql/azure-data-studio/download-azur
e-data-studio?view=sql-server-ver16

128
2. Ejecutar el instalador .exe descargado. En la primera ventana
aceptamos el acuerdo de licencia.

3. Luego dejamos la ruta de instalación por defecto y pulsamos el botón


“Siguiente”.

129
4. En la siguiente ventana dejamos todo por defecto y pulsamos el botón
“Siguiente”.

5. En esta ventana dejamos activado solo el check “Agregar a PATH” y


pulsamos el botón “Siguiente”.

130
6. Ahora pulsamos el botón “Instalar” para iniciar la instalación.

131
7. Finalmente pulsamos el botón “Finalizar”.

Crear una base de datos


Ahora es el momento de conectarnos desde Azure Data Studio a nuestro
servicio de SQL Server que tenemos instalado y corriendo en nuestro PC. El
objetivo en este punto es crear una nueva base de datos llamada “ucmdb”.

1. Una vez se abra Azure Data Studio crearemos una nueva conexión.

132
2. En los parámetros de conexión escribimos únicamente “localhost” en el
campo “Server” y pulsamos el botón “Connect”.

3. Una vez esté establecida la conexión pulsamos sobre el botón “New


query”.

4. Primero escribimos la siguiente consulta SQL y después pulsamos en el


botón “Run”.

CREATE DATABASE ucmdb;


USE ucmdb;

133
Crear una tabla e insertar datos en ella
Ahora es el momento de crear una tabla donde podamos añadir datos.

5. Para crear una tabla llamada “alumnos” escribimos la siguiente


consulta SQL y pulsamos el botón “Run”.

CREATE TABLE ucmdb.dbo.alumnos (Nombre varchar(255), Apellidos varchar(255));

6. Ahora vamos a añadir datos en la tabla recién creada. Para ello


escribimos las siguientes consultas SQL y pulsamos el botón “Run”.

INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Mari Carmen', 'García');


INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Antonio', 'Rodríguez');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('María', 'González');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Manuel', 'Fernández');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Carmen', 'López');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Jose', 'Martínez');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Ana María', 'Sánchez');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Francisco', 'Pérez');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('María Pilar', 'Gómez');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('David', 'Martín');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Laura', 'Jiménez');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Juan', 'Hernández');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Josefa', 'Ruíz');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Javier', 'Díaz');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Isabel', 'Moreno');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('Jose Antonio', 'Muñoz');
INSERT INTO ucmdb.dbo.alumnos (Nombre, Apellidos) VALUES ('María Dolores',
'Álvarez');

134
Todos los nombres y apellidos utilizados en este ejemplo son ficticios, han sido
seleccionados en base a la estadística de los nombres y apellidos más
comunes a día de hoy en España.

Creación de un login y credenciales


Por último tendremos que crear un login y unas credenciales (usuario y
contraseña) para poder establecer nuestra conexión con la base de datos SQL
Server desde Python.

1. Para crear el login y las credenciales, por ejemplo usuario


“JavierDominguezGomez” y contraseña “X6jY47bL!” escribiremos las
siguientes consultas SQL y pulsamos el botón “Run”.

CREATE LOGIN JavierDominguezGomez WITH PASSWORD = 'X6jY47bL!', DEFAULT_DATABASE = ucmdb;


CREATE USER JavierDominguezGomez FOR LOGIN JavierDominguezGomez;

2. Finalmente tenemos que dar permisos de lectura al usuario recién


creado. Para ello ejecutaremos la siguiente consulta SQL y pulsamos el
botón “Run”.

135
GRANT SELECT on ucmdb.dbo.alumnos to JavierDominguezGomez;

Con esto ya tendríamos todo lo necesario para conectarnos a nuestra base de


datos SQL Server a la que hemos llamado “ucmdb”, y podremos realizar
consultas SQL sobre ella desde Python para leer, añadir o eliminar datos.

Instalación del driver Microsoft ODBC 18


SQL Server es una tecnología cerrada y exclusiva de Microsoft, por lo tanto si
tu sistema operativo es Windows no es necesario que instales este driver
por que ya viene integrado. Por el contrario, si tu sistema operativo es
MacOS o GNU/Linux deberás instalar este driver para poder realizar la
conexión a la base de datos SQL Server.

Tanto en MacOS como en GNU/Linux abre una terminal y ejecuta los


siguientes comandos en orden, de uno en uno:

MacOS
~$ /bin/bash -c "$(curl -fsSL
https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
~$ brew tap microsoft/mssql-release
https://github.com/Microsoft/homebrew-mssql-release
~$ brew update
~$ HOMEBREW_NO_ENV_FILTERING=1 ACCEPT_EULA=Y brew install msodbcsql18
mssql-tools18

GNU/Linux
~$ sudo apt-get install odbcinst
~$ sudo curl https://packages.microsoft.com/keys/microsoft.asc | sudo
apt-key add -
~$ echo "deb [arch=amd64] https://packages.microsoft.com/ubuntu/21.10/prod
impish main" | sudo tee /etc/apt/sources.list.d/mssql-release.list
~$ sudo apt update

136
~$ sudo apt install msodbcsql18

Instalación de librerías necesarias


Para poder conectarnos a una base de datos SQL Server desde Python
necesitamos instalar la librería pyodbc.

1. Para instalar pyodbc desde PyCharm hacemos click sobre “Python


3.10”, que aparece en la esquina inferior derecha de la ventana.

2. En la ventana que emerge podremos ver en la opción “Python


Interpreter” la versión que estamos utilizando en este proyecto, que en
este caso es “Python 3.10”, y en la ventana de abajo se muestran
listados los paquetes que actualmente tenemos instalados y sus
versiones.

3. Para instalar un paquete nuevo hemos de hacer click sobre el botón “+”
tal y como se muestra en la siguiente imagen:

137
4. En la barra de búsqueda escribimos “pyodbc”, y nos aparecerán varios
paquetes en los que coincide el literal que buscamos. Hemos de
seleccionar el primero, el que se llama solo “pyodbc” y pulsamos el
botón “Install Package”.

5. Cuando se termine de instalar deberemos ver un mensaje que dice


“Package ‘pyodbc’ installed successfully” tal y como se muestra en la
siguiente imagen. Podemos cerrar esta ventana:

138
Conexión con la base de datos
Para conectarnos a una base de datos SQL Server desde Python podremos
hacerlo con el siguiente código de ejemplo

import pyodbc

conn = pyodbc.connect(
'Driver={SQL Server};'
'server=localhost;'
'database=ucmdb;'
'uid=JavierDominguezGomez;'
'pwd=X6jY47bL!;'
'Trusted_connection=yes;'
'autocommit=True'
)

Lo primero que hacemos es importar la librería pyodbc, y a continuación


creamos una variable conn cuyo valor será una conexión a la base de datos a la
que deberemos pasarle varios parámetros. El primer parámetro es Driver, cuyo
valor es {SQL Server}, el driver de conexión de Microsoft. Si este valor no
funcionara también se puede usar el valor {ODBC Driver 18 for SQL Server}.
Luego viene el parámetro server, donde tendremos que especificar el host y el
puerto donde está alojada la base de datos SQL Server. Este valor podrá ser

139
localhost si la base de datos la tuviéramos en nuestro propio PC, pero si se
trata de una base de datos que se encuentra en otro servidor en internet
tendremos que poner el host seguido de una coma y el puerto en el que está
levantado el servicio, por ejemplo servidor.com,13627. Luego vendría el
parámetro database, cuyo valor es el nombre de la base de datos a la que nos
queremos conectar, en este ejemplo pondremos ucmdb. Ahora vienen los
parámetros de autenticación, es decir, el parámetro uid, y como valor
pondremos nuestro usuario de conexión, y el parámetro password con la
contraseña de conexión ligada a nuestro usuario. Obviamente las credenciales
en este ejemplo son ficticias, tendrás que utilizar el usuario y contraseña que
tengas asignado (preguntar al profesor). Finalmente añadiremos los parámetros
Trusted_connection con valor yes y autocommit con valor True.

Después de configurar los datos de la conexión debemos crear una variable


cursor cuyo valor será conn.cursor(). Se trata de un cursor de lectura que
podremos utilizar una vez tengamos la conexión establecida.

cursor = conn.cursor()

La manera de ejecutar consultas (queries) en la base de datos y capturar la


respuesta es a través del método execute() de nuestro cursor, y dentro de los
paréntesis podremos escribir nuestra query SQL, por ejemplo:

cursor.execute('SELECT * FROM ucmdb.dbo.alumnos;')

A través de esta simple query le estamos pidiendo a la base de datos ucmdb


que nos devuelva todas las filas o registros de la tabla alumnos.

Si ahora quisiéramos iterar o trabajar con cada uno de los registros que nos ha
devuelto la consulta anterior, podremos hacerlo con un bucle for de la
siguiente manera:

for i in cursor:
print(i)

Nos devolvería un resultado como el siguiente:

('Mari Carmen', 'García')


('Antonio', 'Rodríguez')
('María', 'González')

140
('Manuel', 'Fernández')
('Carmen', 'López')
('Jose', 'Martínez')

Cada registro (row) es devuelto en forma de tupla, lo que nos permite trabajar
con estos datos desde Python.

Servicio web

Instalación de librerías necesarias

Se puede crear un servicio web con Python de varias maneras diferentes, pero
para este ejemplo usaremos un método bastante sencillo. Únicamente
necesitamos instalar dos librerías: Flask y gunicorn.

1. Para instalar Flask desde PyCharm hacemos click sobre “Python 3.10”,
que aparece en la esquina inferior derecha de la ventana.

2. En la ventana que emerge podremos ver en la opción “Python


Interpreter” la versión que estamos utilizando en este proyecto, que en
este caso es “Python 3.10”, y en la ventana de abajo se muestran
listados los paquetes que actualmente tenemos instalados y sus
versiones.

141
3. Para instalar un paquete nuevo hemos de hacer click sobre el botón “+”
tal y como se muestra en la siguiente imagen:

4. En la barra de búsqueda escribimos “Flask”, y nos aparecerán varios


paquetes en los que coincide el literal que buscamos. Hemos de
seleccionar el primero, el que se llama solo “Flask” y pulsamos el botón
“Install Package”.

142
5. Cuando se termine de instalar deberemos ver un mensaje que dice
“Package ‘Flask’ installed successfully” tal y como se muestra en la
siguiente imagen. Podemos cerrar esta ventana:

143
6. Por último, hay que instalar el paquete “gunicorn” siguiendo los mismos
pasos que hemos realizado en la instalación del paquete “Flask”.

Desarrollo de una API Rest

Primero importamos las librerías necesarias:

import json

from werkzeug.wrappers import Response


from flask import Flask

A continuación instanciamos un objeto de tipo Flask y lo almacenamos en una


variable llamada app.

app = Flask(__name__)

Esta será la base de nuestra aplicación web.

Ahora añadiremos una función llamada home() con el siguiente código:

@app.route('/')
def home():
return '<h3>Hola mundo!</h3>'

El código @app.route('/') que está encima de la firma de la función home se


llama decorador, y en este caso lo utilizaremos para indicar a la aplicación web
que esta función se invocará únicamente cuando se haga una petición desde
un navegador a la URL de nuestra aplicación compuesta por un
hostname:puerto, sin ningún path adicional (por ejemplo
http://localhost:9000/). En el cuerpo de la función únicamente tenemos el
retorno de un string que realmente es un código HTML con una cabecera de
nivel 3 (<h3>) y el texto “Hola mundo!”.

Después añadiremos otra función similar, pero con algún cambio en el


decorador y un retorno un poco más complejo. A esta función la llamaremos
get_alumnos():

144
@app.route('/alumnos')
def get_alumnos():
data = {
'centro': 'Universidad Complutense de Madrid',
'alumnos': [
{'id': '00', 'matricula': '2022QSPM', 'nombre': 'Mari Carmen',
'apellidos': 'García', 'clase': 'A'},
{'id': '01', 'matricula': '2022NDFK', 'nombre': 'Antonio',
'apellidos': 'Rodríguez', 'clase': 'A'},
{'id': '02', 'matricula': '2022KWSH', 'nombre': 'María',
'apellidos': 'González', 'clase': 'B'},
{'id': '03', 'matricula': '2022ZMLD', 'nombre': 'Manuel',
'apellidos': 'Fernández', 'clase': 'A'},
{'id': '04', 'matricula': '2022POWM', 'nombre': 'Carmen',
'apellidos': 'López', 'clase': 'A'},
{'id': '05', 'matricula': '2022LJBF', 'nombre': 'Jose',
'apellidos': 'Martínez', 'clase': 'B'},
{'id': '06', 'matricula': '2022GQKD', 'nombre': 'Ana María',
'apellidos': 'Sánchez', 'clase': 'B'},
{'id': '07', 'matricula': '2022KJEM', 'nombre': 'Francisco',
'apellidos': 'Pérez', 'clase': 'A'},
{'id': '08', 'matricula': '2022TTMK', 'nombre': 'María Pilar',
'apellidos': 'Gómez', 'clase': 'A'},
{'id': '09', 'matricula': '2022EFRW', 'nombre': 'David',
'apellidos': 'Martín', 'clase': 'A'},
{'id': '10', 'matricula': '2022LWJB', 'nombre': 'Laura',
'apellidos': 'Jiménez', 'clase': 'B'},
{'id': '11', 'matricula': '2022GFDV', 'nombre': 'Juan',
'apellidos': 'Hernández', 'clase': 'B'},
{'id': '12', 'matricula': '2022MKWX', 'nombre': 'Josefa',
'apellidos': 'Ruíz', 'clase': 'B'},
{'id': '13', 'matricula': '2022NSHC', 'nombre': 'Javier',
'apellidos': 'Díaz', 'clase': 'A'},
{'id': '14', 'matricula': '2022PCER', 'nombre': 'Isabel',
'apellidos': 'Moreno', 'clase': 'B'},
{'id': '15', 'matricula': '2022AHIV', 'nombre': 'Jose Antonio',
'apellidos': 'Muñoz', 'clase': 'A'},
{'id': '16', 'matricula': '2022MCTM', 'nombre': 'María
Dolores', 'apellidos': 'Álvarez', 'clase': 'B'}
]
}
return Response(
json.dumps(data, ensure_ascii=False),
mimetype='application/json'
)

145
En este caso el decorador es @app.route('/alumnos'), quiere decir que esta
función get_alumnos() se invocará únicamente cuando se haga una petición
desde un navegador a la URL de nuestra aplicación compuesta por un
hostname:puerto/alumnos (por ejemplo http://localhost:9000/alumnos). En el
cuerpo de la función tenemos por un lado una variable llamada data cuyo valor
es una lista de diccionarios, y cada uno de estos diccionarios contiene los
datos de alumnos (ficticios, son datos random), y por otro lado el retorno de
esta colección de datos en formato JSON. El atributo ensure_ascii con valor
False lo usamos para que nos respete los caracteres especiales como las
vocales con acentos, las eñes, etc.

Por último añadiremos al final de nuestro programa el siguiente código:

if __name__ == '__main__':
from werkzeug.serving import run_simple

run_simple('localhost', 9000, app)

Dentro de un contexto '__main__' lo primero que hacemos es importar el


módulo run_simple del paquete werkzeug.serving. Finalmente, lo invocamos
pasándole como argumentos localhost como nombre del servicio, 9000 como
puerto en el que corre el servicio web en nuestro PC y finalmente nuestro
objeto app.

Extracción de datos de una API


Ahora es el momento de ejecutar el código del servicio web que hemos
desarrollado en el punto anterior y ver cómo funciona.

En la terminal de PyCharm aparecerá un mensaje como el siguiente:

146
Ahora el servicio permanece iniciado y no debemos pararlo. Podemos hacer
una petición web desde un navegador web cualquiera a la URL de nuestro
servicio: http://localhost:9000/.

El navegador web nos mostrará una página web en la que podremos leer el
mensaje “Hola mundo!” que definimos en el código de nuestra aplicación web
en la función home().

Ahora hacemos otra petición web a esta misma URL pero añadiendo el path
/alumnos después del puerto: http://localhost:9000/alumnos

147
Podemos observar que esta parte o endpoint de la API nos devuelve toda la
información de los alumnos en formato JSON. También hemos observado que
nuestra API devuelve la información en una sola línea muy larga, sin identar o
tabular. Esto es bastante habitual, pues las APIs normalmente suelen devolver
gran cantidad de información y no suele ser procesada por un humano, por lo
que no necesita añadir caracteres adicionales como saltos de línea o
tabuladores. Lo normal es que la respuesta se procese con algún programa o
proceso automático. Pero si queremos verlo más legible (beautify) podemos
instalar en nuestro navegador algún plugin como JSONVUE que nos mostrará
la información de este tipo de respuestas JSON de una manera más
presentable, por ejemplo:

Ahora veamos cómo podemos obtener o procesar esta misma petición web al
endpoint “/alumnos” de nuestra API desde Python. Bastaría con emplear el
siguiente código:

148
import json
import requests

url = 'http://localhost:9000/alumnos'

try:
response = requests.get(url)

print(f'Respuesta obtenida en {response.elapsed.total_seconds()} segundos.')

response_dict = json.loads(response.text)

for alumno in response_dict['alumnos']:


print(alumno)
except Exception as err:
print(f'Ha habido un error: {err}')

Al inicio importamos las librerías json y requests. Luego establecemos una


variable url con la dirección web de nuestra API y el endpoint “/alumnos”.
Después, en el contexto de un try, ejecutaremos nuestra petición web a la
URL establecida anteriormente y almacenaremos la respuesta en la variable
response. Una vez obtenida la respuesta, esta tiene un montón de metadatos, y
uno de ellos es el tiempo que ha tardado en ejecutarse y ser devuelta. Este
dato lo podremos obtener mediante el método elapsed.total_seconds() de
nuestra respuesta almacenada en la variable response. Imprimimos este dato
para verlo por pantalla, son microsegundos (millonésima parte de un segundo).

Luego convertimos la respuesta JSON a un diccionario Python con


json.loads() . Recordemos que la respuesta es un JSON de dos campos
(centro y alumnos). Finalmente iteramos por cada elemento del campo “alumnos”
para imprimir por pantalla cada uno de los alumnos.

Si todo ha ido bien veremos una salida como la siguiente por la terminal de
PyCharm:

149
Y si algo fue mal veremos el error por pantalla también mediante la sentencia
except que sucede al método try. Por ejemplo, si la API no estuviera disponible
por que hemos parado el servicio:

Twitter como fuente de datos


Si saliera a la calle y preguntara a cualquier persona “¿Qué es para tí Twitter?”,
la mayoría probablemente respondería: “Una red social muy conocida en todo
el mundo”. Pero, ¿y si le preguntamos a un analista de datos, un científico de
datos o a un Hacker? Probablemente te respondan:

“Una de las mayores fuentes de datos que existen”

Y no están equivocados. A día de hoy (mediados del año 2022) Twitter tiene
345.5 millones de usuarios, y la mayoría escriben a diario mensajes llenos de
información. Gran parte de esta información es pública y la podemos ver todos
desde la propia aplicación web o móvil, pero otra no es accesible tan
fácilmente. Es una base de datos descomunal que los propios usuarios de la

150
aplicación se encargan de alimentar, indicando en todo momento su
orientación política, sus gustos musicales, culinarios, qué ropa llevan, a dónde
les gusta ir de vacaciones, cuándo se van, cuando vuelven, dónde viven,
poder adquisitivo, si tienen hijos, cómo se llaman sus hijos, a qué colegio
van…

Parece algo inofensivo, pero si cae en determinadas manos es posible que no


lo veamos con la misma indiferencia o pasividad. En ocasiones esta
información se utiliza para hacer mera estadística y ciencia de datos, en otras
ocasiones puede ser utilizada de forma deliberada con fines comerciales o de
lucro, y también cómo no, para hacer el mal, cometer delitos, etc.

A veces también hay un lado positivo en la recopilación de datos masivos de


personas, por ejemplo, puede resultar información de gran valor para encontrar
a personas desaparecidas (OSINT), no solo a través de los mensajes o el
timing, sino también con las fotografías y vídeos, que la mayoría de las veces
contienen más información que los propios metadatos de los mensajes y los
usuarios.

En esta sección vamos a ver cómo podemos extraer información de Twitter


desde Python para su posterior tratamiento o análisis.

Instalación de Tweepy

Si decidimos utilizar Jupyter Notebook, la instalación de Tweepy es tan


sencillo como escribir y ejecutar el siguiente código:

! pip install tweepy

Comenzará la instalación del paquete tweepy en background, esperamos a


que termine y desde ese momento ya tendríamos la librería disponible para
utilizarla.

Para instalar el paquete Tweepy en PyCharm hay que seguir los siguientes
pasos.

1. Hacemos click sobre “Python 3.10”, que aparece en la esquina inferior


derecha de la ventana.

151
2. En la ventana que emerge podremos ver en la opción “Python
Interpreter” la versión que estamos utilizando en este proyecto, que en
este caso es “Python 3.10”, y en la ventana de abajo se muestran
listados los paquetes que actualmente tenemos instalados y sus
versiones.

3. Para instalar un paquete nuevo hemos de hacer click sobre el botón “+”
tal y como se muestra en la siguiente imagen:

152
4. En la barra de búsqueda escribimos “tweepy”, y nos aparecerán varios
paquetes en los que coincide el literal que buscamos. Hemos de
seleccionar el primero, el que se llama solo “tweepy” y pulsamos el
botón “Install Package”.

153
5. Cuando termine la instalación deberemos ver un mensaje que dice
“Package ‘tweepy’ installed successfully” tal y como se muestra en la
siguiente imagen. Podemos cerrar esta ventana:

6. Ahora que volvemos a la ventana anterior, podemos ver que se han


instalado con éxito varios paquetes, además del paquete “tweepy”. Esto
sucede porque existen dependencias entre paquetes y cuando instalas
uno muy concreto se suelen instalar otros adicionales necesarios para
que todo funcione correctamente. Finalmente pulsamos el botón “OK”.

154
Uso de la API

Primero importamos la librería tweepy de la siguiente forma:

import tweepy

Para poder usar la API de Twitter debemos configurar una cuenta Developer*
previamente. Una vez creada añadimos las credenciales de nuestra cuenta
Developer de Twitter:

TWITTER_CONSUMER_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxx'
TWITTER_CONSUMER_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
TWITTER_TOKEN = 'xxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
TWITTER_TOKEN_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

155
* Si tienes una cuenta de Twitter la puedes convertir en una cuenta “Dev” o
“Developer”, si no puedes crear una cuenta nueva para probar la API:
https://developer.twitter.com/en

Consultas y extracción de datos


Mediante la siguiente función generamos un objeto de de tipo tweepy.API que
usaremos para interactuar con Twitter:

def get_api() -> tweepy.API:


auth = tweepy.OAuthHandler(
TWITTER_CONSUMER_KEY,
TWITTER_CONSUMER_SECRET
)
auth.set_access_token(
TWITTER_TOKEN,
TWITTER_TOKEN_SECRET
)

return tweepy.API(auth, wait_on_rate_limit=True)

A continuación instanciamos un objeto de tipo tweepy.API y lo almacenamos en


la variable api, creamos una variable llamada twitter_user que tenga como
valor el usuario de Twitter del que queremos extraer sus tweets y por último
hacemos una petición a la API para extraer los 5 últimos tweets mediante el
método user_timeline().

api = get_api()
twitter_user = 'elonmusk'
tweets = api.user_timeline(screen_name=twitter_user, count=5)

Ahora podremos iterar a través de los tweets recuperados e imprimir el texto


por pantalla:

for tweet in tweets:


print(tweet._json['text'])

En la terminal de PyCharm podremos ver los 5 últimos tweets del usuario que
hubiéramos escogido, por ejemplo:

156
Aplicación de vuelos comerciales
En internet existen numerosas aplicaciones web de seguimiento de vuelos que
nos permiten hacer tracking o seguimiento de qué vuelos hay en qué momento
y en qué área del mapa, como flightradar24, FlightAware, flightview, etc,
además algunas de ellas nos proporcionan algunos datos interesantes como a
qué altitud se encuentra en ese momento un vuelo determinado o a qué
velocidad va.

En esta sección vamos a ver cómo podemos explotar esta fuente de datos
para su estudio o su representación en un mapa.

Utilizaremos como fuente de datos Opensky Network y como librerías Pandas


y Bokeh. Pandas ya lo conocemos, pero Bokeh no lo habíamos usado hasta
ahora.

Bokeh es una biblioteca de Python para crear visualizaciones interactivas para


navegadores web modernos. Con esta librería podremos realizar gráficos muy
vistosos y atractivos, que van desde gráficos simples hasta algunos gráficos
más complejos con conjuntos de datos de transmisión. Las visualizaciones
están basadas en JavaScript pero no necesitamos escribir ni una línea de
código JavaScript, no necesitamos conocer este lenguaje, Bokeh ya hace el
trabajo por nosotros.

Vamos a crear una aplicación de seguimiento de vuelos con Python utilizando


como fuente de datos estas APIs con información del tráfico aéreo en tiempo
real. Veremos las siguientes fases en detalle:

1. Obtener datos.
2. Importar las bibliotecas necesarias.

157
3. Cargar el mapa base.
4. Trazar la posición de los aviones.
5. Crear una aplicación de seguimiento de vuelos en "tiempo real".

Vamos a analizar cada una de estas fases y, al final, si la API está disponible y
tenemos datos, veremos cómo funciona una aplicación que es capaz de hacer
un seguimiento de vuelos en tiempo real (refresca la información cada pocos
segundos) y que se ejecuta en el navegador.

Figura 1. Vista de la app de seguimiento de vuelos

Obtención de datos de tráfico aéreo

Estamos usando datos públicos de tráfico aéreo de OpenSky Network.


OpenSky Network es un consorcio sin fines de lucro que proporciona datos de
tráfico aéreo abiertos al público, en particular para fines de investigación y no
comerciales. Se puede acceder a los datos a través de la API REST.

Para recuperar los datos usando la API REST se puede hacer usando una
solicitud HTTP de tipo GET. Se pueden utilizar dos tipos de solicitudes. La
primera es la solicitud de un avión específico según la hora en la marca de
tiempo UNIX o la dirección ICAO24. Por ejemplo:

https://opensky-network.org/api/states/all?time=1458564121&icao24=3c6444

158
El segundo método, podemos obtener todos los datos del avión dentro de una
extensión de área utilizando el sistema de coordenadas WGS84. Además el
acceso a los datos puede hacerse de forma anónima o como usuario
registrado. La diferencia es que para una solicitud anónima tiene 10 segundos
de respuesta y 5 segundos para un usuario registrado.

Nosotros vamos a usar el segundo. Definiremos una extensión de área con


coordenadas mínimas y máximas, y luego enviaremos la consulta para obtener
todos los datos del avión dentro del área. Por ejemplo, queremos obtener los
datos sobre la Península Ibérica con la coordenada mínima -9.9503,35.9425 y
la coordenada máxima 4.5517,43.9617. La consulta tanto para el usuario
anónimo como para el registrado será la siguiente.

● Solicitud anónima
https://opensky-network.org/api/states/all?lamin=35.9425&lomin=-9.95
03&lamax=43.9617&lomax=4.5517

159
● Solicitud de usuario registrado
https://john_doe:abcd1234@opensky-network.org/api/states/all?lamin=
35.9425&lomin=-9.9503&lamax=43.9617&lomax=4.5517

Vamos a asegurarnos de que la consulta es correcta, probemos. Si obtenemos


una respuesta como la siguiente, entonces funciona.

Figura 2. Respuesta de datos de tráfico aéreo

160
La respuesta, como se muestra en la figura 2, tiene una estructura JSON con
dos claves. El primero es time y el segundo es states que contiene datos para
cada avión en una matriz de listas. Cada una de las listas almacena muchos
datos, como: dirección ICAO24, distintivo de llamada del avión, país de origen,
posición horaria, último contacto, longitud, latitud, altitud del barómetro, etc.
Para obtener una explicación completa sobre la respuesta de los datos y
también más información sobre la API de red de OpenSky, consulte la
documentación de la API de OpenSky Network.

Obtener datos de tráfico aéreo en Python

Ya recuperamos los datos de tráfico utilizando la API REST en un navegador.


Ahora hagámoslo en Python. Usaremos Jupyter Notebook con Python 3 y
algunas bibliotecas como Bokeh, Pandas, Requests, json y numpy.

A continuación se muestra el código para realizar una solicitud y obtener


datos.

Primero importamos las bibliotecas requeridas, como json, pandas y requests.

import json
import pandas as pd
import requests

Luego definimos las coordenadas de extensión en WGS84 (es un sistema


geodésico de coordenadas geográficas usado mundialmente, que permite
localizar cualquier punto de la Tierra, sin necesitar otro de referencia, por
medio de tres unidades dadas x, y, z) mediante las variables lon_min, lat_min,
lon_max y lat_max. En este ejemplo pondremos las coordenadas de la
península Ibérica, aunque quizás entren datos de alguna región cercana más,
pues al final estamos trazando un rectángulo en el mapa.

# Iberian Peninsula min and max longitude & latitude.


lon_min, lat_min = -9.9503, 35.9425
lon_max, lat_max = 4.5517, 43.9617

Usaremos las coordenadas de extensión para construir la dirección URL a la


que haremos la petición de datos. Si quisiéramos usar un usuario registrado,
crearemos las variables user y password (el usuario y password utilizados en

161
esta documentación son de ejemplo, debes registrarte en la web de OpenSky
Network y poner tu propio usuario y password). Finalmente creamos la variable
url en el que concatenamos todos los datos anteriormente especificados.

user = 'john_doe'
password = 'abcd1234'
query = f'lamin={lat_min}&lomin={lon_min}&lamax={lat_max}&lomax={lon_max}'
url =
f'https://{user}:{password}@opensky-network.org/api/states/all?{query}'

Ahora es el momento de hacer la petición web a la URL mediante requests y el


método get. La respuesta la almacenamos en formato JSON mediante la
función json y almacenaremos los datos en la variable response.

response = requests.get(url).json()

Como ya he mencionado anteriormente, estamos utilizando la API de OpenSky


Network, que al fin y al cabo es un servicio en internet gratuíto, sin ánimo de
lucro y al que accede un gran número de personas constantemente para hacer
casos de estudio o simplemente para recibir información en tiempo real, Esto
quiere decir que es un servicio muy solicitado y es posible que en ocasiones
no esté disponible porque el servidor en el que está alojado esté en
mantenimiento o se haya caído, en cuyo caso habría que esperar a que se
vuelva a levantar, y a veces pueden pasar horas.

Si en el momento de realizar esta práctica el servicio no estuviera disponible…


no estaríamos perdidos del todo, pues yo ya he descargado en un archivo
JSON la información de las coordenadas anteriormente citadas y en un
momento muy concreto del tiempo, lo que podríamos llamar snapshot o
captura de datos. Son datos estáticos, ya no tendríamos datos en tiempo real,
pero tendríamos datos para poder seguir trabajando y seguir aprendiendo.

Así pues, única y exclusivamente si este fuera el caso, os compartiría un


archivo llamado IberianPeninsula.json con los datos ya capturados,
deberíamos descargarlo en una carpeta llamada data, e importarlos de la
siguiente manera.

with open('data/IberianPeninsula.json') as data:

162
response = json.load(data)

Lo bueno de este método de carga de datos es que si ya tienes una captura de


datos descargada en tu PC local podrías trabajar offline, es decir, sin acceso a
internet.

Ahora, antes de volcar los datos obtenidos en un dataframe de Pandas


estableceremos el nombre de todas las columnas con las que trabajaremos,
estos nombres se corresponden a cada uno de los datos que obtenemos para
cada vuelo.

col_name = [
'icao24', 'callsign', 'origin_country', 'time_position', 'last_contact',
'long', 'lat', 'baro_altitude', 'on_ground', 'velocity', 'true_track',
'vertical_rate', 'sensors', 'geo_altitude', 'squawk', 'spi',
'position_source'
]

De toda la información que obtuvimos en la respuesta inicial nos quedaremos


con el valor del campo states, lo volcaremos a un dataframe de Pandas,
obtenemos datos de las 16 columnas de datos y le añadiremos los nombres
de las columnas que definimos antes.

flight_df = pd.DataFrame(response['states'])
flight_df = flight_df.loc[:, 0:16]
flight_df.columns = col_name

Ahora reemplazamos los datos en puedan llegar blanco o vacíos con el string
'No Data'.

flight_df = flight_df.fillna('No Data') # Replace NAN with No Data string.

Finalmente comprobamos que tenemos el dataframe usando el método head


para ver las primeras 5 filas de datos.

flight_df.head()

163
Deberemos ver algo como la siguiente imagen:

Trazar avión en el mapa

Ahora que ya tenemos un dataframe con todos los datos cargados es el


momento de ubicar todos los vuelos en un mapa. Para ello usaremos la
biblioteca Bokeh, así que la tenemos que importar. También necesitaremos
importar la biblioteca NumPy que utilizaremos más adelante en la conversión
de coordenadas.

import numpy as np

from bokeh.plotting import figure, show


from bokeh.tile_providers import get_provider, STAMEN_TERRAIN
from bokeh.models import HoverTool, LabelSet, ColumnDataSource

La conversión del sistema de coordenadas la vamos a implementar mediante


un par de funciones llamadas wgs84_to_web_mercator_point y
wgs84_to_web_mercator.

def wgs84_to_web_mercator_point(lon, lat):


""" Function to convert GCS WGS84 to web marcator system. """

k = 6378137 # WGS84 oblate spheroid with this equatorial radius in


meters.
x = lon * (k * np.pi / 180.0)
y = np.log(np.tan((90 + lat) * np.pi / 360.0)) * k

return x, y

def wgs84_to_web_mercator(df, lon='long', lat='lat'):


""" Function to get converted Dataframe. """

k = 6378137 # WGS84 oblate spheroid with this equatorial radius in

164
meters.
df['x'] = df[lon] * (k * np.pi / 180.0)
df['y'] = np.log(np.tan((90 + df[lat]) * np.pi / 360.0)) * k

return df

Como su nombre indica, estas funciones las utilizaremos para transformar


coordenadas del sistema WGS84 al sistema de coordenadas universal
transversal de Mercator. Esta transformación es necesaria porque vamos a
usar un mapa con base STAMEN_TERRAIN en un navegador web que tiene
proyección web Mercator (EPSG: 3857).

Realizamos la transformación para el marco de datos y las coordenadas de


extensión.

# COORDINATE CONVERSION
xy_min = wgs84_to_web_mercator_point(lon_min, lat_min)
xy_max = wgs84_to_web_mercator_point(lon_max, lat_max)

wgs84_to_web_mercator(flight_df)

A continuación configuramos el mapa especificando la extensión del área en


función del rango de coordenadas x e y. Cada uno de los vuelos aparecerá en
el mapa en función de las coordenadas x e y, con puntos de color rojo y
también con la imagen del icono del avión que hayamos escogido en la
variable icon_url.

flight_df['rot_angle'] = flight_df['true_track'] * -1 # Rotation angle


icon_url = 'https://www.iconsdb.com/icons/preview/red/airplane-4-xl.png'
flight_df['url'] = icon_url

En el siguiente código se configura y genera el mapa final en el que aparecerán


todos los datos representados en forma de aviones con una etiqueta con el
nombre del vuelo. Además se ha añadido la función de mostrar información
relevante sobre el vuelo al pasar el puntero del ratón por encima.

# FIGURE SETTING
x_range, y_range = ([xy_min[0], xy_max[0]], [xy_min[1], xy_max[1]])
p = figure(

165
x_range=x_range,
y_range=y_range,
x_axis_type='mercator',
y_axis_type='mercator',
sizing_mode='scale_width',
plot_height=300
)

# PLOT BASEMAP AND AIRPLANE POINTS


flight_source = ColumnDataSource(flight_df)
tile_prov = get_provider(STAMEN_TERRAIN)
p.add_tile(tile_prov, level='image')
p.image_url(
url='url',
x='x',
y='y',
source=flight_source,
anchor='center',
angle_units='deg',
angle='rot_angle',
h_units='screen',
w_units='screen',
w=40,
h=40
)
p.circle(
'x',
'y',
source=flight_source,
fill_color='red',
hover_color='yellow',
size=10,
fill_alpha=0.8,
line_width=0
)

# HOVER INFORMATION AND LABEL


my_hover = HoverTool()
my_hover.tooltips = [
('Call sign', '@callsign'),
('Origin Country', '@origin_country'),
('velocity(m/s)', '@velocity'),
('Altitude(m)', '@baro_altitude')
]
labels = LabelSet(
x='x',

166
y='y',
text='callsign',
level='glyph',
x_offset=5,
y_offset=5,
source=flight_source,
render_mode='canvas',
background_fill_color='white',
text_font_size='8pt'
)
p.add_tools(my_hover)
p.add_layout(labels)

show(p)

Si se ejecuta obtendremos un resultado como el de la siguiente imagen.

Figura 4. Mapa con los vuelos tal como se ve en el navegador.

Crear aplicación de seguimiento de vuelos real-time

Hasta ahora habíamos visto cómo obtener datos de tráfico aéreo y ubicar los
aviones en un mapa. En esta última sección, veremos cómo crear una
aplicación de seguimiento de vuelos que se ejecuta en un navegador web. La
aplicación recuperará automáticamente nuevos datos en un intervalo
específico y trazará los datos en el mapa. Combinaremos el código del punto

167
anterior y lo empaquetamos en una aplicación usando la biblioteca Bokeh. El
código completo se puede encontrar a continuación.

Si observamos el código, necesitamos importar una biblioteca Bokeh


adicional, como Server , Application y FunctionHandler . El código de la
aplicación comienza en la línea 50. Aquí creamos la función principal de la
aplicación que se llama seguimiento_de_vuelo . La función principal consta de
todos los procesos que se tomarán cuando se ejecute la función principal,
como actualizar los datos de vuelo, volcarlos en el marco de datos de Pandas,
convertirlos en fuente de columna de datos Bokeh y transmitir, recuperar la
actualización cada 5 segundos y trazar los datos en el mapa. Después de crear
la función principal de la aplicación, al final determinamos algunas variables o
parámetros para el servidor. Puedes encontrar todo el proceso descrito paso a
paso en los comentarios del código.

"""
FLIGHT TRACKING WITH PYTHON AND OPEN AIR TRAFFIC DATA
by ideagora geomatics | www.geodose.com | @ideageo
updated by @JavDomGom
"""

import json
import numpy as np
import pandas as pd
import requests

from bokeh.application import Application


from bokeh.application.handlers.function import FunctionHandler
from bokeh.models import HoverTool,LabelSet,ColumnDataSource
from bokeh.plotting import figure
from bokeh.server.server import Server
from bokeh.tile_providers import get_provider, STAMEN_TERRAIN

def wgs84_to_web_mercator_point(lon, lat):


""" Function to convert GCS WGS84 to web marcator point. """

k = 6378137 # WGS84 oblate spheroid with this equatorial radius in


meters.
x= lon * (k * np.pi / 180.0)
y= np.log(np.tan((90 + lat) * np.pi / 360.0)) * k

return x,y

168
def wgs84_to_web_mercator(df, lon='long', lat='lat'):
""" Function to get Dataframe. """

k = 6378137 # WGS84 oblate spheroid with this equatorial radius in


meters.
df['x'] = df[lon] * (k * np.pi / 180.0)
df['y'] = np.log(np.tan((90 + df[lat]) * np.pi / 360.0)) * k

return df

# Iberian Peninsula min and max longitude & latitude.


lon_min, lat_min = -9.9503, 35.9425
lon_max, lat_max = 4.5517, 43.9617

# COORDINATE CONVERSION
xy_min = wgs84_to_web_mercator_point(lon_min, lat_min)
xy_max = wgs84_to_web_mercator_point(lon_max, lat_max)

# COORDINATE RANGE IN WEB MERCATOR


x_range, y_range=([xy_min[0], xy_max[0]], [xy_min[1], xy_max[1]])

# REST API QUERY


user = 'john_doe'
password = 'abcd1234'
query = f'lamin={lat_min}&lomin={lon_min}&lamax={lat_max}&lomax={lon_max}'
url =
f'https://{user}:{password}@opensky-network.org/api/states/all?{query}'

def flight_tracking(doc):
""" Init bokeh column data source. """

flight_source = ColumnDataSource(
{
'icao24': [],
'callsign': [],
'origin_country': [],
'time_position': [],
'last_contact': [],
'long': [],
'lat': [],
'baro_altitude': [],
'on_ground': [],

169
'velocity': [],
'true_track': [],
'vertical_rate': [],
'sensors': [],
'geo_altitude': [],
'squawk': [],
'spi': [],
'position_source': [],
'x': [],
'y':[],
'rot_angle': [],
'url': []
}
)

def update():
""" Updating flight data. """

response = requests.get(url).json()

# CONVERT TO PANDAS DATAFRAME


col_name=[
'icao24', 'callsign', 'origin_country', 'time_position',
'last_contact', 'long', 'lat',
'baro_altitude', 'on_ground', 'velocity', 'true_track',
'vertical_rate', 'sensors', 'geo_altitude',
'squawk', 'spi', 'position_source'
]

flight_df = pd.DataFrame(response['states'])
flight_df = flight_df.loc[:, 0:16]
flight_df.columns = col_name
wgs84_to_web_mercator(flight_df)
flight_df = flight_df.fillna('No Data')
flight_df['rot_angle'] = flight_df['true_track'] * -1
icon_url =
'https://www.iconsdb.com/icons/preview/red/airplane-4-xl.png'
flight_df['url'] = icon_url

# CONVERT TO BOKEH DATASOURCE AND STREAMING


n_roll = len(flight_df.index)
flight_source.stream(flight_df.to_dict(orient='list'), n_roll)

# CALLBACK UPATE IN AN INTERVAL


doc.add_periodic_callback(update, 5000) # 5000 ms/10000 ms for
registered user.

170
# PLOT AIRCRAFT POSITION
p = figure(
x_range=x_range,
y_range=y_range,
x_axis_type='mercator',
y_axis_type='mercator',
sizing_mode='scale_width',
plot_height=300
)
tile_prov = get_provider(STAMEN_TERRAIN)

p.add_tile(
tile_prov,
level='image'
)
p.image_url(
url='url',
x='x',
y='y',
source=flight_source,
anchor='center',
angle_units='deg',
angle='rot_angle',
h_units='screen',
w_units='screen',
w=40,
h=40
)
p.circle(
'x',
'y',
source=flight_source,
fill_color='red',
hover_color='yellow',
size=10,
fill_alpha=0.8,
line_width=0
)

# ADD HOVER TOOL AND LABEL


my_hover=HoverTool()
my_hover.tooltips=[
('Call sign', '@callsign'),
('Origin Country', '@origin_country'),
('velocity(m/s)', '@velocity'),

171
('Altitude(m)', '@baro_altitude')
]
labels = LabelSet(
x='x',
y='y',
text='callsign',
level='glyph',
x_offset=5,
y_offset=5,
source=flight_source,
render_mode='canvas',
background_fill_color='white',
text_font_size='8pt'
)
p.add_tools(my_hover)
p.add_layout(labels)
doc.title='REAL TIME FLIGHT TRACKING'
doc.add_root(p)

# SERVER CODE
apps = {'/': Application(FunctionHandler(flight_tracking))}
server = Server(apps, port=8084) # Define an unused port.
server.start()

Ahora es el momento de probar la aplicación. Ejecutamos el código y abrimos


un navegador web. debemos escribir la dirección localhost:número_de_puerto
(por ejemplo, localhost:8084). Deberíamos ver la aplicación de seguimiento de
vuelos ejecutándose en el navegador web como se muestra en la siguiente
imagen:

172
Figura 5. Aplicación de seguimiento de vuelos en un navegador web

173

También podría gustarte