0% encontró este documento útil (0 votos)
788 vistas

Documentacion Selenium Python

Este documento proporciona una introducción a Python y Selenium. Explica qué es Python, su historia y evolución, y cómo configurar el entorno de desarrollo con Visual Studio Code. Luego cubre los tipos de datos de Python, las estructuras de control, la definición de funciones y las clases y objetos. Incluye secciones de laboratorio con ejercicios para practicar los conceptos.

Cargado por

Ana
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
0% encontró este documento útil (0 votos)
788 vistas

Documentacion Selenium Python

Este documento proporciona una introducción a Python y Selenium. Explica qué es Python, su historia y evolución, y cómo configurar el entorno de desarrollo con Visual Studio Code. Luego cubre los tipos de datos de Python, las estructuras de control, la definición de funciones y las clases y objetos. Incluye secciones de laboratorio con ejercicios para practicar los conceptos.

Cargado por

Ana
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/ 273

Python y Selenium

v1.1.4 2020-01-09
Contenidos
1. Introducción a Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1  

1.1. ¿Qué es Python? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1  

1.2. El zen de Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1  

1.3. Historia y evolución de Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1  

1.3.1. Versión 1.0. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2  

1.3.2. Versión 2.0. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2  

1.3.3. Versión 3.0. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2  

1.4. Lab: Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3  

1.4.1. Iniciando VS Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3  

1.4.2. Instalando la extensión de Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5  

1.4.3. Creando un entorno virtual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5  

1.4.4. Seleccionando el interprete de Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6  

1.4.5. Nuestro primer script en Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7  

1.4.6. Configurando Debugg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9  

1.4.7. Instalando y usando módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9  

2. Python y los tipos de datos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11  

2.1. Introducción a la sintáxis de Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11  

2.1.1. Tipos numéricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11  

2.2. Asignación y operadores de comparación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13  

2.3. Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
 

2.4. Secuencias: Listas y Tuplas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17  

2.5. Otros tipos de datos: Conjuntos (Sets) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19  

2.5.1. Otros tipos de datos: diccionarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22  

2.6. Lab: Introducción y tipo de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24  

2.6.1. Intro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
 

3. Estructuras de control. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33  

3.1. Condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33  

3.2. Bucles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
 

3.2.1. Bucles while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34  

3.2.2. Bucles for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35  

3.3. Lab: Estructuras de control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38  

3.3.1. Ejercicio 1: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38  

3.3.2. Laboratorio 02 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40  

4. Definición de funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41  

4.1. Funciones con parametros opcionales *args y **kwargs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42  

4.2. PEP8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
 

4.3. Lab: Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45  

4.3.1. Ejercicio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 


4.3.2. Ejercicio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
 

4.3.3. Ejercicio 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
 

4.3.4. Ejercicio 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
 

4.3.5. Ejercicio 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
 

4.3.6. Ejercicio 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
 

4.3.7. Ejercicio 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
 

4.3.8. Ejercicio 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
 

5. Clases y objetos en Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52  

5.1. Creando clases en Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52  

5.1.1. Creando constructores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53  

5.1.2. Herencia en Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54  

5.1.3. Overriding (Sobrecarga de metodos) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54  

5.1.4. Clases Abstractas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55  

5.1.5. ¿Es Python realmente orientado a objetos? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57  

5.1.6. Python y la encapsulación de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58  

5.1.7. Métodos de clase vs funciones estáticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61  

5.2. Clases para controlar errores: excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63  

5.3. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
 

5.4. USando el metodo "Slots" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64  

5.5. Lab: Programación Orientada a Objetos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66  

5.5.1. Laboratorio 01 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 

5.5.2. Laboratorio 02 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 

5.5.3. Laboratorio 03 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 

5.5.4. Laboratorio 04 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 

5.5.5. Laboratorio 05 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 

5.5.6. Laboratorio 06 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 

6. Excepciones con Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76  

6.1. Gestionando excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76  

6.2. Lanzando excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78  

6.3. Encadenando excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79  

6.4. Built-in exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80  

6.4.1. Clases Padre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80


 

6.4.2. Excepciones especificas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81  

6.5. Excepciones definidas por el usuario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82  

6.6. Lab: Excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84  

6.6.1. Laboratorio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
 

6.6.2. Laboratorio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
 

7. Creación de paquetes propios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85  

7.1. Manejando módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85  

7.1.1. Importando módulos enteros: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85  

7.1.2. Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 


7.1.3. Alias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
 

7.1.4. Importar módulos sin utilizar namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86  

7.1.5. Ejecuntando módulos como scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87  

7.2. Manejo de paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87  

7.2.1. Referencias internas en paquetes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90  

7.2.2. Paquetes en varios directorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90  

7.3. Lab: Módulos y paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92  

7.3.1. Creando un módulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92  

7.3.2. Creando e importando un paquete. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93  

7.3.3. Distribuyendo un paquete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95  

8. Manipulación de parametros de entrada/salida. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97  

8.1. Tipos de argumentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97  

8.2. Empezando con argparse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97  

8.3. Parámetros posicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98  

8.4. Parámetros optativos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100  

8.5. Mezclando parámetros posicionales y optativos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101  

8.6. Gestionando conflictos entre opciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103  

8.7. Lab: Manipulacion E/S. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106  

8.7.1. Laboratorio 01 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106  

8.7.2. Laboratorio 02 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106  

8.7.3. Laboratorio 03 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109  

9. Test, depuración y loggin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115  

9.1. Tests unitarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 

9.2. Capturando excepciones en tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116  

9.3. TDD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117


 

9.4. Refactorizando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118  

9.5. Depurando código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119  

9.5.1. Comando (l)ist . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120  

9.5.2. Comando (w)here . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121  

9.5.3. Comando (s)tep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121  

9.5.4. Comando (b)reak . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121  

9.5.5. Comando (c)ontinue. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122  

9.5.6. Inspeccionar valores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122  

9.5.7. Comando (q)uit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123  

9.6. Rastreando errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123  

10. Selenium Mozilla IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125  

10.1. Grabación de casos de prueba . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126  

10.2. Reproducción de casos de prueba . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128  

10.3. Debugger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 

10.4. Verificación de casos de prueba . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130  

10.5. Bancos de pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132  


10.6. Selenese . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
 

10.6.1. Comandos de navegación y acción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135  

10.6.2. Comandos de verificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135  

10.6.3. Comandos de almacenamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136  

10.7. Exportar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136


 

10.8. Generar informes HTML de los casos de prueba . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137  

10.9. Lab: Mozilla IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140  

10.9.1. Ejercicio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140


 

10.9.2. Ejercicio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140


 

10.9.3. Ejercicio 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140


 

11. Selenium WebDriver con Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141  

11.1. Configuración del entorno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141  

11.2. Objeto WebDriver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141  

11.3. Tests usando Firefox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142  

11.4. Tests usando Chrome . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142  

11.5. Tests usando Internet Explorer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143  

11.6. Clase TestCase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143  

11.7. Herramientas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144  

11.8. Objeto WebElement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145  

11.9. Aserciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146


 

11.10. Encontrar elementos web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146  

11.11. Métodos de busqueda de Elementos Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147  

11.11.1. Id. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147


 

11.11.2. Name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149


 

11.11.3. ClassName . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150  

11.11.4. TagName. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150  

11.11.5. LinkText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 

11.11.6. PartialLinkText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153  

11.11.7. XPath . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154


 

11.11.8. Selectores CSS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159  

11.11.9. jQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165


 

11.12. Lab: Python Webdriver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167  

11.12.1. Ejercicio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167


 

11.12.2. Ejercicio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167


 

11.12.3. Ejercicio 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168


 

11.12.4. Ejercicio 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169


 

11.12.5. Ejercicio 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170


 

11.12.6. Ejercicio 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171


 

12. Interacciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173


 

12.1. Cajas de texto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 

12.2. Desplegables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176  


12.3. Radio Buttons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182  

12.4. Checkbox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 

12.5. Alerts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185


 

12.6. Lab: Interacciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188  

12.6.1. Ejercicio 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188


 

12.6.2. Ejercicio 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188


 

12.6.3. Ejercicio 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189


 

12.6.4. Ejercicio 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190 

12.6.5. Ejercicio 11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 

13. Selenium API. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193  

13.1. Comprobar el estado de los WebElement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193  

13.2. Doble click . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193


 

13.3. Drag and Drop. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194  

13.4. JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195


 

13.5. ScreenShots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196  

13.6. Manejo de cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197  

13.7. Eventos del WebDriver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197  

13.8. Lab: Selenium API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200  

13.8.1. Ejercicio 12 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 

13.8.2. Ejercicio 13 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 

13.8.3. Ejercicio 14 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202 

14. Sincronización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204  

14.1. Wait implícito . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204  

14.2. Wait explícito . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205  

14.3. FluentWait . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206 

15. DataDriven . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 

15.1. CSV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208


 

15.2. Excel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210


 

15.3. Lab: DataDriver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213  

15.3.1. Ejercicio 15 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 

15.4. Ejercicio 16. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214


 

16. Pruebas Multi Browser y Selenium Grid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216  

16.1. Multibrowse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216  

16.2. Selenium Grid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218  

16.2.1. Transferencia de archivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219  

16.2.2. Configurando Selenium Grid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220  

17. Web Scraping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223  

17.1. ¿Qué es el Web Scraping?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223  

17.2. ¿Para qué podemos usar web scraping?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223  

17.3. ¿Por qué Python es adecuado para el web scraping? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223  

17.4. Scrapy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224


 
17.4.1. Empezando a trabajar con Scrapy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224  

17.5. Selenium. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230


 

17.5.1. Empezando a trabajar con Selenium. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231  

17.6. Lab: Web Scraping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236  

17.6.1. Lab 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236


 

18. Planificando tareas con Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238  

18.1. Lab: Planificando tareas con Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241  

18.1.1. Laboratorio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241  

18.1.2. Laboratorio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241  

19. Envío de Email con Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243  

19.1. Lab: Enviando un mail con Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244  

20. Librerias SciKit y OpenCV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250  

20.1. SciKit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250


 

20.1.1. Las ventajas de su nivel de abstracción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250  

20.2. OpenCV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253


 

20.2.1. Una imagen, una matriz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254  

20.3. Tipos de imágenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254  

20.3.1. Imágenes binarias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254  

20.3.2. Escala de grises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255  

20.3.3. Imagen a color. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255  

20.4. Comandos y herramientas de OpenCV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256  

20.4.1. Comandos básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256  

20.4.2. Dibujando en imagenes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257  

20.4.3. Escribiendo en imágenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258  

20.5. Scikit-Image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258


 

20.6. Lab: SciKit y OpenCV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263  

20.6.1. Laboratorio: Operaciones simples en OpenCV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263  

20.6.2. Laboratorio: Formas y texto sobre una imagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264  

20.6.3. Modificando una imagen con skimage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265  


Capítulo 1. Introducción a Python
1.1. ¿Qué es Python?
En pocas palabras:

• Lenguaje de programación dinámico, interpretado y fácil de aprender

• Creado por Guido van Rossum en 1991

• Ampliamente utilizado en ciencia e ingeniería

• Multitud de bibliotecas para realizar diferentes tareas.

1.2. El zen de Python


Si abrimos el intérprete de Python en consola y ejecutamos 'import this', obtendremos la siguiente
salida

The Zen of Python, by Tim Peters

Beautiful is better than ugly.


Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

1.3. Historia y evolución de Python


¿Como ha llegado Python a ser este lenguaje ampliamente utilizado en ciencias e ingenieria? En
este apartado vamos a conocer sus cambios y evolución.

• Python tiene su origen en el lenguaje ABC, cuyo fin era ser una alternativa a Basic como
elección para principiantesRossum. Este lenguaje con un codigo compacto y legible no

1
trascendio por incompatibilidades con el software de la época.

• Guido Van Rossum decidió darle una segunda oportunidad con Python.

• En 1991 publica en alt.sources el codigo de la versión 0.9.0 que ya tenía disponibles, clases con
herencia, manejo de excepciones, funciones y los tipos modulares.

• Esta versión 0.9.0 utiliza un sistema de módulos adaptado de Modula-3, un lenguaje de


programación estructurado y modular.

• En 1994 se crea comp.lang.python un foro de discusión de Python que disparó su popularidad y


aumentó notablemente su cantidad de usuarios.

1.3.1. Versión 1.0

También en ese año llegó la versión 1.0 de Python:

• Incorporaba herramientas de programación funcional: lambda, reduce, filter y map

• La versión 1.4 trajó nuevas características inspiradas en Modula 3 y soporte "built in" para
numeros complejos.

• Tras la versión 1.6 la Free Software Foundation en acuerdo con el CNRI acordaron que Python
tuviera licencia de Software Libre dando lugar a la versión 1.6.1

1.3.2. Versión 2.0

• La versión 2.0 trajo algo tan importante como es la creación de listas y un sistema de
recolección de basura capar de recolectar referencias cíclicas.

• En 2001 se crea la Python Software Foundation, dueña de todo el código, documentación y


especificaciones del lenguaje desde la version 2.1.

• Hasta 2020 recibió actualizaciones de seguridad. Ha dejado ya de tener soporte.

1.3.3. Versión 3.0

• Hasta ahora es la última gran actualización de Python es la 3.0 de 2008

• Como lenguaje a acumulado nuevas y redundantes formas de programar una misma tarea. Por
eso esta versión hace enfasis en eliminar construtores duplicados y módulos con un solo
objetivo: "tener solo un modo obvio de hacer las cosas".

• Python 2.6 y Python 3.0 se lanzaron casi al mismo tiempo, fueron planeadas para coexistir. Por
ejemplo la 2.6 mantenia herramientas eliminadas en la versión 3.0

• La versión 2.7 (ultima de la serie 2.x) se lanzó a la vez de la 3.1

• Con Python 3.0 se rompe la retrocompatibilidad del lenguaje, y algunos objetos modifican su
sintaxis.

2
1.4. Lab: Introducción
En este capítulo vamos a aprender a utilizar VS Code como un IDE para Python, usandolo como un
entorno de Python en el que vamos a poder:

• Escribir, ejecutar y depurar apliaciones en Python

• Aprender a installar paquetes dentro de un entorno virtual que crearemos

• Escribir scripts de Python

1.4.1. Iniciando VS Code

Tenemos varias maneras de iniciar VS Code:

• Desde su icono en el directorio correspondiente

• Desde la raiz usando la linea de comandos

• Desde un directorio a nuestra elección

Iniciando desde el icono


Para iniciar desde el icono debemos buscarlo dentro del menú "Aplications" en la opción
"Development"

3
Iniciando desde la raíz usando la linea de comandos

$ code .

Una vez abierto de este modo para desplazarnos por nuestro sistema de archivos tendremos que
usar los comandos correspondientes en la terminal seleccionada que estemos usando en VS Code

Usamos el "." para indicar que lo abrimos desde el directorio en el que nos encontramos, en este
caso la raíz. Si queremos iniciar VS Code en un directorio concreto, lo hariamos con el mismo
comando ejecutado en ese directorio.

$ cd /home/vagrant/Documents
$ code .

El directorio elegido se convierte de este manera en nuestro workspace.

4
1.4.2. Instalando la extensión de Python

Una vez iniciado el IDE lo primero que tenemos que hacer es instalar la extensión de Python

1.4.3. Creando un entorno virtual

Un entorno virtual es un directorio que contiene una instalación de Python de una versión en
particular, además de unos cuantos paquetes adicionales.

Diferentes aplicaciones pueden entonces usar entornos virtuales diferentes. Para resolver el
problema de requerimientos en conflicto, la aplicación A puede tener su propio entorno virtual con
la versión 1.0 instalada mientras que la aplicación B tiene otro entorno virtual con la versión 2.0. Si
la aplicación B requiere que actualizar la librería a la versión 3.0, esto no afectará el entorno virtual
de la aplicación A.

Para la creación de este entorno Python 3 incluye el módulo "venv", que nos permitirá crearlo de
una manera sencilla.

• Seleccionamos la carpeta o directorio donde queremos crearlo.

• En la consola ejecutamos: Directorio> python -m venv env (podemos sustituir env, por el
nombre que queramos)

$ python -m venv virtualenv

Esto creará la carpeta tutorial-env si no existe, y también creará las subcarpetas conteniendo la
copia del intérprete Python, la librería estándar y los archivos de soporte.

Para activarlo ejecutamos en consola:

1. Si estamos trabajando en un sistema operativo Linux

5
$ source virtualenv/bin/activate
(virtualenv) [vagrant@fedora Documents]$

Para desactivarlo simplemente escribiremos:

$ deactivate

1. Si estamos trabajando en un sistema operativo Windows

PS C:\Users\benja\OneDrive\Documents\PRONOIDE\CURSOS\PYTHON> virtualenv\Scripts\Activate.ps1
(virtualenv) PS C:\Users\benja\OneDrive\Documents\PRONOIDE\CURSOS\PYTHON>

Es probable que al intentar ejecutar este scrip desde la PowerShell de Windows obtengamos un
error en el que nos diga que no se puede ejecutar el script, esto es debido a la política de
restricciones. Para solucionarlo ejecutamos la PowerShell como administrador y ejecutamos:

Set-ExecutionPolicy Unrestricted

Cambio de directiva de ejecución


La directiva de ejecución te ayuda a protegerte de scripts en los que no confías. Si cambias dicha directiva, podrías
exponerte a los riesgos de seguridad descritos en el tema de la Ayuda about_Execution_Policies en
https:/go.microsoft.com/fwlink/?LinkID=135170. ¿Quieres cambiar la directiva de ejecución?
[S] Sí [O] Sí a todo [N] No [T] No a todo [U] Suspender [?] Ayuda (el valor predeterminado es "N"): O

1.4.4. Seleccionando el interprete de Python

Como hemos visto, Python es un lenguaje interpretado y debemos indicarle a VS Code que
interprete queremos usar para poder usar todas las características del IDE. Para asegurarnos que
tenemos elegido el interprete correcto vamos a abrir el menú de comandos con

Ctrl+Shift+P

Una vez abierto este menú, escrbiremos en el buscador "Python: Select Interpreter"

6
Puede que el VS Code tarde en reconocer el entorno virtual, es recomendable reiniciar el sistema
para que aparezca y se pueda continuar.

1.4.5. Nuestro primer script en Python

Una vez iniciado el Entorno Virtual y el Interprete de Python vamos a escribir nuestro primer script
en Python usando VS Code. Para ello usamos cualquier de las opciones disponibles para crear un
nuevo archivo

#Este es nuestro primer script en Python usando VS Code


# Vamos a almacenar un string en una variable

wlcm = 'Bienvenidos al curso de Python'

print(wlcm)

Para ejecutarlo es tan sencillo como utilizar el icono a la derecha de la sección de archivos

7
Aunque hay otras tres manera de hacerlo:

• Botón derecho del ratón sobre cualquier parte vacía del script

• Botón derecho sobre una selección de lineas

• Desde Command Palette (Ctrl+Shift+P) y seleccionando Python: Start REPL

8
1.4.6. Configurando Debugg

Vamos a utilizar nuestro sencillo ejemplo para ver como podemos configurar el debugg de VS Code.
Lo primero es establecer un breakpoint con F9

Una vez seleccionado debemos iniciar el debugg con con F5. La primera vez que lo iniciemos nos
pedirá que elijamos el tipo

Una vez el ejecutado el resultado debería ser parecido a esto:

1.4.7. Instalando y usando módulos

En Python los módulos contienen interesantes librerias a las que podemos acudir en caso de
necesitar una función especifica. Para este ejemplo vamos a usar los módulos matplotlib y numpy.

Para empezar esta instalación vamos a crear un archivo standardplot.py

standarplot.py

import matplotlib.pyplot as plt


import numpy as np

x = np.linspace(0, 20, 100) # Create a list of evenly-spaced numbers over the range
plt.plot(x, np.sin(x)) # Plot the sine of each x point
plt.show()

Al ejecutarlo es normal que nos devuelva una salida en la que nos indique que no encuentra el
módulo matplotlib Para ello abrimos la terminal y procedemos de la siguiente manera:

9
python3 -m pip install matplotlib

python3 -m pip install numpy

python3 -m pip install tk

A través de está consola podemos instalar los módulos necesarios. Hay que recordar que estamos
dentro de un entorno virtual, por lo tanto la instalación del módulo dentro del mismo y solo se
podrá usar en dicho entorno.

Si volvemos a correr el script este será el resultado

10
Capítulo 2. Python y los tipos de datos
Antes de empezar con la sintáxis, un pequeño comentario sobre los tipos de datos y las variables en
Python

En Python una variable no representa lo mismo que en otros lenguajes de


programación. Una variable no es una ubicación de memoria donde guardamos
un dato. Es una mera etiqueta o alias, que apunta a un objeto. Este objeto puede
 ser cualquier cosa: un número, una cadena, una función, una instancia de una
clase, etc. Y una misma variable puede apuntar a diferentes objetos durante el
tiempo de ejecución del programa. Por eso decimos que Python usa tipado
dinámico

2.1. Introducción a la sintáxis de Python


Veamos los principales ladrillos del lenguaje Python.

Todos los ejemplos de código que veamos se pueden ejecutar directamente en el


 intérprete Python de consola. No es necesario crear un fichero aparte para ello.

2.1.1. Tipos numéricos

Python dispone de los tipos numéricos y las operaciones más habituales:

2 * 4 - (7 - 1) / 3 + 1.0 # 7.0

Las divisiones por cero lanzan un error

1/0

Traceback (most recent call last):


  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

La división entre enteros en Python 3 devuelve un número real, al contrario que en Python 2.

3 / 2 # 1.5

Se puede forzar que la división sea entera con el operador //:

3 // 2 # 1

11
Se puede elevar un número a otro con el operador **:

2 ** 16 # 65536

Otro tipo que nos resultará muy útil son los complejos:

2 + 3j # (2+3j)

abs(2 + 3j) # 3.605551275463989

Podemos convertir variables a int, float, complex, str…

int(18.6) # 18

round(18.6) # 19

float(1) # 1.0

complex(2) # (2+0j)

str(256568) # '256568'

Podemos comprobar el tipo de una variable:

a = 2.
type(a) # float

isinstance(a, float) # True

Otras funciones útiles son:

print('hola mundo') # hola mundo

La función print es la que se usa para imprimir resultados por pantalla. Por si lo
 ves en algún sitio, en Python 2 era una sentencia y funcionaba de manera distinta,
sin paréntesis y sin posibilidad de pasar argumentos adicionales.

12
max(1,5,8,7) # 8

min(-1,1,0) # -1

2.2. Asignación y operadores de comparación


La asignación se realiza con el operador =. Los nombres de las variables en Python pueden
contener caracteres alfanuméricos (empezando con una letra) a-z, A-Z, 0-9 y otros símbolos como '_'

Por cuestiones de estilo, las variables suelen empezar con minúscula, reservando la mayúcula para
clases. Algunos nombres no pueden ser usados porque son usados por python:

and, as, assert, break, class, continue, def, del, elif, else, except, exec, finally,
for, from, global, if, import, in, is, lambda, not, or, pass, print, raise, return,
try, while, with, yield

Un ejemplo simple de asignación a una variable:

a = 1 + 2j

En Python la asignación no imprime el resultado por pantalla, al contrario de


 como sucede en MATLAB y Octave (salvo que se incluya el punto y coma al final).
La mejor manera de visualizar la variable que acabamos de asignar es esta:

b = 3.14159
b

Podemos hacer asignación múltiple

x, y = 1, 2

Y aprovecharla para intercambiar el valor de dos variables, por ejemplo

x = y, y = x
x, y # (2, 1)

Los operadores de comparación son:

• '==' igual a

• '!=' distinto de

13
• '<' menor que

• '⇐' menor o igual que

• '>' mayor que

• '>=' mayor o igual que

Devolverán un booleano: 'True' o 'False'

x = 3
y = 1
x < y # False
x <= y # False
x > y # True
x >= y # True
x <= 2 < y # True
'a' < 'b' # True

Si comparamos elementos no comparables, obtendremos un error

1 < 'hola'

Traceback (most recent call last):


  File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str()

2.3. Strings
Uno de los mayores cismas entre Python 2 y Python 3

• En Python 2, hay unicode y str

• En Python 3, hay byte y unicode (todas las cadenas lo son)

Uno de los mantras del programador: El texto plano no existe. Cualquier texto ha
 de llevar asociado una codificación de caracteres, para saber cómo transformar los
bytes en texto legible

Hay idiomas que pueden ser completamente representados por la codificación de caracteres 'ASCII',
como el inglés. Para otros idiomas, como el español, es popular la codificación de caracteres CP-
1252, también conocida como windows-1252, por ser utilizada en sistemas Windows.

En CP-1252, los bytes 0 a 127 son iguales que en ASCII (se usan para las letras del alfabeto, los
números, etc). Los bytes 128 a 255 se utilizan para representar otros caracteres, como la ñ.

Tanto ASCII como CP-1252 utilizan un solo byte para representar cada caracter.

14
Otros idiomas, como el koreano, requieren más de un byte para representar todos los caracteres
disponibles.

¿Qué hacemos?: UNICODE: un estándar de codificación de caracteres

Al principio, surgieron dos versiones de Unicode:

• 'UTF-16': Codificación Unicode que usa 2 bytes para representar todos los caracteres. Para poder
representar idiomas que requieren de más de 65535 caracteres, utiliza algunos hacks un poco
sucios…

• 'UTF-32': Codificación Unicode que usa 4 bytes para representar todos los caracteres existentes.
Cualquier idioma conocido es representable.

Lo más razonable parecía usar 'UTF-32', ya que es capaz de representar cualquier idioma. Pero 4
bytes por caracter es mucho espacio. Hay idiomas, como el español o el inglés, que pueden
representarse con un byte por caracter.

Para solucionar este desperdicio de espacio que supone usar 4 bytes por caracter, surgió UTF-8

UTF-8 es una codificación Unicode de longitud variable. Usa la misma representación que 'ASCII'
para los 128 primeros caracteres, dos bytes para alfabeto latino extendido, 3 bytes para caracteres
chinos, y 4 para el resto (no representables con menos de 4 bytes)

Con 'UTF-8' se optimiza el espacio utilizado para representación, pero tiene la desventaja de que la
búsqueda de caracteres es de 'O(N)', siendo 'N' la longitud del texto, al usar diferente número de
bytes para diferentes caracteres

 En Python 3, Todos los strings son secuencias de caracteres Unicode

# Podemos asignar a una variable cualquier caracter unicode


s = '深入 Python'

# Su longitud, dependerá del número de Bytes usados para representarla


len(s) # 9

# El elemento string es un array de caracteres, podemos acceder a ellos como a cualquier array
s[0] # 深

En Python 3 el formateador de cadenas es muy poderoso. Podemos construir cadenas en base a


prácticamente cualquier cosa (variables de todo tipo, listas, tuplas…)

15
sources/t03/t03ej01.py

# Ejemplo sencillo de formateo de cadenas con variables posicionales


nombre = 'Jorge'
cualidad = 'sexy'

print("{0} es tremendamente {1}".format(nombre, cualidad))

# Puedo sustituir posiciones de un array


si_suffixes = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']

print("1000{0[0]} = 1{0[1]}".format(si_suffixes)) # 1000KB = 1MB

La documentación del formateador de cadenas es bastante densa. Se pueden consultar ejemplos


concretos aquí

En Python 2, el formateador de cadenas era muy parecido al estándar de lenguajes


como C. Aun es posible usarlo en Python 3, pero no se recomienda. También
 podemos formatear cadenas simplemente concatenando, pero tampoco se
recomienda

# Mala practica: concatenación simple de cadenas


def format_user_info(name, age, sex):
  return "Nombre: " + name + ", Edad: " + str(age) + ", Sexo: " + sex

print(format_user_info("Jorge", 36, "Hombre"))

# Mala practica: formateador antiguo


def format_user_info(name, age, sex):
  return "Nombre: %s, Edad: %d, Sexo: %s" % (name, age, sex)

print(format_user_info("Jorge", 36, "Hombre"))

sources/t03/t03ej04.py

# Buena practica: formateador nuevo


def format_user_info(name, age, sex):
  return "Nombre:{0}, Edad: {1}, Sexo: {2}".format(name, age, sex)

print(format_user_info("Jorge", 36, "Hombre"))

# Mejor practica: formateador nuevo


f"Nombre: {name}, Edad: {age}"

16
2.4. Secuencias: Listas y Tuplas
Otro tipo de datos muy importante que vamos a usar son las secuencias: las tuplas y las listas. Los
dos son conjuntos ordenados de elementos: las tuplas se demarcan con paréntesis y las listas con
corchetes.

sources/t03/t03ej05.py

una_lista = [1, 2, 3.0, 4 + 0j, "5"]


una_tupla = (1, 2, 3.0, 4 + 0j, "5")
assert(una_lista == una_tupla) # False

Para las tuplas, podemos incluso obviar los paréntesis:

tupla_sin_parentesis = 2,5,6,9,7
print(type(tupla_sin_parentesis)) # tuple

Puedes ver las tuplas como una estructura de datos similar al 'struct' de C: un
 almacén de longitud fija de datos potencialmente heterogéneos.

En los dos tipos podemos comprobar fácilmente la longitud, o si contienen un elemento concreto

una_lista = [1, 2, 3.0, 4 + 0j, "5"]


una_tupla = (1, 2, 3.0, 4 + 0j, "5")

assert(2 in una_lista) # True


assert(2 in una_tupla) # True
print(len(una_lista)) # 5
print(len(una_tupla)) # 5

Podemos indexar las secuencias, utilizando la sintaxis [<inicio>:<final>:<salto>]:

una_lista = [1, 2, 3.0, 4 + 0j, "5"]


una_tupla = (1, 2, 3.0, 4 + 0j, "5")

print(una_lista[0]) # Primer elemento, 1


print(una_tupla[1]) # Segundo elemento, 2
print(una_lista[0:2]) # Desde el primero hasta el tercero, excluyendo este: 1, 2
print(una_tupla[:3]) # Desde el primero hasta el cuarto, excluyendo este: 1, 2, 3.0
print(una_lista[-1]) # El último: 4 + 0j
print(una_tupla[:]) # Desde el primero hasta el último
print(una_lista[::2]) # Desde el primero hasta el último, saltando 2: 1, 3.0

 En Python la indexación comienza por 0.

17
Fíjate en dos detalles:

• Cuando especificamos índices negativos, recorremos el array desde el final hasta el principio.
Por eso [-1] da el último elemento, [-2] el penúltimo y así sucesivamente.

• Hemos utilizado la notación [::2] para el último caso. Si no especificamos nada para el inicio y el
fin, empezamos en el principio y terminamos en el final. Por eso [:] devuelve todos los
elementos de la secuencia.

Podemos complicarlo un poco más y hacer cosas como una lista de listas:

a = [
 [1, 2, 3],
 [4, 5],
]
print(a) # [[1, 2, 3], [4, 5]]
print(a[0]) # [[[1, 2, 3], [4, 5]]]
print(a[0][0]) # 1

Las listas en Python nos ayudan a programar como un pythonista. Por ejemplo, mediante el uso de
list comprehensions

# Mal, esto no es pythonista


numeros = range(10)
multiplos_de_3 = list() # Otra manera de declarar una lista
for element in numeros:
  if not element % 3:
  multiplos_de_3.append(element)

print(multiplos_de_3)

# Así sí que eres un buen Pythonista


numeros = range(10)
multiplos_de_3 = [element
  for element in numeros
  if not element % 3]

print(multiplos_de_3)

En cuanto a las secuencias, son especialmente útiles a la hora de desempaquetar datos

18
# Mal, esto no es Pythonista
mis_datos = ['perro', 'Miko', 8]
animal = mis_datos[0]
nombre = mis_datos[1]
edad = mis_datos[2]

print("Mi {a} se llama {n} y tiene {e} años".format(a=animal, n=nombre, e=edad))

# Bien, esto es Pythonista


mis_datos = ['perro', 'Miko', 7]
(animal, nombre, edad) = mis_datos

print("Mi {a} se llama {n} y tiene {e} años".format(a=animal, n=nombre, e=edad))

Si solo queremos desempaquetar parte de una estructura, podemos hacer uso de *, junto con el
placeholder _:

# Pythonista, pero mejorable


cadenas = ['uno', 'dos', 'tres', 'esto', 'me', 'sobra']
(primero, segundo, tercero, *resto) = cadenas

print(primero)
print(segundo)
print(tercero)

# Bien, esto sí es pythonista 100%


cadenas = ['uno', 'dos', 'tres', 'esto', 'me', 'sobra']
(primero, segundo, tercero, *_) = cadenas

print(primero)
print(segundo)
print(tercero)

Para los que hayáis trabajado con LISP, esto se llamaba desctructuring bind
 (extraer valores de una estructura de datos y enlazarlos con variables).

2.5. Otros tipos de datos: Conjuntos (Sets)


Los conjuntos, o set son contenedores de valores únicos. Pueden contener valores de diferentes
tipos mezclados. Además, podemos realizar con ellos las típicas operaciones de álgebra de
conjuntos (union, intersección, diferencia).

Es fácil crear sets, usando llaves, y metiendo los elementos separados por comas

19
sources/t03/t03ej15.py

# Creamos conjunto con un elemento, y vemos su tipo


a_set = {1}
print(type(a_set)) # set

# Podemos meter todos los elementos que queramos


a_set = {1, 2}
print(a_set)

# Se puede crear un set a partir de una lista, pero no recuerda el orden


a_list = ['a', 'b', 'mpilgrim', True, False, 42]
a_set = set(a_list)

print(a_set) # {False, True, 'a', 'mpilgrim', 42, 'b'}


print(a_list) # ['a', 'b', 'mpilgrim', True, False, 42]

Podemos crear un set vacío, pero ojo, que si intentamos crearlo con dos llaves y nada en medio, nos
crea un diccionario, no un conjunto (veramos a continuación los diccionarios)

# Creamos un set vacío


a_set = set()
print(type(a_set)) # set

not_sure = {}
print(type(not_sure)) # dict

Podemos añadir elementos a un set mediante add o mediante update

# Añadimos elementos con add. Si añadimos un elemento que ya existe, no hace nada
a_set = {1, 2, 3}
print(a_set) # {1, 2, 3}

a_set.add(4)
print(a_set) # {1, 2, 3, 4}

a_set.add(1)
print(a_set) # {1, 2, 3, 4}

# Con update, es como si llamaramos a add varias veces


a_set = {1, 2, 3}
print(a_set) # {1, 2, 3}

a_set.update({4, 5, 6})
print(a_set) # {1, 2, 3, 4, 5, 6}

20
Para borrar elementos de un set, tenemos 4 opciones

• El método discard

• El método remove

• El método pop

• El método clear

# pop devuelve el último elemento extraído, y clear elimina todos los elementos
a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
print(a_set) # {1, 3, 6, 10, 15, 21, 28, 36, 45}

a_set.pop() # 1
a_set.pop() # 3

a_set.clear()
print(a_set) # set()

# Discard y remove se diferencian en que, si el elemento no existe, remove lanza una excepcion y discard no
a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
print(a_set) # {1, 3, 36, 6, 10, 45, 15, 21, 28}

a_set.discard(10)
print(a_set) # {1, 3, 36, 6, 45, 15, 21, 28}

a_set.discard(10)
print(a_set) # {1, 3, 36, 6, 45, 15, 21, 28}

a_set.remove(21)
print(a_set) # {1, 3, 36, 6, 45, 15, 28}

a_set.remove(21) # Excepción

La última llamada devuelve esta excepción

Traceback (most recent call last):


  File "<stdin>", line 1, in <module>
KeyError: 21

Las operaciones típicas de teorías de conjuntos se pueden hacer también con set

# Operaciones típicas de conjuntos: in, union, intersection, difference, symmetric_difference


a_set = {2, 4, 5, 9, 12, 21, 30, 51, 76, 127, 195}
30 in a_set
31 not in a_set

b_set = {1, 2, 3, 5, 6, 8, 9, 12, 15, 17, 18, 21}


print(a_set.union(b_set)) # {1, 2, 195, 4, 5, 3, 6, 8, 9, 76, 12, 15, 17, 18, 21, 30, 51, 127}
print(a_set.intersection(b_set)) # {9, 2, 21, 12, 5}
print(a_set.difference(b_set)) # {195, 4, 76, 51, 30, 127}
print(a_set.symmetric_difference(b_set)) # {1, 3, 195, 6, 4, 8, 76, 15, 17, 18, 51, 30, 127}

21
La diferencia simétrica es el equivalente a la operación booleana XOR, pero con el
 tipo de dato 'set'. Es decir, nos devuelve los elementos que se encuentran en uno u
otro de los conjuntos a comparar, pero no en los dos a la vez.

Al igual que las secuencias, los set también nos ayudan a programar como un pythonista. Un caso
de uso típico es eliminar los duplicados de una lista: set lo hace de manera automática

# Podemos construir un set a partir de una lista, y se eliminarán los duplicados directamente
a_list = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
print(a_list) # [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

a_set = set(a_list)
print(a_set) # {1, 2, 3, 4}

2.5.1. Otros tipos de datos: diccionarios

Los diccionarios son como los set, pero tienen tuplas clave:valor, en lugar de elementos sueltos.

# Crear un diccionario es igual de sencillo que un set


a_dict = {'server': 'server.com', 'database': 'MySQL'}
print(a_dict) # {'database': 'MySQL', 'server': 'server.com'}

# Podemos acceder a los elementos de un diccionario usando indexación tipo array


print(a_dict['server']) # server.com
print(a_dict['database']) # MySQL

# Se pueden añadir todos los elementos que se quiera a un diccionario. Y también podemos modificar los existentes
a_dict['user'] = 'Jorge'
a_dict['database'] = 'PostgreSQL'
print(a_dict) # {'database': 'PostgreSQL', 'user': 'Jorge', 'server': 'server.com'}

# Se pueden mezclar elementos de diferentes tipos en un diccionario


SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
  1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

print(SUFFIXES[1000]) # ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']


print(SUFFIXES[1024]) # ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']

Los diccionarios, como no, también nos dan la oportunidad de programar como un pythonista.

22
# Mal, esto no es pythonista
frutas = {'verde': 'pera', 'amarilla': 'plátano', 'roja': 'fresa'}
if 'azul' in frutas:
  fruta_azul = frutas['azul']
else:
  fruta_azul = 'piña'

print(fruta_azul)

# Esto si es pythonista
frutas = {'verde': 'pera', 'amarilla': 'plátano', 'roja': 'fresa'}

fruta_azul = frutas.get('azul', 'piña')


print(fruta_azul)

23
2.6. Lab: Introducción y tipo de datos
2.6.1. Intro

Buenos días, Sr. Hunt. Su misión, si decide aceptarla, implica la recuperación de un artículo robado
denominado "Quimera''. Puede seleccionar dos miembros del equipo, pero es esencial que el tercer
miembro de su equipo sea Nyah Nordoff-Hall. Ella es una civil y una ladrona profesional altamente
capacitada. Tiene cuarenta y ocho horas para reclutar a la señorita Hall y reunirse conmigo en Sevilla
para recibir su asignación. Como siempre, si algún miembro de su equipo es capturado o asesinado, el
Secretario desautorizará todo conocimiento de sus acciones. Y Sr. Hunt, la próxima vez que se vaya de
vacaciones, tenga la bondad de decirnos adónde va. Este mensaje se autodestruirá en cinco segundos.

Problema

Usted es el científico informático que tiene que proteger valiosos datos de inteligencia nacional.
Estos datos se encuentran en una computadora sin conexión a internet en una habitación con suelo
táctil que detecta la presión. Pero ha descubierto que más de un espía se ha infiltrado y robado los
datos, por lo que debe implementar y probar el nuevo sistema de seguridad.

Se ha instalado un micrófono ultrasensible que registra todo el sonido de la habitación. La lista de


sonidos se almacena en una lista denominada dbs. Tan pronto como la puerta se cierre, si se detecta
ruido por encima de un umbral de 10 decibelios: la alarma sonará y alertará a todo el edificio.

Cada elemento de la lista de bases de datos corresponde a un intervalo de 5 segundos

dbs = [0, 1, 0, 0, 0, 0, 1, 0, 1, 23, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 20, 1, 1, 15, 1, 0, 0, 0, 40, 15, 0, 0]

# Variables
dbs = [0, 1, 0, 0, 0, 0, 1, 0, 1, 23, 1, 0, 1, 1, 0, 0, 0,
  1, 1, 0, 20, 1, 1, 15, 1, 0, 0, 0, 40, 15, 0, 0]

• Muestra los valores que estén por encima de 10 dBs (guárdalo en una lista)

Solución

db1 = []
for e in dbs:
  if e > 10:
  db1 += [e]

print(db1)

[23, 20, 15, 40, 15]

24
db_vol10 = [e for e in dbs if e > 10]
print(db_vol10)

[23, 20, 15, 40, 15]

#Si lo hago con una tupla, obtengo una funcion generadora


db_vol10 = (e for e in dbs if e > 10)

[23, 20, 15, 40, 40]

• Muestra el momento (índices de la lista) en los cuales el sonido excede de los 10 dBs

Solución

vol10_i = []
for i in range(len(dbs)):
  if dbs[i] > 10:
  vol10_i += [i]
print(f"los indices son {vol10_i}")

los indices son [9, 20, 23, 28, 29]

vol10_i2 = [ i for i in range(len(dbs)) if dbs[i] > 10]


print(f"los indices son {vol10_i2}")

los indices son [9, 20, 23, 28, 29]

Combina los dos apartados anteriores, obtén una lista de tuplas donde el primer elemento de la
tupla sea el índice y el segundo el valor de los sonidos superiores a 10 dBs

Solución

vol10 = []
for i in vol10_i:
  vol10 += [(i, dbs[i])]
print(vol10)

[(9, 23), (20, 20), (23, 15), (28, 40), (29, 15)]

25
vol10 = [(i, dbs[i]) for i in range(len(dbs)) if dbs[i] > 10]
print(vol10)

[(9, 23), (20, 20), (23, 15), (28, 40), (29, 15)]

a = list(enumerate(dbs))
dbe = []
for (e, e1) in a:
  if e1 > 10:
  dbe += [(e, e1)]
print(f"Los valores son {dbe}")

Los valores son [(9, 23), (20, 20), (23, 15), (28, 40), (29, 15)]

dbe = [(e, e1) for (e, e1) in list(enumerate(dbs)) if e1 > 10]


dbe

[(9, 23), (20, 20), (23, 15), (28, 40), (29, 15)]

• Ethan es descubierto si dos sonidos consecutivos son mayores de 10 dB

◦ Imprime "Alarm!" si se dan esas condiciones

◦ Pista: ten cuidado con los extremos, no tengas un error de tipo IndexError: list index out of
range

Solución

for i in range(len(dbs)):
  if dbs[i] >10 and dbs[i+1] > 10:
  print('ALARM')
  print(f"los indices consecutivos son {i} y {i+1} con valores {dbs[i]} y {dbs[i+1]}")

• Ahora vamos a tener el rol del hacker que acompaña a Ethan. Y por tanto, engañaremos al
nuevo sistema de seguridad de sonidos que han implementado. Y para toda señal igual o
superior a 10 dB la cambiaremos por 2 dB

Solución

dbsm = [2 if e>10 else e for e in dbs]


print(dbsm)

[0, 1, 0, 0, 0, 0, 1, 0, 1, 2, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 2, 1, 1, 2, 1, 0, 0, 0, 2, 2, 0, 0]

26
Problema del caracol y el pozo

Un caracol cae al fondo de un pozo de 125 cm. Cada día el caracol se eleva 30 cm. Pero por la noche,
mientras duerme, se desliza 20 cm porque las paredes están mojadas. ¿Cuántos días se necesitan
para escapar del pozo?

# Variables
well_height = 125
daily_advance = 30
night_retreat = 20

Solución

well_height = 125
daily_advance = 30
night_retreat = 20
adv = (daily_advance - night_retreat)
dia = 1
while well_height >= daily_advance:
  dia += 1
  rest_height = well_height - adv
  well_height = rest_height

print(f"el caracol tarda {dia} dias en salir del pozo")

el caracol tarda 11 dias en salir del pozo

• Ahora en lugar de avanzar una cantidad constante diaria, su avance está definido por una lista
de los cm que avanza cada día

well_height = 125
advance_cm = [30, 21, 33, 77, 44, 45, 23, 45, 12, 34, 55]
night_retreat = 20

Solución

27
#la idea sigue siendo la misma, solo que ahora variara el avance diario.
dia = 1
adv = [advance_cm[i] - night_retreat for i in range(len(advance_cm))]
for i in range(len(advance_cm)):
  while well_height > advance_cm[i]:
  dia += 1
  rest_height = well_height - adv[i]
  well_height = rest_height
  break
print(f"el caracol tarda {dia} dias en salir del pozo")

el caracol tarda 8 dias en salir del pozo

Bus

Este autobús tiene un sistema de control de entrada y salida de pasajeros para monitorizar el
número de ocupantes que transporta y así detectar cuando hay una capacidad demasiado alta.

En cada parada, la entrada y salida de pasajeros está representada por una tupla que consta de dos
números enteros.

bus_stop = (in, out)

La sucesión de paradas está representada por una lista de estas tuplas.

stops = [(in1, out1), (in2, out2), (in3, out3), (in4, out4)]

# Variables

stops = [(10, 0), (4, 1), (3, 5), (3, 4), (5, 1), (1, 5), (5, 8), (4, 6), (2, 3)]

• Calcula el número de paradas

Solución

paradas = len(stops)
print(f"El numero de paradas es {paradas}")

El numero de paradas es 9

• Asigna a una variable una lista cuyos elemento son los números de los pasajeros en cada
parada. Cada elemento depende del anterior y de los pasajeros que suban y bajen

Solución

28
A = []
for i in range(len(stops)):
  for i2 in range(1):
  #Resto cada tupla para que me de la diferencia de entre "in" y "out"
  A += [stops[i][i2] - stops[i][i2 + 1]]
passengers = [A[0]]
B = A[0]
for i in range(len(A) - 1):
  B = B + A[i + 1]
  passengers += [B]
print(f"los pasajeros que quedan despues de cada para es {passengers}")

los pasajeros que quedan despues de cada parada es [10, 13, 11, 10, 14, 10, 7, 5, 4]

Solución * Encuentra la máxima ocupación del bus

# 3.
Max_ocu = max(passengers)
print(f"La maxima ocupación del bus es {Max_ocu}")

La maxima ocupación del bus es 14

Duelo de magos

Estás presenciando una batalla épica entre dos poderosos hechiceros: Gandalf y Saruman. Cada
hechicero tiene 10 hechizos de poder variable en su mente y los lanzarán uno tras otro. El ganador
del duelo será el que gane más de esos enfrentamientos entre hechizos. Los hechizos se
representan como una lista de 10 números enteros cuyo valor es igual al poder del hechizo.

gandalf = [10, 11, 13, 30, 22, 11, 10, 33, 22, 22]
saruman = [23, 66, 12, 43, 12, 10, 44, 23, 12, 17]

Por ejemplo: 1. Saruman gana el primer enfrentamiento: 10 contra 23, gana 23 2. El segundo gana
Saruman: 11 contra 66, gana 66 3. etc.

# Variables
gandalf = [10, 11, 13, 30, 22, 11, 10, 33, 22, 22] # conjuros de Gandalf
saruman = [23, 66, 12, 43, 12, 10, 44, 23, 12, 17] # conjuros Saruman
gandalf_wins = 0 # victorias de Gandalf
saruman_wins = 0 # victorias de Saruman
tie = 0 # empate

• Imprime por pantalla cuántas victorias tiene cada uno y el número de empates

Solución

29
gandalf = [10, 11, 13, 30, 22, 11, 10, 33, 22, 22] # conjuros de Gandalf
saruman = [23, 66, 12, 43, 12, 10, 44, 23, 12, 17] # conjuros Saruman
gandalf_wins = 0 # victorias de Gandalf
saruman_wins = 0 # victorias de Saruman
tie = 0
for i in range(len(gandalf)):
  if gandalf[i] > saruman[i]:
  gandalf_wins += 1
  elif gandalf[i] < saruman[i]:
  saruman_wins += 1
  else:
  tie += 1
print(f"Las victorias de Gandalf son {gandalf_wins}")
print(f"Las victorias de Saruman son {saruman_wins}")
print(f"{tie} empates")

Las victorias de Gandalf son 6


Las victorias de Saruman son 4
0 empates

#Si en vez con comparaciones directas lo hacemos con la comparación de la diferencia.


gandalf_wins = 0
saruman_wins = 0
empate = 0
for i in range(len(gandalf)):
  if (gandalf[i] - saruman[i]) > 0:
  gandalf_wins += 1
  elif(gandalf[i] - saruman[i]) < 0:
  saruman_wins += 1
  else:
  empate += 1

print(f'victorias de Gandalf: {gandalf_wins}')


print(f'victorias de Saruman: {saruman_wins}')
print(f'Empates: {empate}')

victorias de Gandalf: 6
victorias de Saruman: 4
Empates: 0

Comprueba quien ha ganado la batalla

Solución

30
# Comprueba quién ha ganado la batalla
Victoria = 'Gandalf' if gandalf_wins > saruman_wins else 'Saruman'
print(f'la victoria es de {Victoria}')

la victoria es de Gandalf

• Ahora en lugar de tener una lista con el poder de sus hechizos, la lista será el nombre de los
hechizos, y tendremos un diccionario con el poder de dichos hechizos

POWER = {
  'Fireball': 50,
  'Lightning bolt': 40,
  'Magic arrow': 10,
  'Black Tentacles': 25,
  'Contagion': 45
}

gandalf = ['Fireball', 'Lightning bolt', 'Lightning bolt', 'Magic arrow', 'Fireball',


  'Magic arrow', 'Lightning bolt', 'Fireball', 'Fireball', 'Fireball']
saruman = ['Contagion', 'Contagion', 'Black Tentacles', 'Fireball', 'Black Tentacles',
  'Lightning bolt', 'Magic arrow', 'Contagion', 'Magic arrow', 'Magic arrow']

Cambia las listas de gandalf y saruman para que en lugar de los nombres tengan el poder de los
hechizos

Solucion

31
POWER = {
  'Fireball': 50,
  'Lightning bolt': 40,
  'Magic arrow': 10,
  'Black Tentacles': 25,
  'Contagion': 45
}

gandalf = ['Fireball', 'Lightning bolt', 'Lightning bolt', 'Magic arrow', 'Fireball',


  'Magic arrow', 'Lightning bolt', 'Fireball', 'Fireball', 'Fireball']
saruman = ['Contagion', 'Contagion', 'Black Tentacles', 'Fireball', 'Black Tentacles',
  'Lightning bolt', 'Magic arrow', 'Contagion', 'Magic arrow', 'Magic arrow']
gandalf2 = [POWER.get(gandalf[i]) for i in range(len(gandalf))]
saruman2 = [POWER.get(saruman[i]) for i in range(len(saruman))]
gandalf = gandalf2
saruman = saruman2
print(f"gandalf: {gandalf}")
print(f"Saruman: {saruman}")

gandalf: [50, 40, 40, 10, 50, 10, 40, 50, 50, 50]
Saruman: [45, 45, 25, 50, 25, 40, 10, 45, 10, 10]

32
Capítulo 3. Estructuras de control
3.1. Condicionales
Los condicionales en Python tienen este aspecto:

if <condition>:
  <do something>
elif <condition>:
  <do other thing>
else:
  <do other thing>

Posiblemete ya te hayas percatado de ello, pero en Python los bloques se delimitan


por sangrado, utilizando siempre cuatro espacios. Cuando ponemos los dos puntos
al final de la primera línea del condicional, todo lo que vaya a continuación con un
 nivel de sangrado superior se considera dentro del condicional. En cuanto
escribimos la primera línea con un nivel de sangrado inferior, hemos cerrado el
condicional. Si no seguimos esto a rajatabla Python nos dará errores; es una forma
de forzar a que el código sea legible.

x,y = 8, 4

if x > y:
  print("x es mayor que y")
  print("x es el doble de y")

if x > y:
  print("x es mayor que y")
else:
  print("x es menor o igual que y")

if x < y:
  print("x es menor que y")
elif x == y:
  print("x es igual a y")
else:
  print("x es mayor que y")

Las reglas del buen pythonista dicen que evites las comparaciones directas con valores como 'True',
'False' o 'None' (el equivalente a 'null' en otros lenguajes de programación)

33
sources/t03/t03ej24.py

# Mal, esto no es pythonista


a = None
if a == None:
  print('a vale None')

# Bien, esto es pythonista


a = None
if not a:
  print('a tiene un valor nulo')

También nos dicen que evitemos usar demasiados condicionales en la misma sentencia, si es
posible

# Mal, esto no es pythonista


color = 'verde'
if color == 'verde' or color == 'rojo' or color == 'azul':
  print('El {0} es un color primario'.format(color))

# Bien, esto es pythonista


color = 'verde'
if color in ('verde', 'rojo', 'azul'):
  print('El {0} es un color primario'.format(color))

3.2. Bucles
En Python existen dos tipos clásicos de bucles:

• Bucles while

• Bucles for

3.2.1. Bucles while

Los bucles while repetiran las sentencias anidadas en él mientras se cumpla una condición:

while <condition>:
  <things to do>

Como en el caso de los condicionales, los bloques se separan por indentación sin necesidad de
sentencias del tipo end

34
ii = -2
while ii < 5:
  print(ii)
  ii += 1 # Operador in-place +=

Se puede interrumpir el bucle a la mitad con la sentencia break:

sources/t03/t03ej27.py

ii = 0
while ii < 5:
  print(ii)
  ii += 1
  if ii == 3:
  break

Un bloque else justo después del bucle se ejecuta si este no ha sido interrumpido por nosotros:

ii = 0
while ii < 5:
  print(ii)
  ii += 1
  if ii == -8:
  break
else:
  print("El bucle no ha sido interrumpido")

Hay maneras más pythonistas que otras de escribir bucles while, como no podría ser de otra forma.
Por ejemplo, si queremos recorrer una lista de elementos:

# Mal, esto no es pythonista


my_list = ['Larry', 'Moe', 'Curly']
index = 0
while index < len(my_list):
  print (my_list[index])
  index += 1

# Bien, esto es pythonista


my_list = ['Larry', 'Moe', 'Curly']
for element in my_list:
  print (element)

3.2.2. Bucles for

El otro bucle en Python es el bucle for, y funciona de manera un que puede resultar chocante al
principio. La idea es recorrer un conjunto de elementos:

35
for <element> in <iterable_object>:
  <do whatever...>

for ii in (1,2,3,4,5):
  print(ii)

for nombre in "Juanlu", "Siro", "Carlos":


  print(nombre)

for ii in range(3):
  print(ii)

for jj in range(2, 5):


  print(jj)

Al igual que en los bucles while, se puede usar else para ejecutar código después de un bucle,
siempre que éste no haya sido interrumpido

# Mal, esto no es pythonista


pares = [2, 4, 6, 8, 10, 12, 14]
hay_impar = False
for n in pares:
  if n % 2:
  hay_impar = True
  break

if not hay_impar:
  print('Todos los números son pares')

# Bien, esto es pythonista


pares = [2, 4, 6, 8, 10, 12, 14]
for n in pares:
  if n % 2:
  break

# El else se va a ejecutar después del bucle siempre y cuando no lo rompamos de manera abrupta con un break
else:
  print('Todos los números son pares')

Si queremos seguir ejecutando un bucle y excluir algún elemento del mismo podemos hacer uso de
continue, que representaria el caso contrario a break que vimos en el ejemplo del bucle while. Si
por ejemplo definimos el siguiente bucle:

36
number = 0
for number in range(10):
  if number == 5:
  print (f"numero {number} no se va a ejecutar pero el bucle va a continuar")
  continue
  print('El numero es el ' + str(number))
print ("Ya estamos fuera del bucle")

En cuanto a programar bucles for como un pythonista, existe el caso de uso en el que queremos
acceder al índice de un elemento desde dentro del bucle. Para ello usamos enumerate:

# Mal, esto no es pythonista. Tipico vicio de programadores C/C++


my_container = ['Larry', 'Moe', 'Curly']
index = 0
for element in my_container:
  print ('{} {}'.format(index, element))
  index += 1

# Bien, esto es pythonista


my_container = ['Larry', 'Moe', 'Curly']
for index, element in enumerate(my_container):
  print ('{} {}'.format(index, element))

37
3.3. Lab: Estructuras de control
3.3.1. Ejercicio 1:

Usted es el científico informático que tiene que proteger valiosos datos de inteligencia nacional.
Estos datos se encuentran en una computadora sin conexión a internet en una habitación con suelo
táctil que detecta la presión. Pero ha descubierto que más de un espía se ha infiltrado y robado los
datos, por lo que debe implementar y probar el nuevo sistema de seguridad.

Se ha instalado un micrófono ultrasensible que registra todo el sonido de la habitación. La lista de


sonidos se almacena en una lista denominada dbs. Tan pronto como la puerta se cierre, si se detecta
ruido por encima de un umbral de 10 decibelios: la alarma sonará y alertará a todo el edificio.

Cada elemento de la lista de bases de datos corresponde a un intervalo de 5 segundos

• Muestra los valores que estén por encima de 10 dBs (guárdalo en una lista)

• Muestra el momento (índices de la lista) en los cuales el sonido excede de los 10 dBs

• Combina los dos apartados anteriores, obtén una lista de tuplas donde el primer elemento de la
tupla sea el índice y el segundo el valor de los sonidos superiores a 10 dBs

• Ethan es descubierto si dos sonidos consecutivos son mayores de 10 dB:

◦ Imprime "Alarm!" si se dan esas condiciones

◦ Pista: ten cuidado con los extremos, no tengas un error de tipo IndexError: list index out of
range

• Ahora vamos a tener el rol del hacker que acompaña a Ethan. Y por tanto, engañaremos al
nuevo sistema de seguridad de sonidos que han implementado. Y para toda señal superior a 10
dB la cambiaremos por 2 dB

dbs = [0, 1, 0, 0, 0, 0, 1, 0, 1, 23, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 20, 1, 1, 15, 1, 0, 0, 0, 40, 15, 0, 0]

Solución

38
# Variables
dbs = [0, 1, 0, 0, 0, 0, 1, 0, 1, 23, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 20, 1, 1, 15, 1, 0, 0, 0, 40, 15, 0, 0]

# Muestra los valores que estén por encima de 10 dBs (guárdalo en una lista)
db1 = []
for e in dbs:
  if e > 10:
  db1 += [e]

print(db1)

db_vol10 = [e for e in dbs if e > 10]


print(db_vol10)

# Muestra el momento (índices de la lista) en los cuales el sonido excede de los 10 dBs
vol10_i = []
for i in range(len(dbs)):
  if dbs[i] > 10:
  vol10_i += [i]
print(f"los indices son {vol10_i}")

vol10_i2 = [ i for i in range(len(dbs)) if dbs[i] > 10]


print(f"los indices son {vol10_i2}")

print(f"valores mayores que 10. db1={db1}")


print(f"indice de los valores >10 vol10_i={vol10_i}")
# Combina los dos apartados anteriores, obtén una lista de tuplas
# donde el primer elemento de la tupla sea el índice y el segundo el valor
# de los sonidos superiores a 10 dBs
vol10 = []
for i in vol10_i:
  vol10 += [(i, dbs[i])]
print(vol10)

vol10 = [(i, dbs[i]) for i in range(len(dbs)) if dbs[i] > 10]


print(vol10)

a = list(enumerate(dbs))
dbe = []
for (e, e1) in a:
  if e1 > 10:
  dbe += [(e, e1)]
print(f"Los valores son {dbe}")

dbe = [(e, e1) for (e, e1) in list(enumerate(dbs)) if e1 > 10]


dbe

# Ethan es descubierto si dos sonidos consecutivos son mayores de 10 dB


# Imprime "Alarm!" si se dan esas condiciones
# Pista: ten cuidado con los extremos, no tengas un error de tipo
# IndexError: list index out of range
for i in dbs:
  if dbs[i]>10:
  elif dbs[i+1]:
print('ALARM')

Ahora vamos a tener el rol del hacker que acompaña a Ethan. Y por tanto, engañaremos al nuevo
sistema de seguridad de sonidos que han implementado. Y para toda señal superior a 10 dB la

39
cambiaremos por 2 dB

dbsm = [2 if e>10 else e for e in dbs]


print(dbsm)

3.3.2. Laboratorio 02

Escribir un programa que le pregunte al usuario por una palabra y le siga preguntando mientras no
la acierte, o hasta un máximo de 10 veces. La comparación la puedes hacer case insensitive, y para
leer la entrada de usuario, puedes usar la funcion input

Solución

veces = 0
palabra_correcta = "cacahuete"
palabra = ""

while palabra.lower() != palabra_correcta.lower() and veces < 10:


  palabra = input("A ver si adivinas la palabra. Te quedan {} intentos: ".format(10 - veces))
  if palabra.lower() == palabra_correcta.lower():
  print('¡La has adivinado!, la palabra es {}'.format(palabra_correcta.lower()))
  break
  veces = veces + 1
else:
  print("Lo sentimos, no la has adivinado :-(. La palabra era {}".format(palabra_correcta))

40
Capítulo 4. Definición de funciones
Ya lo hemos visto en algún ejemplo, pero para definir nuestra propia función utilizamos la
sentencia def seguida del nombre de la misma y entre paréntesis los argumentos de entrada. La
primera línea de la función suele ser una cadena de documentación.

# Definimos función "funcion" que no haga nada


def funcion(x, y):
  """Función de prueba."""
  pass

Los valores de retorno de la función se especifican con la sentencia return. Por ejemplo:

def al_cuadrado(x):
  """Función que eleva un número al cuadrado."""
  y = x ** 2
  return y

assert(al_cuadrado(4) == 16)

Las funciones permiten la definición de parámetros por defecto

# Definir función que multiplica dos números. Por defecto, el segundo de ellos valdrá 2
def multiplica(x, y=2.0):
  """Multiplica dos números, por defecto el primero por 2."""
  return x * y

assert(multiplica(2, 3) == 6)
assert(multiplica(4) == 8) # 8

 Evita el uso de '[]', '{}' o '''' como parámetros por defecto para funciones

41
Potencial error aquí. El valor por defecto de una función es evaluado una sola vez. Si dicho argumento es un objeto
mutable,
como una lista o un diccionario, la función acumula los argumentos pasados en llamadas sucesivas
"""
def f(a, L=[]):
  L.append(a)
  return L

print (f(1)) # [1]


print (f(2)) # [1, 2]
print (f(3)) # [1, 2, 3]

# Si queremos evitar que se acumule el valor del argumento por defecto, lo podemos escribir así
def f(a, L=None):
  if not L:
  L = []
  L.append(a)
  return L

print (f(1)) # [1]


print (f(2)) # [2]
print (f(3)) # [3]

4.1. Funciones con parametros opcionales *args y


**kwargs
Además de parámetros por defecto, las funciones aceptan parámetros opcionales mediante el uso
de *args y **kwargs

Definiendo *args como parámetro de una función, permitimos que dicha función reciba cualquier
número de parámetros. Dentro de la función, *args es desempaquetado como una lista, de manera
que espera que se le pase una lista de elementos

def func(*args):
  print("Argumentos:")
  for arg in args:
  print(arg)

func(134, 543, "sfds", [])

La salida que produce la función es:

Argumentos:
134
543
sfds
[]

Definiendo kwargs como parámetro de una función, permitimos que dicha función reciba un
diccionario con un número arbitrario de elementos clave:valor. Dentro de la función, kwargs

42
es desempaquetado como un diccionario

def func(**kwargs):
  print("Argumentos:")
  for k,v in kwargs.items():
  print("{0} => {1}".format(k, v))

func(a=134, b=543, c="sfds")

La salida de la función anterior es:

Argumentos:
a => 134
b => 543
c => sfds

Por supuesto, se pueden usar ambos a la vez:

def func(*args, **kwargs):


  print ("Args:")
  for arg in args:
  print(arg)

  print ("Kwargs:")
  for k,v in kwargs.items():
  print("{0} => {1}".format(k, v))

func(2342, 43534, 5645, x=456, y=3)

La salida de la función anterior es:

Args:
2342
43534
5645
Kwargs:
x => 456
y => 3

4.2. PEP8
PEP8 es una guía de estilo para desarrollar en Python. Fue creada en 2001, y su última actualización
hasta la fecha es de 2013.

Podemos resumir los puntos más importantes de la guía de estilos PEP8:

43
• Usa sangrado de 4 espacios, no tabuladores

• Acota las líneas a 79 caracteres.

• Usa líneas en blanco para separar funciones y bloques de código dentro de ellas.

• Pon los comentarios en líneas aparte si es posible.

• Usa cadenas de documentación (docstrings).

• Pon espacios alrededor de los operadores y después de coma.

• Usa la convención minuscula_con_guiones_bajos para los nombres de las funciones y las


variables.

• Aunque Python 3 te lo permite, no uses caracteres especiales para los identificadores.

Traducido de aquí

Para comprobar si nuestro código cumple con las reglas de estilo, existe el paquete pep8.

Lo instalamos mediante

pip install pep8

Y lo usamos así

pep8 mifichero.py

Se nos informará de las violaciones a pep8 que encierra nuestro código, si las hay.

44
4.3. Lab: Funciones
4.3.1. Ejercicio 1

Crear una función que reciba un número y nos diga si es mayor, menor o igual a 5. La función ha
de tener una sola línea, y ha de estar escrita lo más pythonista posible. Para ello, es posible que
tengas que averiguar lo que es un ternary operator, y cómo se implementa en Python.

Solución

def comparar(num):
  return 'El número es menor que 5' if num < 5 else 'El número es igual a 5' if num == 5 else 'El número es mayor que
5'

print(comparar(2))
print(comparar(5))
print(comparar(7))

4.3.2. Ejercicio 2

Implementar la función sumatorio, de manera que devuelva la suma de los n primeros números

Solución

def sumatorio(n):
  """
  Suma los n primeros números.
  Ejemplos
  --------
  >>> sumatorio(4)
  10
  """
  s = 0
  for x in range(1, n + 1):
  s = s + x

  return s

assert(sumatorio(4) == 10)

4.3.3. Ejercicio 3

Implementar la función sumatorio\_tope, de manera que sume los n primeros números hasta que
lleguen al valor establecido como tope.

Solución

45
def sumatorio_tope(tope):
  """
  Va sumando números naturales hasta llegar al tope.

  Ejemplos
  --------
  >>> sumatorio_tope(9)
  6
  # 1 + 2 + 3

  >>> sumatorio_tope(10)
  10 # 1 + 2 + 3 + 4

  >>> sumatorio_tope(12)
  10 # 1 + 2 + 3 + 4

  >>> sumatorio_tope(16)
  15 # 1 + 2 + 3 + 4 + 5
  """
  s = 0
  n = 1
  while s + n <= tope:
  s += n
  n += 1

  return s

assert(sumatorio_tope(9) == 6)
assert(sumatorio_tope(10) == 10)
assert(sumatorio_tope(12) == 10)
assert(sumatorio_tope(16) == 15)

4.3.4. Ejercicio 4

Crear una función para determinar si la longitud de un determinado examen cumple la normativa.
Dicha normativa es: si un examen dura más de 3 horas, entonces debe tener un descanso

Los argumentos de la función son el tiempo en horas y un valor booleano que indica si hay
descanso o no. Puntos extra si lo haces en una sola línea.

Solución

46
def cumple_normativa(tiempo, descanso):
  """
  Decide si los criterios establecidos para un determinado examen pasan la normativa o no.
  @param tiempo: tiempo en horas
  @param descanso: booleano que define si hay descanso o no
  @return: True si se cumple la normativa. False en caso contrario
  """
  return True if tiempo <= 3 else descanso

assert(cumple_normativa(4, True) is True)


assert(cumple_normativa(5, False) is False)

4.3.5. Ejercicio 5

Calcular

x = \sqrt{S}

Mediante el método babilónico. A saber:

x = \frac{S} {2}

x = \frac{1} {2}(x + \frac{S} {x})

Repetir el paso 2 hasta que se cumpla que el valor obtenido no cambie con respecto al obtenido en
el paso anterior (hasta que se alcance la convergencia). Tendras que importar el módulo math

Solución

47
import math

Para Python inferior a 3.5, podemos usar esta función-


def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
  return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

def raiz(S):
  """
  Devuelve la raiz cuadrada de S calculada mediante el método babilónico
  """
  x = S / 2
  while True:
  temp = x
  x = (x + S / x) / 2

  # Doc de isclose: https://docs.python.org/3/library/math.html#math.isclose


  # Guia para precisión:
https://en.wikipedia.org/wiki/Machine_epsilon#Values_for_standard_hardware_floating_point_arithmetics
  if math.isclose(temp, x):
  return x

S = 10
x = raiz(S)

Puedes comparar x con math.sqrt(S). Es posible que el valor sea ligeramente diferente, debido a la
precisión print(x) print(math.sqrt(S))

Como ejercicio ampliado, se propone afinar el algoritmo. Por ejemplo:

• Que el error relativo aceptado esté por debajo de un umbral

• Que se realice un número máximo de iteraciones, y si no se ha llegado tras ellas al criterio de


convergencia, se lance una excepción

4.3.6. Ejercicio 6

La Secuencia de Fibonacci es:

F_n = F_{n-1} + F_{n-2}

Implementar:

• Una función 'fib(n)' que devuelve el término n de la secuencia de Fibonacci usando iteración +
Una función 'fib_recursivo(n)' que haga lo mismo pero de manera recursiva

• Una función 'n_primeros(n)' que devuelva una lista con los n primeros números de la secuencia,
llamando a cualquiera de las dos funciones anteriores. Puntos extra si obtienes una expresión
Python equivalente para obtener lo mismo (aunque la manera de obtener esa expresión se verá
en el tema siguiente…)

Puedes obviar los problemas de memoria que supondría obtener un término muy grande. No te
preocupes de eso por ahora…

Solución

48
def fib(n):
  """
  Devuelve el término n de la secuencia de Fibonacci
  @param n: posición a devolver
  @return: El término n de la secuencia
  """
  a, b = 0, 1
  for i in range(n):
  a, b = b, a + b # Bendita asignación múltiple
  return a

def fib_recursivo(n):
  """
  Función recursiva que devuelve el término n de la secuencia de Fibonacci
  @param n: posición a devolver
  @return: El término n de la secuencia
  """
  if n == 0:
  res = 0
  elif n == 1:
  res = 1
  else:
  res = fib_recursivo(n - 1) + fib_recursivo(n - 2)
  return res

def n_primeros(n):
  """
  Devuelve los n primeros términos de la secuencia de Fibonacci
  @param n: Número de términos a devolver
  @return: Lista con los n primeros términos
  """
  F = fib_recursivo
  lista = []
  for ii in range(n):
  lista.append(F(ii))
  return lista

  # Esto es mas pythonista, lo veremos en el siguiente tema


  #return [fib_recursivo(ii) for ii in range(n)]

assert(fib(5) == 5)
assert(fib_recursivo(0) == 0)
assert(fib_recursivo(3) == 2)
assert(fib(8) == 21)
assert(n_primeros(10) == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34])

4.3.7. Ejercicio 7

Crear una función que nos devuelva el dia de que es hoy en formato texto.

49
Pista: Usa el módulo 'datetime' para averiguar la fecha de hoy

Pista: Usa el módulo 'calendar' para obtener una lista con los días de la semana

Pista: Usa el módulo ['locale'] para obtener esos días escritos en español

Solución

# Estos son los módulos que necesitas


import datetime
import calendar
import locale
def get_today_as_string():
  """
  Funcion que me dice el día de la semana que es hoy, en formato string.
  Pequeña guia de implementación:

  1. Obtén el día de la semana que es hoy, en formato entero (1, 2, 3, etc). Usa datetime.
  2. Obtén en una lista, los días de la semana en español en formato string. Usa calendar y locale.
  3. Usa el resultado de 1 para indexar el array de 2

  @returns: "Lunes", "Martes"...


  """
  locale.setlocale(locale.LC_ALL, 'es_ES.UTF-8')

  # El dia de la semana en formato entero, empezando por 1


  today_date = datetime.date.today()
  num_weekday = today_date.isoweekday()

  # Lista con los días de la semana en formato cadena


  weekdays = list(calendar.day_name)

  return weekdays[num_weekday - 1]

print("Hoy es {}".format(get_today_as_string()))

4.3.8. Ejercicio 8

Implementa una función 'suma(*args)' que devuelva la suma de una lista de argumentos de
longitud arbitraria. Puedes asumir que todos son números.

Solución

50
def suma(*args):
  """
  Función que devuelve el sumatorio de todos sus argumentos
  @param *args: lista de números de longitud arbitraria
  @returns: Sumatorio de todos los argumentos
  """
  s = args[0]
  for a in args[1:]:
  s = s + a
  return s

assert(suma(1, 2, 3, 4, 5, 6, 7) == 28)

51
Capítulo 5. Clases y objetos en Python
Python es un lenguaje que permite la programación orientada a objetos, pese a no haber sido
especificamente diseñado para ello. Podemos crear nuestras propias clases, heredar de otras clases
ya existentes e instanciar cualquier clase para trabajar con ella. Veamos cómo.

5.1. Creando clases en Python


Crear una clase en Python no podría ser más sencillo

# Clase vacía en Python


class MiClase:
  pass

c = MiClase()

La palabra clave pass en Python no tiene ningún efecto. Simplemente no hace


 nada. Nos salva de tener un error de indentación, en este caso particular.

Una clase puede tener atributos y métodos. Y se pueden definir de manera estática (cuando
escribimos el código de la clase) como de manera dinámica (en tiempo de ejecución)

# Esta clase tiene atributos estáticos y dinámicos


class MiClase:
  # Estos atributos serían compartidos por todas las instancias de la clase
  x = 1
  y = 2

c = MiClase()
print(c.x, c.y) # 1, 2

# Nuevo atributo
c.z = 3
print(c.z) # 3

Los métodos de nuestra clase, tendrán siempre un primer argumento self, que representa la
instancia de la propia clase con la que es llamado el método (el equivalente al this en otros
lenguajes orientados a objetos). Esto es una convención en Python.

52
# Podemos llamar a los métodos de la clase
class MiClase:
  x = 1
  y = 2

  def saludar(self):
  print("Hola")

c = MiClase()
c.saludar()

print(c.x, c.y)

# También podemos asignar el método a una variable y llamarlo después. Ojo: usamos el nombre del método sin paréntesis
saludo = c.saludar

# Ahora lo llamamos
saludo()

5.1.1. Creando constructores

Podemos controlar lo que pasa en la instanciación de nuestra clase mediante el método init, que
hace el papel de constructor de nuestra clase

# Implementamos un constructor para nuestra clase, y dentro del constructor asignamos variables.
class MiClase:
  def __init__(self, x, y):

  # self.x y self.y son propias de la instancia, no compartidas


  self.x = x
  self.y = y

c = MiClase(7, 12)

print(c.x, c.y)

Otro método interesante es str, que devuelve una representación textual de la clase

# Implementamos un método __unicode__ que será llamado cuando pasemos una instancia de la clase como argumento de print()
class MiClase:
  def __init__(self, x, y):

  # self.x y self.y son propias de la instancia, no compartidas


  self.x = x
  self.y = y

  def __str__(self):
  return "x = {0}, y={1}".format(self.x, self.y)

c = MiClase(7, 12)
print(c) # x=7, y=12

53
5.1.2. Herencia en Python

Python permite herencia múltiple, de manera que una clase puede heredar de una o más clases
padre

# Creamos una clase padre y una clase hija, que heredará sus métodos y atributos
class ClasePadre:
  def __init__(self, x, y):
  self.x = x
  self.y = y

  def __str__(self):
  return "x = {0}, y={1}".format(self.x, self.y)

class OtraClasePadre:
  def __init__(self, x, y):
  self.a = x
  self.b = y

  def foo(self):
  return "foo"

# Entre paréntesis van las clases padres, separadas por comas


class ClaseHija(ClasePadre, OtraClasePadre):
  def __init__(self, x, y, z):

  # Podemos llamar al constructor del padre mediante super.


  # En caso de ambigüedad, como ahora, se llama al primero
  # de la lista (ClasePadre.__init__)
  super().__init__(x, y)

  # Y asignar nuestras propias variables


  self.z = z

  def __str__(self):
  # Podemos llamar a cualquier método de nuestra clase padre, igual que llamamos a __init__
  return "{0}, z={1}".format(super().__str__(), self.z)

padre = ClasePadre(3, 4)
hija = ClaseHija(5, 6, 7)

print(padre) # x = 3, y=4
print(hija) # x = 5, y=6, z=7
print(hija.foo())

5.1.3. Overriding (Sobrecarga de metodos)

La sobrecarga de métodos es también es conocida por Overriding Methods, nos permite sustituir un
método proveniente de la Clase padre, en la Clase hijo debe definir un método con el mismo
nombre de método y mismo número de parámetros que como está definido en la Clase Padre.

Vamos a definir una clase Humano, con un metodo mensaje que nos muestre el mensaje "Es un

54
humano'' con nombre, planeta de origen y edad. Después vamos a definir una clase "Jedi'' que
herede los metodos de la clase padre pero que el metodo mensaje lo cambie por la "El humano es
un''Jedi".

class Humano:
  def __init__(self, nombre, planeta, edad):
  self.nombre = nombre
  self.planeta = planeta
  self.edad = edad
  def mensaje(self):
  print(f' Nombre: {self.nombre}, Origen: {self.planeta}, Edad: {self.edad}, Especie: Humano')
class Jedi(Humano):
  def __init__(self, nombre, planeta, edad):
  super().__init__(nombre, planeta, edad)
  def mensaje(self):
  print(f' Nombre: {self.nombre}, Origen: {self.planeta}, Edad: {self.edad}, Especie: Humano Jedi')

#Si lo probamos

Humano1 = Humano('Han Solo', 'Corellia', 32)


Humano1.mensaje()
Humano2 = Jedi('Luke Skywalker', 'Tatooine', 25)
Humano2.mensaje()

Nombre: Han Solo, Origen: Corellia, Edad: 32, Especie: Humano


Nombre: Luke Skywalker, Origen: Tatooine, Edad: 25, Especie: Humano Jedi

5.1.4. Clases Abstractas

Las clases abstractas, como su nombre lo indica, son algo abstracto, no representan algo específico
y las podemos usar para crear otras clases. No pueden ser instanciadas, por lo que no podemos
crear nuevos objetos con ellas.

Podemos imaginar una clase Animal, con métodos como caminar y comer, como una clase base que
podemos heredar para construir otras clases como León o Pájaros. Ambas van a heredar de nuestra
clase animal con sus respectivos métodos y tendremos la posibilidad de crear nuestros objetos. De
esta manera podemos reducir código duplicado y mejorar la calidad del código.

También podemos hacer lo mismo con los métodos: si una clase tiene métodos abstractos, entonces
nuestra clase deberá ser abstracta. Los métodos abstractos son aquellos que solamente tienen una
declaración, pero no una implementación detallada de las funcionalidades.

Nuestro método abstracto será compartido por las clases que hereden de nuestra clase abstracta.
En el ejemplo, un animal puede comportarse de manera similar y realizar las mismas acciones,
como caminar, comer y dormir, pero el sonido emitido no será igual en todos ellos, no escucharás a
un pájaro rugir como un león.

Para poder crear clases abstractas en Python es necesario importar la clase ABC y el decorador
abstractmethod del módulo abc (Abstract Base Classes). Un módulo que se encuentra en la librería
estándar del lenguaje, por lo que no es necesario instalar. Así para definir una clase privada
solamente se tiene que crear una clase heredada de ABC con un método abstracto.

55
from abc import ABC, abstractmethod

class Animal(ABC):
  @abstractmethod
  def mover(self):
  pass
  @abstractmethod
  def comer(self):
  print('Animal come')

Las subclases tienen que implementar todos los métodos abstractos, en el caso de que falta alguno
de ellos Python no permitirá instancias tampoco la clase hija.

class Animal(ABC):
  @abstractmethod
  def mover(self):
  pass
  @abstractmethod
  def comer(self):
  print('Animal come')
class Gato(Animal):
  def mover(self):
  print('El gato se mueve')

g = Gato()

56
class Animal(ABC):
  @abstractmethod
  def mover(self):
  pass
  @abstractmethod
  def comer(self):
  print('Animal come')

class Gato(Animal):
  def mover(self):
  print('El gato se mueve')
  def comer(self):
  super().comer()
  print('El gato come')

g = Gato()
g.mover()
g.comer()

El gato se mueve
Animal come
El gato come

5.1.5. ¿Es Python realmente orientado a objetos?

Por ahora hemos visto algunas pinceladas de orientación a objetos en Python. Para los
acostumbrados a Java o C++ puede resultar una sintáxis un poco forzada. Que se pueden hacer las
cosas, pero no parece que el lenguaje esté pensado para ello.

De hecho, seguramente te hayan surgido preguntas típicas de orientación a objetos:

• ¿Cómo declaro métodos públicos y privados?

• ¿Cómo hago clases y/o métodos virtuales?

• ¿Puedo sobreescribir métodos?

Son preguntas lógicas. Y si te surgen, es porque Python no se considera un lenguaje orientado a


objetos puro, ya que no te impone el paradigma, solo te lo permite usar.

En Python es posible obtener la encapsulación y la separación de capas


 de nuestra aplicación, dos de las cualidades básicas de la orientación
a objetos, mediante el uso de paquetes y módulos.

Como reflexión totalmente personal, la orientación a objetos es un paradigma muy útil cuando
nuestra aplicación ha de modelar objetos con una funcionalidad asociada a un estado y que han
de ser perdurables mientras nuestra aplicación esté funcionando. Ejemplos clásicos serían el
desarrollo de aplicaciones de escritorio, o videojuegos, donde deberemos modelar botones,

57
ventanas, personajes, etc. Estos objetos estarán presentes en memoria, y seguramente pasen por
diferentes estados, mientras nuestra aplicación esté corriendo.

Si no estamos en una de esas situaciones, tras vez un paradigma stateless, como el de la


programación funcional, pueda resultarnos más útil. Especialmente si tenemos que afrontar
problemas de concurrencia. Veremos este paradigma más adelante.

Si queremos desarrollar nuestra aplicación mediante el paradigma de orientación a objetos, puede


sernos de ayuda el uso de una sintáxis de Python llamada decorators, que veremos a continuación

5.1.6. Python y la encapsulación de datos

Una de las primeras ideas que nos viene a la cabeza cuando comenzamos a diseñar nuestra
aplicación en Python usando el paradigma de orientación a objetos es: ¿cómo declaro miembros
privados en mis clases?

La respuesta a esta pregunta es… no lo haces. Y no lo haces porque eso solo serviría para complicar
las cosas.

La filosofía tras el diseño de Python es, en palabras de su creador, Guido Van Rosuum:

Python is an open kimono language.

— Guido Van Rossum

Es decir, que por defecto todo lo que haya en tu código, es público. Y es público por sencillez.

La justificación tras esa filosofía es que la única razón para implementar miembros privados en
una clase, es encapsular el funcionamiento de la misma. Separar la implementación de la interfaz.
O en otras palabras: que los usuarios de la clase no tengan que preocuparse de su funcionamiento
para poder usarla. Y si la clase está bien diseñada, y su interfaz es clara e intuitiva, no habría
porqué preocuparse de cómo funciona.

O en otras palabras: La encapsulación no consiste en prohibir el acceso a ciertas partes de tu


clase, sino en que no sea necesario acceder a las mismas para poder usarla

Además, Guido apoya su filosofía con dos argumentos más:

• Acceder a miembros internos de una clase en lenguajes como Java o C++ sigue siendo posible
mediante introspección. Así que, ¿para qué inventar un método que nos permita acceder a los
miembros privados de una clase, pudiendo simplemente dejarlos públicos? Recordemos que
uno de los mantras de Python es Simple is better than complex

• Dejar que todas las internalidades de una clase sean accesible facilita mucho la depuración del
código (no hay que hacer nada para mostrar atributos privados en una sesión de depuracón,
simplemente imprimirlos)

A pesar de este choque entre Python y los lenguajes orientados a objetos clásicos, tenemos a
nuestra disposición algunas facilidades que nos permiten programar de manera más parecida a
como se acostumbra en los lenguajes que siguen fielmente este paradigma, como Java o C++.

58
Mediante el uso de @property y setter puedes simular el patrón get/set para
 acceso a los atributos de una clase,pero de forma más elegante (o como mínimo,
menos verbosa)

class Person:
  def __init__(self, name):
  # En esta asignación, realmente estamos llamando al setter de más abajo
  self.name = name

  @property ①
  def name(self):
  return self._name

  @name.setter ②
  def name(self, value):
  self._name = value

  # Mediante el decorator @name.deleter, permitiremos eliminar una propiedad de nuestra persona


  @name.deleter
  def name(self):
  del self._name

p = Person('Paco')
print(p.name)

p.name = 'Jorge'
print(p.name)

del p.name
print(p.name) # Esto provoca una excepcion, porque el campo ya no existe

① El decorador @property hace que se pueda llamar al método sin necesidad de ponerle
paréntesis. Es decir, poder llamar a p.name, y que se llame a ese método

② El decorador @name.setter hace que podamos asignar un valor a p.name. Por supuesto,
también lo podíamos hacer antes de implementar el método, pero con este decorator,
estaríamos ocultando el acceso a la variable nombre en sí. Además, podemos hacer
comprobaciones previas a la asignación

Mejoremos nuestro ejemplo, para ver la utilidad de @property y @name.setter

59
# Aquí haremos algunas validaciones dentro de nuestros métodos get/set
class Person:
  def __init__(self, name, edad=18):
  # Aquí estamos llamando a los setters
  self.name = name
  self.edad = edad

  @property
  def name(self):
  return "{} es una bestia sexy".format(self._name) if self._name == "Jorge" else self._name

  @name.setter
  def name(self, value):
  self._name = value

  @property
  def edad(self):
  return self._edad

  @edad.setter
  def edad(self, value):
  if value > 17:
  self._edad = value
  else:
  raise ValueError("No puedes entrar a la discoteca, eres menor de edad")

p = Person('Paco')
print(p.name)

p.name = 'Jorge'
print(p.name)

p.edad = 15

Hemos conseguido la encapsulación de nuestros datos, pero con una sintáxis más elegante que la
clásica get/set

Además de estos conceptos, conviene mencionar también el caso especial de init y str. Cuando
precedemos el nombre de un miembro de una clase con un doble subrayado, __, Python
automáticamente añade en tiempo de ejecución el nombre de la clase delante del nombre del
miembro. Este comportamiento se llama name mangling.

Su uso fundamental es evitar que subclases sobreescriban ciertos métodos considerados


importantes dentro de una clase de Python. Métodos como init o str comienzan por __ para
asegurarse de que el método implementado por las clases hijas, será el suyo propio, y no
sobreescribirá al del padre.

Se ve con más claridad en este ejemplo

60
# Python añadirá internamente el nombre de la clase delante de __baz
class Foo(object):
  def __init__(self):
  self.__baz = 42

  def foo(self):
  print(self.__baz)

# Como el método __init__ empieza por __, en realidad será Bar__init__, y el del padre Foo__init__. Así existen por
separado
class Bar(Foo):
  def __init__(self):
  super().__init__()
  self.__baz = 21

  def bar(self):
  print(self.__baz)

x = Bar()

x.foo() # 42, porque el método imprime Foo__baz


x.bar() # 21, porque el método imprime Bar__baz

# Podemos ver los miembros "mangleados" que tiene la instancia x


print(x.__dict__) # {'_Bar__baz': 21, '_Foo__baz': 42}

Otro uso que tradicionalmente se ha dado al __ es, precisamente, simular la existencia de métodos
privados en clases. Es posible, pero no demasiado útil. Lo vemos a continuación.

# El método private parece privado, pero vemos que realmente no lo es...


class MiClase:
  def public(self):
  print("Hola, soy un método público")

  def __private(self):
  print("Soy un método privado y no me puedes llamar, mwahahahaha")

c = MiClase()
c.public()

# Esta llamada nos dará un AttributeError, diciendo que no existe el método 'private'...
#c.__private()

# Pero así funciona...


#
c._MiClase__private()

5.1.7. Métodos de clase vs funciones estáticas

En Python es posible crear métodos de clase (es decir, métodos que pueden ser llamados
directamente sin instanciar la clase). Para ello, se usa el decorador @classmethod. Simplemente,
tengamos en cuenta que estos métodos no reciben self como parámetro, sino que reciben un objeto
representando la clase en si

61
# Definimos un método de clase, que podemos llamar tanto desde la clase en si como desde una instancia de la misma
class MiClase:
  def f(self):
  print("Esto es un método de instancia")

  # Método de clase: no recibe la instancia self como parámetro


  @classmethod
  def classf(*_):
  print(*_)
  print("Esto es un método de clase")

c = MiClase()
c.f()

# Podemos llamar al método desde la clase o desde la instancia


c.classf(4,5,6,7,8,9)
MiClase.classf()

# No podríamos hacer esto


# MiClase.f()

Un caso de uso claro para @classmethod es definir constructores alternativos para una clase.

# MiClase tendrá un constructor alternativo, que usará una lista en lugar de una cadena
class Persona:
  def __init__(self, nombre=''):
  self.nombre = nombre

  @classmethod
  def fromList(cls, l):

  # Instanciamos un nuevo objeto de la clase


  x = cls()

  # Lo rellenamos y lo devolvemos
  x.nombre = ' '.join([l[0], l[1]])
  return x

  def __str__(self):
  return self.nombre

p = Persona("Pepito Perez")
p2 = Persona.fromList(["Jorge", "Blanco"])

print(p) # Pepito Perez


print(p2) # Jorge Blanco

62
No confundamos @classmethod con @staticmethod. El segundo decorador
simplemente sirve para declarar una función dentro de la clase. No tiene mucha
 más utilidad que definir funciones útiles para la clase pero que no necesitan
acceder a ninguno de sus miembros.

# Ejemplo de uso de función estática, no recibe ningún argumento, pero hace alguna operación útil dentro de la clase
class Math:
  @staticmethod
  def es_par(n):
  return not n % 2

m = Math()

# La puedo llamar desde la clase o desde una instancia


print(m.es_par(2))
print(Math.es_par(3))

5.2. Clases para controlar errores: excepciones


Python posee un tipo de clases especiales, que heredan de la clase BaseException, y que sirven
para enviar mensajes de error cuando nuestro programa encuentra algún problema.

Lanzamos excepciones mediante la palabra reservada raise, y las capturamos mediante bloques try
… except

# Ejemplo de lanzamiento y captura de excepción


class Triangulo:
  def __init__(self, base, altura):
  if base <= 0:
  raise ValueError("La base del triángulo tiene que medir más de 0")

  if altura <= 0:
  raise ValueError("La altura del triángulo tiene que medir más que 0")

t = Triangulo(2, 3)

# Capturamos la excepción
try:
  t = Triangulo(-2, 4)
except ValueError as e:
  print(e)

# Esto se ejecuta siempre, tanto si salta la excepción como si no. No es obligatorio ponerlo
finally:
  print("Programa terminado")

63
5.3. Conclusiones
La orientación a objetos en Python tiene más complejidad, pero con lo visto hasta ahora, es
suficiente para comparar Python con los lenguajes orientados a objetos clásicos, y ver los puntos de
fricción. En el próximo capítulo veremos otros aspectos propios de Python que dan mucho poder al
lenguaje, y tal vez convenzan a los acostumbrados a los lenguajes orientados a objetos de que hay
otras maneras de desarrollar en Python que pueden ser tanto o más interesantes, dependiendo del
problema a resolver

¿Qué conclusión podemos sacar de la orientación a objetos en Python? Que Python


 no es Java, ni pretende serlo

5.4. USando el metodo "Slots"


En Python cualquier clase que creemos tiene atributos de instancia. Los atributos de un
determinado objeto, por defecto, se almacenan en un diccionario. Lo cual es bastante útil ya que
nos permite, por ejemplo, la creacion de nuevos atributos en tiempo de ejecución.

Pero ya sabemos que no todo puede ser perfecto constantemente, si estamos manejando clases
pequeñas y con atributos conocidos lo anterior puede llegar a generar un cuello de botella. El uso
de elementos de python del tipo dict (diccionarios) desperdicia una gran cantidad de memoria RAM
y Python no puede asignar una cantidad de memoria estática para almacenar los atributos. Si
creamos muchos objetos, en el orden de miles o millones, estaremós desperdiciando RAM de forma
inútil.

Para solucionar lo anterior es cuando entran en juego "Slots". Este método nos permite decirle a
Python que use un diccionario y que solo asigne memoraria para una cantidad fija de atributos, los
que conocemos.

Antes de usar slots definiriamos una clase de esta manera:

class MiClase(object):
  def __init__(self, nombre, identificador):
  self.nombre =nombre
  self.identificador = identificador
...

Si usaramos Slots:

class MiClase(object):
  __slots__ = ['nombre', 'identificador']
  def __init__(self, nombre, identificador):
  self.nombre = nombre
  self.identificador = identificador
...

64
¿Que ganamos con el uso de slots?. Usando este metodo reducimos drasticámente el uso de RAM. En
algunos casos podemos llegar hasta un 50%. Es importante tener claro que este metodo hay que
usarlo siempre al empezar a definir la clase.

65
5.5. Lab: Programación Orientada a Objetos
A continuación repasaremos los conceptos aprendidos con unos ejercicios

5.5.1. Laboratorio 01

Escribir una clase en Python llamada 'Rectangle' con propiedades length y width y métodos para
calcular el área y el perímetro. Debemos poder asignar ancho y alto, comprobando que son
números mayores de 0.

Solución

class Rectangle:
  def __init__(self, l, w):
  # Aqui estamos llamando a los setters (ver abajo)
  self.length = l
  self.width = w

  @property
  def area(self):
  return self._length*self._width

  @property
  def perimeter(self):
  return self._length * 2 + self._width * 2

  @property
  def length(self):
  return self._length

  @property
  def width(self):
  return self._width

  @length.setter
  def length(self, value):
  if value < 0:
  raise ValueError("El valor de length ha de ser mayor que 0")
  else:
  self._length = value

  @width.setter
  def width(self, value):
  if value < 0:
  raise ValueError("El valor de width ha de ser mayor que 0")
  else:
  self._width = value

# Esto implica que no ha de ser posible crear objeto con dimensiones negativas.
try:

66
  newRectangle = Rectangle(-12, 10) ①
except ValueError as e:
  print(e)

newRectangle = Rectangle(12, 10)


print(newRectangle.area)
print(newRectangle.perimeter)

newRectangle.width = 5
newRectangle.length = 8
print(newRectangle.area)
print(newRectangle.perimeter)

newRectangle.width = -10

5.5.2. Laboratorio 02

Escribir una clase 'Circulo' con una propiedad radius y métodos para calcular el área y el
perímetro. También se ha de poder comprobar que el valor de radius es positivo, al asignarlo

Solución

67
import math

class Circle:
  def __init__(self, r):
  self.radius = r

  @property
  def area(self):
  return self._radius**2*math.pi

  @property
  def perimeter(self):
  return 2*self._radius*math.pi

  @property
  def radius(self):
  return self._radius

  @radius.setter
  def radius(self, value):
  if value < 0:
  print("El valor de radius ha de ser mayor que 0")
  else:
  self._radius = value

NewCircle = Circle(8)
print(NewCircle.area) # 201.06192982974676
print(NewCircle.perimeter) # 50.26548245743669

NewCircle.radius = 5
print(NewCircle.area) # 78.53981633974483
print(NewCircle.perimeter) # 31.41592653589793

NewCircle.radius = -12 # El valor de radius ha de ser mayor que 0

5.5.3. Laboratorio 03

Escribir una clase Shape con métodos abstractos para calcular el área y el perímetro. Las dos clases
de los ejercicios anteriores podrán heredar entonces de esta clase padre

Una técnica habitual para implementar métodos abstractos en clases es limitarse a


 lanzar una excepción de tipo NotImplementedError en el método de la clase
padre

Solución

class Shape:
  @property

68
  def area():
  raise NotImplementedError("Por favor, implementa este método en una clase hija")

  @property
  def perimeter():
  raise NotImplementedError("Por favor, implementa este método en una clase hija")

class Rectangle(Shape):
  def __init__(self, l, w):
  self.length = l
  self.width = w

  @property
  def area(self):
  return self._length*self._width

  @property
  def perimeter(self):
  return self._length * 2 + self._width * 2

  @property
  def length(self):
  return self._length

  @property
  def width(self):
  return self._width

  @length.setter
  def length(self, value):
  if value < 0:
  print("El valor de length ha de ser mayor que 0")
  else:
  self._length = value

  @width.setter
  def width(self, value):
  if value < 0:
  print("El valor de width ha de ser mayor que 0")
  else:
  self._width = value

newRectangle = Rectangle(12, 10)


print(newRectangle.area) # 120
print(newRectangle.perimeter) # 44

newRectangle.width = 5
newRectangle.height = 8
print(newRectangle.area) # 60
print(newRectangle.perimeter) # 34

69
newRectangle.width = -10 # El valor de width ha de ser mayor que 0

5.5.4. Laboratorio 04

Escribir un programa que itere sobre una lista conteniendo datos de personas en forma de tuplas:
username, email, age y añada al usuario en un almacen de datos si al menos tiene 18 años.

Deberemos crear una clase para almacenar los datos de los usuarios, y también deberemos crear
clases de excepciones para controlar los siguientes casos de error:

• El nombre del usuario está repetido

• La edad es un número negativo (podemos asumir que va a ser un número entero)

• El usuario es menor de 18 años

• La dirección de email no es válida (basta con comprobar que está el símbolo @ y un nombre de
dominio)

El programa deberá detectar estos casos de error y lanzar excepciones donde corresponda. Además,
las excepciones han de ser capturadas y se debe mostrar un mensaje diferente por cada una de
ellas.

Solución

# Excepciones. Definelas aqui


class DuplicatedUsernameError(Exception):
  pass

class InvalidAgeError(Exception):
  pass

class UnderageError(Exception):
  pass

class InvalidEmailError(Exception):
  pass

# Clase para almacenar los datos de los usuarios: nombre, edad y email

class User:
  def __init__(self, username, age, email):
  self.username = username
  self.age = age
  self.email = email

  @property
  def age(self):
  return self._age

  @age.setter
  def age(self, a):
  if int(a) < 0:
  raise InvalidAgeError("La edad no puede ser menor que cero")

70
  elif int(a) < 18:
  raise UnderageError("El usario es menor de 18 años")
  else:
  self._age = int(a)

  @property
  def email(self):
  return self._email

  @email.setter
  def email(self, e):
  if '@' not in e:
  raise InvalidEmailError("El email no tiene un formato correcto")
  else:
  self._email = e

  def __str__(self):
  return "{} is {} years old and has the email {}".format(self.username, self.age, self.email)

example_list = [
  ("jane", "[email protected]", 21),
  ("bob", "bob@example", 19),
  ("jane", "[email protected]", 25),
  ("steve", "steve@somewhere", 15),
  ("joe", "joe", 23),
  ("anna", "[email protected]", -3),
]

directory = {}

for username, email, age in example_list:


  try:
  if username in directory.keys():
  raise DuplicatedUsernameError("El usuario {} ya existe".format(username))

  directory[username] = User(username, age, email)


  except Exception as e:
  print(e)

for username, u in directory.items():


  print("{} => {}".format(username, u))

5.5.5. Laboratorio 05

Construye una clase llamada 'Numbers'. que cumpla los siguientes requisitos

• Ha de tener un atributo de clase llamado 'MULTIPLIER', con valor 3.5

• Ha de tener un constructor, que reciba dos parámetros, 'x' e 'y', y los almacene por separado

• Ha de tener un método 'add', que devuelva la suma de los atributos 'x' + 'y'

• Ha de tener un método de clase 'multiply', que devuelva el producto de 'MULTIPLIER' por el


argumento que se le pase

• Ha de tener un método estático 'substract', que reciba dos parámetros 'a', 'b', y devuelva 'a' - 'b'

71
• Ha de tener un método 'value' que devuelva una tupla formada por 'x', 'y'. Convertir dicho
método en una propiedad, y escribirle un setter y un deleter.

Solución

class Numbers:
  MULTIPLIER = 3.5

  def __init__(self, x, y):


  self.x = x
  self.y = y

  def add(self):
  return self.x + self.y

  @classmethod
  def multiply(cls, a):
  return cls.MULTIPLIER * a

  @staticmethod
  def substract(b, c):
  return b - c

  @property
  def value(self):
  return (self.x, self.y)

  @value.setter
  def value(self, xy_tuple):
  self.x, self.y = xy_tuple

  @value.deleter
  def value(self):
  del self.x
  del self.y

# Pruebas
assert(Numbers.MULTIPLIER == 3.5)

n = Numbers(4, 4)
assert(n.add() == 8)
assert(Numbers.multiply(2) == 7.0)
assert(Numbers.substract(5, 3) == 2)
assert(n.value == (4, 4))
n.value = (8, 6)
assert(n.value == (8, 6))
del n.value

72
try:
  print(n.x)
except Exception as e:
  print(e)

try:
  print(n.y)
except Exception as e:
  print(e)

5.5.6. Laboratorio 06

Vamos a simular la existencia de clases abstractas.

Escribir una clase 'Box' y usarla para definir ciertos métodos que una caja debería tener:

• 'add': para añadir items a la caja. Recibe una cantidad indeterminada de items, pero por
separado, no en forma de lista.

• 'empty': para sacar todos los items de la caja y devolverlos como una lista

• 'count': para contar los elementos que hay en la caja

Estos métodos, deberán ser implementados por las subclases de 'Box':

• 'ListBox': Usa una lista para guardar los items

• 'DictBox': Usa un diccionario para guardar los items

De manera que los métodos de la clase padre deberían informar de que no son métodos destinados
a ser llamados, sino a ser sobreescritos por sus hijos. ¿Cómo lo conseguiríamos?

PISTA: Seguramente haya una excepción que los métodos de la clase base puedan lanzar en un caso
como éste

Escribir también una clase 'Item', genérica, que simplemente contenga como atributos un nombre y
un valor. Estos son los items que se guardarán en la caja

Solución

class Item:
  def __init__(self, name, value):
  self.name = name
  self.value = value

  def __str__(self):
  return "{} => {}".format(self.name, self.value)

class Box:
  def add(self, *items):
  raise NotImplementedError()

73
  def empty(self):
  raise NotImplementedError()

  def count(self):
  raise NotImplementedError()

  def items(self):
  raise NotImplementedError()

class ListBox(Box):
  def __init__(self):
  self._items = []

  def add(self, *items):


  self._items.extend(items)

  def empty(self):
  items = self._items
  self._items = []
  return items

  def count(self):
  return len(self._items)

  def items(self):
  return self._items

class DictBox(Box):
  def __init__(self):
  self._items = {}

  def add(self, *items):


  self._items.update(dict((i.name, i) for i in items))

  def empty(self):
  items = list(self._items.values())
  self._items = {}
  return items

  def count(self):
  return len(self._items)

  def items(self):
  return self._items

# Esto prueba las clases


i1 = Item("Item 1", 1)

74
i2 = Item("Item 2", 2)
i3 = Item("Item 3", 3)

listbox = ListBox()
dictbox = DictBox()

listbox.add(i1, i2, i3)


dictbox.add(i1)
dictbox.add(i2)
dictbox.add(i3)

assert(listbox.count() == 3)
assert(dictbox.count() == 3)

for i in listbox.items():
  print(i)

for k, item in dictbox.items().items():


  print(i)

listbox.empty()
dictbox.empty()

assert(listbox.count() == 0)
assert(dictbox.count() == 0)

for i in listbox.items():
  print(i)

for k, item in dictbox.items().items():


  print(i)

75
Capítulo 6. Excepciones con Python
Es interesante conocer la creación y manejo de excepciones en Python, nos ayudar a realizar una
programación defensiva adelantandos a los posibles errores que pueden sucedernos durante la
ejecución de nuestras aplicaciones. Es decir, podemos controlar los errores.

Aunque una expresión o declaración sea sintactimente correcta, puede generarnos un error cuando
la intentemos ejecutar. Este tipo de errores es lo que denominaremos una excepción. En este
capítulo del temario vamos a aprender a gestionarlos en nuestros programas de Python.

6.1. Gestionando excepciones


Podemos escribir programas que se ocupen de gestionar las excepciones, esto lo conseguiremos
mediante el uso de bloques try/except como el del siguiente ejemplo.

while True:
  try:
  x = int((input("Introduce un número: ")))
  break
  except ValueError:
  print("Eyyyy!! El número introducido no es válido")

El ejemplo anterior pide al usuario que introduzca un número, pero vamos a ver que pasa en su
interior. La sentencia try funciona de la siguiente forma:

• Se ejecuta la cláusula try. Es decir se ejecutan todas las líneas de código que hay entre try y
except.

• Si no se ha producido ninguna excepción se omite el la cláusula except y finaliza la ejecución de


try.

• Si durante la ejecución de try se produce una excepción se omitirá el resto de esta cláusula. * Si
la excepción coincide con la que hemos nombrado después de la palabra reservada except, se
ejecutará esta cláusula y la ejecución continua después del try/except.

• Si ocurre una excepción que no coincide con la indicada en except se pasa a los try mas
externos, si no se encuentra el gestor se genera una excepción no gestionada (unhandled
exception) y la ejecución se interrumpe con un mensaje como el mostrado en el print del
ejemplo.

Tenemos que tener en cuenta que una declaración try puede tener más de una cláusula except, de
esta manera podemos especificar gestores para varias excepciones. Pero tenemos que saber que:

• Como máximo se ejecutará un gestor

• Los gestores solo manejan las excepciones que ocurren en la cláusula try correspondiente, no
en otros gestores de la misma declaración try.

• Una clausula except puede nombrar múltiples excepciones como una tupla

76
except (RuntimeError, TypeError, NameError):

• Todas las excepciones heredan de la clase BaseException:

◦ Podemos usarla como comodín

◦ Hay que se cuidadosos, ya que podemos ocultar un error de programación de esta manera.

◦ Se puede usar para imprimir un mensaje de error y luego volver a generar la excepción. De
esta manera permitimos que la función que llama tambiñen maneje la excepción.

import sys

try:
  f = open('myfile.txt')
  s = f.readline()
  i = int(s.strip())
except OSError as err:
  print(f"OS error: {err}")
except ValueError:
  print("No se puede convertir el dato en un entero")
except BaseException as err:
  print(f"Unexpected {err=}, {type(err)=}")
  raise

Una declaración try/except tiene una cláusula else opcional, que, cuando está incluida debe seguir
todas las cláusulas except. Esto es útil para el código que debe ejecutarse si la cláusula try no lanza
una excepción.

• El uso de else es mejor que el añadir mas código a la cláusula try ya que evita capturar
accidentalmente una excepción que no fue generada por el código protegido por la declaración
try/except

• Cuando sucede una exepción puede darse el caso de que tenga un valor asociado, este valor es
conocido como el argumento de la excepción. La presencia y el tipo de este argumento
dependerá del tipo de excepción.

Una cláusula except puede especificar una variable después del nombre de la excepción:

• La variable está vinculada a una instancia de excepción con los argumentos almacenados en
instance.args.

• Por convenio la instancia de excepción define _str_() para que los argumentos se puedan
imprimir directamente sin tener que hacer referencia a .args.

• Podemos crear una instancia de una excepción antes de generarla y agregarle los atributos.

77
try:
  raise Exception('spam', 'eggs') # Estamos forzando el lanzar una excepción
except Exception as inst:
  print(type(inst)) #Instanciamos la excepción
  print(inst.args) #Imprimimos los argumentos almacenados en .args
  print(inst) #Estamos usando __str__ para imprimir los resultados directamente

  x, y = inst.args
  print("x =", x)
  print("y =", y)

<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

Los gestores de excepciones no solo gestionan las excepciones si ocurren en la cláusula try ,
también las gestionan si ocurren dentro de funciones que son llamadas (aunque se indirectamente)
en las cláusulas try.

def esto_falla():
  x = 1/0

try:
  esto_falla()
except ZeroDivisionError as err:
  print("Manejando error en ejecución:", err)

Manejando error en ejecución: division by zero

6.2. Lanzando excepciones


Aunque ya lo hemos hecho en algún ejemplo anterior vamos a ver con mas detenimiento el uso de
la declaración raise. Esta declaración nos permite forzar a que ocurra una excepción específica.

• El único argumento de raise indica la excepción a generarse

• Este argumento debe ser una instancia de excepción o una clase de excepción (que hereda de
Exception)

• Si se le pasa un clase de Excepción, esta será instanciada implicitamente llamando a su


constructor sin argumentos.

78
raise NameError('HiThere')

Traceback (most recent call last):


  File "<stdin>", line 1, in <module>
NameError: HiThere

• Si fuera necesario determinar si una excepción fue lanzada pero sin intención de gestionarla,
podemos usar una versión simplificada de la instrucción raise para relanzarla

try:
  raise NameError('Que tal')
except NameError:
  print('Aquí tenemos una excepción')
  raise

Aquí tenemos una excepción


Traceback (most recent call last):
  File "/home/spark/Documents/try_except05.py", line 2, in <module>
  raise NameError('Que tal')
NameError: Que tal

6.3. Encadenando excepciones


La declaración raise nos permite hacer uso de la clave opcional from que nos permite habilitar
excepciones. Esto es especialmente útil cuando estamos transformando excepciones.

79
def func():
  raise ConnectionError

try:
  func()
except ConnectionError as exc:
  raise RuntimeError ("Failed to open") from exc

Traceback (most recent call last):


  File "/home/spark/Documents/try_except06.py", line 5, in <module>
  func()
  File "/home/spark/Documents/try_except06.py", line 2, in func
  raise ConnectionError
ConnectionError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):


  File "/home/spark/Documents/try_except06.py", line 7, in <module>
  raise RuntimeError ("Failed to open") from exc
RuntimeError: Failed to open

Si estamos dentro de una sección except o finally. el encadenamiento de excepciones ocurre


automáticamente. Para desactivarlo usaremos from None

try:
  open('database.sqlite')
except OSError:
  raise RuntimeError from None

6.4. Built-in exceptions


Ahora que ya sabemos como manejar las excepciones vamos a conocer la expeciones definidas en
Python

6.4.1. Clases Padre

Son las que utilizamos como clases padre para otras excepciones.

exception BaseException
La hemos usado anteriormente, es la clase padre para todas las excepciones predefinidas.

• No hay que confundirla con Exception, esta clase no está pensada para que la hereden las
clases definidas por el usuario.

• Si se llama al método _str()_ en una instancia de esta clase nos devuelve la representación
de los argumentos de la instancia o la cadena vacia cuando no habia argumentos.

80
• args: Es la tupla de argumentos dada al constructor de excepción.

◦ Algunas excepciones predefinidas esperan cierto número de argumentos y asignan un


significado especial a los elementos de esta tupla.

◦ Otras excepciones se llaman solo con una sola cadena que da un mensaje de error.

• with_traceback(tb): Este método establece tb como el nuevo rastreo para la excepción y


retorna el objeto de excepción.

exception Exception
• Todas las excepciones predefinidas quie no salen del sistema heredan de esta clase.

• Todas las excepciones definidas por el usuario también deben heredar de esta clase

exception ArithmeticError
• Esta es la clase padre para las excepciones predefinidas que se generan para varios errores
aritméticos (OverflowError,ZeroDivisionError,FloatingPointError)

exception BufferError
• Se genera cuando buffer no puede realizar una operación relacionada

exception LookupError
• Clase padre para las excepciones que se generan cuando una clave o índice utilizado en un
mapa o secuencia que no es válido (` IndexError`,KeyError).

• Se puede lanzar directamente por codecs.lookup()

6.4.2. Excepciones especificas.

Estas excepciones son las que normalmente se generan

exception AssertionError

• Se genera cuando se produce un error en un assert

exception AttributeError

• Se genera cuando se produce un error en una referencia de atributo o falla la asignación

exception EOFError

• Se genera cuando la función input() alcanza una condición de fin de archivo sin leer ningún
dato.

exception GeneratorExit

• Se genera cuando un generador o coroutine está cerrado.

• Hereda directamente de BaseException en lugar de Exception ya que tecnicamente no es un


error

exception ImportError

81
• Se genera cuando una instrucción import tiene problemas al intentar cargar un módulo.

• También se genera si en la importación de una función del módulo no la encuentra.

exception IndexError

• Se genera cuando un subíndice está fuera de rango

exception KeyError

• Se genera cuando no se encuentra una clave de asignación en un conjunto de claves existentes.

exception NameError

• Se genera cuando no se encuentra un nombre local o global.

exception OSError([arg])

• Esta excepción se produce cuando una función del sistema retorna un error relacionado con el
sistema, que incluye fallas de E/S como file not found o disk full (no para tipos de argumentos
ilegales u otros errores incidentales).

Por supuesto hay muchas mas excepciones, puedes consultarlas en este enlace

6.5. Excepciones definidas por el usuario


Un programa pueden nombrar sus propias excepciones creando una nueva clase de excepción,
deberan derivar de la clase Exception, de manera directa o indirecta. La mayoria de las excepciones
se definen con nombres acabados en Error, siguiendo la nomenclatura estándar. Muchos módulos
éstandar definen sus propias excepciones para reportar errores que puedan suceder en sus
funciones propias.

Lo primera que tendremos que hacer es crear nuestra propia clase que herede de la ya mencionada
clase Exception

class EstaEsMiExcepcion(Exception):
  pass

Por supuesto podemos pasarle parámetros de entrada al lanzarla. Esto nos va a permitir dar
información adicional a la excepción. Para pasar estos parametros deberemos modificar el método
_init_

class EstaEsMiExcepcion(Exception):
  def __init__(self, parametro1, parametro2):
  self.parametro1 = parametro1
  self.parametro2 = parametro2

Una vez creada nuestra excepción personalizada podemos lanzarla con raise. Además podemos
acceder a los parámetros pasados como argumentos al lanzar la excepción.

82
class EstaEsMiExcepcion(Exception):
  def __init__(self, parametro1, parametro2):
  self.parametro1 = parametro1
  self.parametro2 = parametro2

try:
  raise EstaEsMiExcepcion("Valor1", "Valor2")
except EstaEsMiExcepcion as ex:
  p1, p2 = ex.args
  print(type(ex))
  print("Primer Parametro =", p1)
  print("Segundo Parametro =", p2)

<class '__main__.EstaEsMiExcepcion'>
Primer Parametro = Valor1
Segundo Parametro = Valor2

83
6.6. Lab: Excepciones
6.6.1. Laboratorio 1

Escribe un programa que pida al usuario introducir dos números y realice la división del primero
por el segundo devolviendo el cociente como resultado. Para escribir el programa debes utilizar
try/except para que arroje una excepción si el denominador es cero o el dato introducido no es un
número valido.

def division(a,b):
  c = a/b
  return print(f"el resultado de dividir {a} entre {b} es {c}")

a = input("Introduce el primer número: ")


b = input("Introduce el segundo número: ")

try:
  division(int(a),int(b))
except (ZeroDivisionError, ValueError) as error:
  print(f"Existe un error: {error}")
finally:
  print("\nFin del programa")

6.6.2. Laboratorio 2

Modifica el código anterior para siga pidiendo datos al usuario mientras que estos no sean validos.

def division(a,b):
  c = a/b
  return print(f"el resultado de dividir {a} entre {b} es {c}")
while True:
  a = input("Introduce el primer número: ")
  b = input("Introduce el segundo número: ")

  try:
  division(int(a),int(b))
  except (ZeroDivisionError, ValueError) as error:
  print(f"Existe un error: {error}")
  print(f"Por favor vuelva a introducir los valores")
  else:
  break

84
Capítulo 7. Creación de paquetes propios
7.1. Manejando módulos
Lo primero que debemos aclarar es a que llamamos módulo y paquete en Python:

módulo → Cada uno de nuestros archivos .py

Estos módulos los podemos argrupar en paquetes, es decir una carpeta que contiene archivos .py
pero además debe contener un archivo de inicio llamado init.py Este último archivo no tiene
porque contener ninguna instrucción , puede estar completamente vacio.

Sabiendo esto podemos visualizar la estructura de un paquete de la siguiente manera:

└── paquete
  ├── __init__.py
  ├── modulo1.py
  ├── modulo2.py
  └── modulo3.py

Por supuesto dentro de un paquete podemos tener subpaquetes:

└── paquete
  ├── __init__.py
  ├── modulo1.py
  └── subpaquete
  ├── __init__.py
  ├── modulo1.py
  └── modulo2.py

Tampoco hay que olvidar el destarcar que un módulo no está obligado a pertenecer a un paquete.

7.1.1. Importando módulos enteros:

El contenido de un módulo podrá ser usado por otros módulos mediante la ya conocida instrucción
import

import modulo # importar un módulo que no pertenece a un paquete


import paquete.modulo1 # importar un módulo que está dentro de un paquete
import paquete.subpaquete.módulo1

7.1.2. Namespaces

Para acceder (desde el módulo donde se realizó la importación), a cualquier elemento del módulo
importado, se realiza mediante el namespace, seguido de un punto (.) y el nombre del elemento que

85
se desee obtener. En Python, un namespace, es el nombre que se ha indicado luego de la palabra
import, es decir la ruta (namespace) del módulo:

print módulo.CONSTANTE_1
print paquete.módulo1.CONSTANTE_1
print paquete.subpaquete.módulo1.CONSTANTE_1

7.1.3. Alias

Es posible también, abreviar los namespaces mediante un alias. Para ello, durante la importación,
se asigna la palabra clave as seguida del alias con el cuál nos referiremos en el futuro a ese
namespace importado:

import módulo as m
import paquete.módulo1 as pm
import paquete.subpaquete.módulo1 as psm

Luego, para acceder a cualquier elemento de los módulos importados, el namespace utilizado será
el alias indicado durante la importación:

print m.CONSTANTE _1
print pm.CONSTANTE _1
print psm.CONSTANTE_1

7.1.4. Importar módulos sin utilizar namespaces

En Python, es posible también, importar de un módulo solo los elementos que se desee utilizar.
Para ello se utiliza la instrucción from seguida del namespace, más la instrucción import seguida
del elemento que se desee importar:

from paquete.módulo1 import CONSTANTE_1

• En este caso, se accederá directamente al elemento, sin recurrir a su namespace.

• Es posible también, importar más de un elemento en la misma instrucción. Para ello, cada
elemento irá separado por una coma (,) y un espacio en blanco.

• si los elementos importados desde módulos diferentes tienen los mismos nombres usaremos
alias para prevenir fallos

Por convenio el orden de importación de los módulos es el siguiente:

1. Siempre al comienzo del documento en orden alfabético de paquetes y módulos.

2. Primero debemos importar los módulos de Python, después los de terceros y finalmente los
propios de la aplicación.

86
3. Entre cada bloque de imports, debemos dejar una línea en blanco.

7.1.5. Ejecuntando módulos como scripts

Los módulos, como hemos venido haciendo hasta ahora, podemos ejecutarlos como scripts

python fibo.py <arguments>

El código en el módulo será ejecutado, tal como si lo hubieses importado, pero con name con el
valor de "main". Eso significa que agregando este código al final de tu módulo:

if __name__ == "__main__":
  import sys
  fib(int(sys.argv[1]))

Esto nos permite usar el archivo como script o como módulo importable. Al analizar la linea de
ordenes sólo se ejecutará si el módulo es ejecutado como archivo principal.

7.2. Manejo de paquetes


Deciamos anteriormente que un paquete es un conjunto de módulos que además contiene el
módulo _ _ init _. De una manera mas formal podríamos decir que un _paquete es una forma de
estructurar el espacio de nombres de módulos mediante el uso de puntos:

• Por ejemplo el nombe del módulo A.B designara un submódulo B de un paquete llamado A.

• El uso de los módulos salva a los autores de los diferentes módulos de tener que preocuparse
por los nombres de las variables globales de cada uno

• El uso de nombres de módulo con puntos salva a los autores de paquetes con múltiples módulos
de preocuparse por los nombres de los módulos de cada uno.

Imaginemos que queremos usar una colección de módulos para el manejo uniforme de archivos y
datos de sonido, donde podemos encontrar varios tipos de archivos. Esto nos obligaria a crear y
mantener una coleccion siempre creciente de módulos para la conversión entre los distintos
formatos de archivos. Podríamos ejecutar muchas operaciones sobre estos datos, de esta forma
estariamos escribiendo una lista sin fin de módulos para realizar las operaciones. Imaginemos esta
posible estructura:

87
sound/ Top-level package
  __init__.py Initialize the sound package
  formats/ Subpackage for file format conversions
  __init__.py
  wavread.py
  wavwrite.py
  aiffread.py
  aiffwrite.py
  auread.py
  auwrite.py
  ...
  effects/ Subpackage for sound effects
  __init__.py
  echo.py Dentro de este módulo está la función echofilter
  surround.py
  reverse.py
  ...
  filters/ Subpackage for filters
  __init__.py
  equalizer.py
  vocoder.py
  karaoke.py
  ...

Los archivos init.py son obligatorios para que Python trate los directorios que contienen los
archivos como paquetes. Esto evita que los directorios con un nombre común, como string, oculten
involuntariamente módulos válidos que se producen luego en el camino de búsqueda del módulo.
En el caso mas simple, init.py puede ser solo un archivo vacío, pero también puede ejecutar código
de inicialización para el paquete o el conjunto de variables all, descriptas luego.

Vamos a ver como podemos importar parcial o completamente paquete

• Importando un modulo individual:

Vamos a cargar el submodulo sound.effects.echo

import sound.effects.echo

Esta sería la forma de ejecutarlo:

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

Otra manera alternativa para importar el submodulo:

from sound.effects import echo

88
De esta forma cargariamos el submodulo echo y lo estaríamos dejando disponible sin su prefijo de
paquete. Podriamos usarlo así:

echo.echofilter(input, output, delay=0.7, atten=4)

Una tercer forma sería dejar directamente disponible la función echofilter():

from sound.effects.echo import echofilter

echofilter(input, output, delay=0.7, atten=4)

Hay que destacar que al usar esta forma from package import ítem este item puede ser tanto un
módulo o subpaquete como algún otro nombre definido en el paquete, como una función, clase, o
variable. Realmente funciona de la siguiente manera:

• import_ primero verifica si el item está definido en el paquete

• Si no está definido en el paquete asume que es un módulo y trata de cargarlo.

• Si no lo encuentra genera una excepción ImportError.

Cuado usamos la sintaxis _import item.subitem.subsubitem, cada ítem excepto el último debe ser
un paquete; el mismo puede ser un módulo o un paquete pero no puede ser una clase, función o
variable definida en un ítem previo.

Hay que tener cuidado si se nos ocurre usar from sound.effects import * Lo lógico o
esperable es que fuera al sistema de archivos, encuentra los submódulos en el
 paquete y los importe todos. Pero hay que tener cuidado porque esto puede tardar
demasiado e importar submódulos que nos acarren efectos no esperados.

Para solucionar esto lo único que podemos hacer es como autores del paquete proveer un indice
explicito del paquete, porque la declaración import usa la siguiente convención: * Si el codigo
dentro de init.py define una lista llamada _ _ all _ _.

• Esta lista se toma como la lista de los nombres de los módulos que deberian ser importados con
from package import *|

• Es responsabilidad del autor del paquete mantener esta lista actualizada con las nuevas
versiones. Los autores de paquetes podrían no soportar un importar * si no ven un uso en sus
paquetes.

• Para el ejemplo de archivos de sonido nuestro "init" pdría contener el siguiente código

__all__ = ["echo", "surround", "reverse"]

De esta forma con from sound.effects import * importaria los tres submódulos del paquete sound

89
Si no se define all, la declaración from sound.effects import * no importa todos los submódulos del
paquete sound.effects al espacio de nombres actual; sólo se asegura que se haya importado el
paquete sound.effects (posiblemente ejecutando algún código de inicialización que haya en init.py)
y luego importa aquellos nombres que estén definidos en el paquete. Esto incluye cualquier
nombre definido (y submódulos explícitamente cargados) por init.py. También incluye cualquier
submódulo del paquete que pudiera haber sido explícitamente cargado por declaraciones import
previas. Considere este código:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

En este ejemplo, los módulos echo y surround se importan en el espacio de nombre actual porque
están definidos en el paquete sound.effects cuando se ejecuta la declaración from…import. (Esto
también funciona cuando se define all).

A pesar de que ciertos módulos están diseñados para exportar solo nombres que siguen ciertos
patrones cuando uses import *, también se considera una mala práctica en código de producción.

Recuerda, ¡no hay nada malo al usar from package import specific_submodule! De hecho, esta es la
notación recomendada a menos que el módulo que importamos necesite usar submódulos con el
mismo nombre desde un paquete diferente.

7.2.1. Referencias internas en paquetes

Cuando nuestro paquete está dividido en subpaquetes podemos usar import absolutos para para
referirte a submódulos de paquetes hermanos. Por ejemplo, si tenemos el módulo
sound.filters.vococer puede necesitar usar el módulo echo que se encuentra en sound.effects. Lo
importariamos de esta forma from sound.effects import echo

Es verdad, que también podríamos usar import relativos con la forma from module import name_.
Estos imports hacen uso de puntos para indicar los paquetes actuales o paquetes padre
involucrados en el import relativo. Por ejemplo con surround:

from . import echo


from .. import formats
from ..filters import equalizer

Los import relativos se basan en el nombre del modulo actual porque los el
nombre del modulo principal es siempre "main", los módulos pensados para
 usarse como modulo principal de una aplicación Python siempre deberían usar
import absolutos.

7.2.2. Paquetes en varios directorios

Ademas de los atributos o métodos vistos hasta ahora los paquetes soportan un atributo especial
mas, path. Este path inicializa una lista que contiene el nombre del directorio donde está init del

90
paquete, antes de que el codigo en ese archivo se ejecute.

Esta variable se puede modificar y afectara búsquedas futuras de módulos y subpaquetes


contenidos en el paquete.

Aunque esta característica no se necesita frecuentemente, puede usarse para extender el conjunto
de módulos que se encuentran en el paquete.

91
7.3. Lab: Módulos y paquetes
7.3.1. Creando un módulo

Crea un módulo propio llamado fibo.py en el que vamos a calcular la secuencia de Fibbonacci

Solución

# Fibonacci numbers module

def fib(n): # write Fibonacci series up to n


  a, b = 0, 1
  while a < n:
  print(a, end=' ')
  a, b = b, a+b
  print()

def fib2(n): # return Fibonacci series up to n


  result = []
  a, b = 0, 1
  while a < n:
  result.append(a)
  a, b = b, a+b
  return result

• Importa este nuevo modulo desde el interprete de Python Solución

>>> import fibo

De esta manera solo hemos añadido el nombre del módulo al espacio actual. Para usar las
funciones que contiene deberemos usar nombredemódulo.funcion para acceder a ellas.

Solución

>>> fibo.fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'

• Asigna un nombre local a la función para poder usarla frecuentemente

Solución

92
>>> fib = fibo.fib
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

• Modifica la secuencia de importación para poder usarla frecuentemente sin necesidad de


asignarle un nombre.

Solución

>>> from fibo import fib, fib2


>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

De esta forma hacemos lo contrario que en el caso anterior estamos introduciendo en nuestro
espacio el nombre de las funciones pero el del módulo.

• Ahora importa fibo asignandole el alias fib. De esta forma el alias queda ligado al módulo
importado. Es lo mismo que un import fibo solo que ahora en nuestro espacio usaremos fib para
referirnos a fibo.

Solución

>>> import fibo as fib


>>> fib.fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

• Amplia su uso usando from

>>> from fibo import fib as fibonacci


>>> fibonacci(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

7.3.2. Creando e importando un paquete

Crea un paquete con el nombre que elijas, dentro debe contener dos subpaquetes:

• Uno debe contener una función que nos salude

• El otro debe contener una función que nos despida

Importa las funciones correspondientes desde el paquete.

93
── paquete
│   ├── despedida
│   │   ├── adios.py
│   │   └── __init__.py
│   ├── hola
│   │   ├── __init__.py
│   │   └── saludos.py
│   └── __init__.py
└── script.py

Esta es la estructura que debemos tener para que funcione, una vez creados el paquete,
subpaquetes y módulos crearemos el script.py para ejecutarlo.

Solución

Lo primero es crear

__init.py__

Para el paquete principal y cada subpaquete y después:

paquete/hola/saludos.py

def saludar():
  print("Hola, ¿como estás? Yo estoy bien, puedes encontrarme dentro de la funcion saludar del módulo saludos")

paquete/despedida/adios.py

def despedir():
  print("Hasta luego, me despido del curso de Python desde la funciñon despedir del módulo adios")

Ahora creamos el script.py en el mismo directorio donde se encuentra nuestro paquete

from paquete.hola.saludos import saludar


from paquete.despedida.adios import despedir

saludar()
despedir()

Y lo ejecutamos desde Visual Studio Code. Este será el resultado

[python@fedora ~]$ /bin/python /home/python/workspace/script.py


Hola, ¿como estás? Yo estoy bien, puedes encontrarme dentro de la funcion saludar del módulo saludos
Hasta luego, me despido del curso de Python desde la funciñon despedir del módulo adios

94
7.3.3. Distribuyendo un paquete

Vamos a hacer ahora un paquete que podamos distribuir. Para ello, lo primero es crear un fichero
setup.py fuera de la raíz del paquete. Indicando una información básica de la siguiente forma:

.
├── paquete
│   ├── despedida
│   │   ├── adios.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   │   ├── adios.cpython-39.pyc
│   │   └── __init__.cpython-39.pyc
│   ├── hola
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-39.pyc
│   │   │   └── saludos.cpython-39.pyc
│   │   └── saludos.py
│   ├── __init__.py
│   └── __pycache__
│   └── __init__.cpython-39.pyc
├── script.py
└── setup.py

Para escribir setup.py nos vamos a ayudar del modulo setuptools y la función setup

from setuptools import setup


setup(
  name="paquete",
  version="0.1",
  description="ejemplo de paquete distribuible",
  author="Benja",
  author_email="[email protected]",
  packages=['paquete','paquete.hola','paquete.despedida'],
  scripts=[]
)

Cuando ya hemos definido la información básica, incluyendo los paquetes y subpaquetes que lo
forman como los scripts (en caso de tenerlos), vamos a crear el paquete distribuible. Dedemos
utilzar el siguiente comando en el directorio donde tengamos setup.py

python setup.py sdist

Si todo ha salido bien deberemos ver una carpeta comprimida llamadas dist. Este fichero es
unuestro distribuible, ya podemos compartirlo con todo el mundo para que instale nuestro
paquete.

95
Para instalar un paquete en Python, y hacer que se pueda usar desde cualquier lugar lo haremos de
esta forma, siempre desde el directorio donde este el paquete comprimido.

pip install <paquete>

Ahora lo desinstalamos con

pip uninstall <paquete>

96
Capítulo 8. Manipulación de parametros de
entrada/salida
Los programas que escribamos, recibirán ciertos parámetros de entrada para trabajar con ellos.
Veremos en este capítulo como gestionar todo tipo de parámetros.

8.1. Tipos de argumentos


A grandes rasgos, cuando pasamos parámetros a nuestros programas, diferenciamos entre 3 tipos:

• Parámetros posicionales: Nuestro programa hará una u otra cosa en función de la posición que
ocupe dicho parámetro. El ejemplo más claro es la orden 'cp origen destino'

• Parámetros opcionales: Modifican cierto comportamiento de nuestro programa, caso de estar


presentes. Otro ejemplo claro es 'ls -l'. El parámetro opcional -l hace que se listen los detalles de
los directorios, no solo sus nombres.

• Parámetros de ayuda: Explican cómo usar nuestro programa. Es habitual el uso del parámetro
--help en muchos programas para mostrar ayuda de uso

En Python 3 tenemos a nuestra disposición al menos 3 módulos que nos permitirán gestionar todos
estos tipos de parámetros:

• argparse: El más popular y utilizado actualmente

• getopt: Otro parseador, más familiar para los programadores de C (se parece mucho a la función
homónima de C), pero algo más limitado

• optparse: El padre de argparse. Actualmente obsoleto, pero aun disponible.

Nosotros vamos a usar argparse, del cual tenemos un buen resumen en este enlace.

Empezamos por el Hola Mundo:

8.2. Empezando con argparse


Importemos argparse y empecemos con lo más básico, para ello creamos el siguiente script de
python y lo guardamos como argparse01.py

import argparse
parser = argparse.ArgumentParser()
parser.parse_args()

Si corremos el script anterior con la opción --help: 'python argparse01.py --help', obtenemos esta
salida:

97
usage: argparse01.py [-h]

optional arguments:
  -h, --help show this help message and exit

Es decir, las opciones -h y --help se gestionan automáticamente. No está mal. Pero no podemos ir
mucho más allá por el momento. Intentemos otro parámetro opcional: 'python argparse01.py
--verbose'

La salida será:

usage: argparse01.py [-h]


t05ej01.py: error: unrecognized arguments: --verbose

¿Y si le intentamos pasar un argumento posicional? 'python argparse01.py foo'

usage: argparse01.py [-h]


argparse01.py: error: unrecognized arguments: foo

Comencemos a gestionar parámetros. Empezamos con los posicionales

8.3. Parámetros posicionales


Vamos a permitir que nuestro programa acepte un solo parámetro posicional, al que llamaremos
echo. El resultado será, simplemente, mostrar por pantalla ese mismo parámetro. A este nuevo
script lo llamaremos argparse02.py

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo")
args = parser.parse_args()
print(args.echo)

Ahora, nuestro programa requerirá un parámetro echo para funcionar. Es decir, que si
ejecutamos 'python argparse02.py', obtendremos

usage: argparse02.py [-h] echo


argparse02.py: error: too few arguments

Usemos la ayuda para ver qué parámetros podemos usar: 'python argparse02.py --help'

98
usage: argparse02.py [-h] echo

positional arguments:
  echo

optional arguments:
  -h, --help show this help message and exit

Ya sabemos que nuestro programa requiere un argumento posicional, pero aun no sabemos para
qué se usa. Vamos a añadir esa información. Volvemos a rescribir el archivo y lo guardamos como
argparse03.py

# -*- coding: utf-8 -*-


import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo", help="simplemente muestra por pantalla la cadena que le pasemos en esta posición")
args = parser.parse_args()
print(args.echo)

Si ahora pedimos la ayuda: 'python argparse03.py --help'

usage: argparse03.py [-h] echo

positional arguments:
  echo simplemente muestra por pantalla la cadena que le pasemos en
  esta posición

optional arguments:
  -h, --help show this help message and exit

== Si estuviéramos trabajando con Python 2, tendríamos un problema al añadir en


un fichero de código fuente Python caracteres no ASCII (en nuestro caso, la
 palabra posición) sin especificar qué codificación tienen dichos caracteres.
Recordemos que Python utiliza cadenas de texto y codificación ASCII por defecto.
==

Gracias a que estamos trabajando con Python 3, que usa unicode para todas las cadenas de texto, no
tenemos problemas con el caracter acentuado que hemos añadido en nuestro script. Pero en
Python 2 habríamos obtenido el siguiente error:

File "argparse03.py", line 4


SyntaxError: Non-ASCII character '\xc3' in file sources/t05/t05ej03.py on line 4, but no encoding declared; see http:
//python.org/dev/peps/pep-0263/ for details

Como se indica en el enlace que nos sugiere el error, para poder trabajar de manera segura en
Python 2 con un fichero que contiene caracteres no ASCII, debemos añadir lo siguiente como
primera línea del fichero

99
# -*- coding: utf-8 -*-

En Python 3 no es necesario, como ya hemos explicado.

Ahora que ya sabemos más sobre nuestro script, podemos llamarlo con un argumento, para que
nos devuelva el eco: 'python sources/t05/t05ej03.py "hola mundo"'

Vamos ahora a intentar hacer algo con las variables que le pasemos de entrada, además de
mostrarlas. (Lo guardamos como argparse04.py)

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", help="Muestra el número que le hayamos pasado elevado al cuadrado", type=int) ①
args = parser.parse_args()
print(args.square**2)

① Pasamos type=int para indicar que el parámetro sea convertido a un número entero

Lo lanzamos con 'python argparse04.py 5', y obtenemos '25', que es el resultado de elevar 5 al
cuadrado.

8.4. Parámetros optativos


Hagamos ahora un programa que nos acepte parámetros optativos (argparse05.py)

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbosity", help="incrementa la verbosidad de la salida por pantalla")
args = parser.parse_args()
if args.verbosity:
  print("Ahora saco más cosas por pantalla")

Lo lanzamos con 'python argparse05.py --verbosity 1', y obtenemos la siguiente salida

Ahora saco más cosas por pantalla

Pero, ¿y si quiero pasarle simplemente --verbosity, como hacía con --help?

Si lo que queremos es construir un parámetro optativo que realmente actúe como un flag, que es
como se comporta --help, tendremos que especificárselo.

Este nuevo script lo grabaremos como argparse06.py

100
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", help="incrementa la verbosidad de la salida por pantalla", action="store_true") ①
args = parser.parse_args()
if args.verbose:
  print("Ahora saco más cosas por pantalla")

① Pasamos store=true para indicar que, caso de estar, ese parámetro tome el valor True

Ya podemos llamarlo 'python argparse06.py --verbosity', para obtener la misma salida que en el
caso anterior.

La mayoría de parámetros optativos tienen una version abreviada. Es muy fácil añadirla.
(argpase07.py):

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", "-v", help="incrementa la verbosidad de la salida por pantalla", action="store_true") ①
args = parser.parse_args()
if args.verbose:
  print("Ahora saco más cosas por pantalla")

① Con -V obtenemos lo mismo que con --verbosity

Ahora puedo correr 'python argparse07.py -v'

8.5. Mezclando parámetros posicionales y optativos


Hagamos un programa un poco más complejo mezclando ambos conceptos. Lo guardamos como
argparse08.py

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
  help="Muestra el número que le hayamos pasado elevado al cuadrado")
parser.add_argument("-v", "--verbose", action="store_true",
  help="incrementa la verbosidad de la salida por pantalla")
args = parser.parse_args()
answer = args.square**2
if args.verbose:
  print("El número {} elevado al cuadrado es {}".format(args.square, answer))
else:
  print(answer)

Ahora podemos llamarlo de dos maneras: 'python argparse08.py -v 4' ó 'python argparse08.py 4 -v'.
En ambos casos, obtendremos la misma salida:

El número 4 elevado al cuadrado es 16

101
También podemos limitar los valores de una determinada opción a un rango (argparse09.py)

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
  help="Muestra el número que le hayamos pasado elevado al cuadrado")
parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2], ①
  help="incrementa la verbosidad de la salida por pantalla")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
  print("El número {} elevado al cuadrado es {}".format(args.square, answer))
elif args.verbosity == 1:
  print("{}^2 == {}".format(args.square, answer))
else:
  print(answer)

① Ahora verbosity será un entero con valores entre 0 y 2

Podemos probar todos los niveles

python argparse09.py -v 0
python argparse09.py -v 1
python argparse09.py -v 2

Obtenemos estas salidas

16
4^2 == 16
El número 4 elevado al cuadrado es 16

Vamos a cambiar la manera de trabajar de -v, para que se asemeje más a los típicos programas
escritos en Python, o incluso en C. En lugar de especificar un nivel de verbosidad, contaremos el
número de veces que aparece -v. (argparse10.py)

102
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
  help="Muestra el número que le hayamos pasado elevado al cuadrado")
parser.add_argument("-v", "--verbosity", action="count", default=0, ①
  help="incrementa la verbosidad de la salida por pantalla")
args = parser.parse_args()
answer = args.square**2
if args.verbosity >= 2:
  print("El número {} elevado al cuadrado es {}".format(args.square, answer))
elif args.verbosity >= 1:
  print("{}^2 == {}".format(args.square, answer))
else:
  print(answer)

① Con este flag contamos el número de veces que se usa -v

Podemos llamarlo de estas maneras:

python argparse10.py 4
python argparse10.py 4 -v
python argparse10.py 4 -vv
python argparse10.py 4 -vvvvvvvvv

Obtendremos estas salidas

16
4^2 == 16
El número 4 elevado al cuadrado es 16
El número 4 elevado al cuadrado es 16

8.6. Gestionando conflictos entre opciones


Hay ocasiones en las que dos o más opciones son excluyentes entre si. Para gestionar esa situación,
argparse proporciona la función add_mutually_exclusive_group

Creamos el script y lo guardamos como argparse11.py

103
import argparse

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="la base")
parser.add_argument("y", type=int, help="el exponente")
args = parser.parse_args()
answer = args.x**args.y

if args.quiet:
  print(answer)
elif args.verbose:
  print("{} elevado a {} es igual a {}".format(args.x, args.y, answer))
else:
  print("{}^{} == {}".format(args.x, args.y, answer))

Probemos las dos opciones mutuamente exclusivas con 'python argparse11.py -q -v'

La salida obtenida es:

usage: argparse11.py [-h] [-v | -q] x y


argparse11.py: error: argument -v/--verbose: not allowed with argument -q/--quiet

Y para terminar, añadamos una explicación de lo que nuestro programa hace, salvamos el código
como argparse12.py

import argparse

parser = argparse.ArgumentParser(description="Este programa calcula X^Y")


group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="la base")
parser.add_argument("y", type=int, help="el exponente")
args = parser.parse_args()
answer = args.x**args.y

if args.quiet:
  print(answer)
elif args.verbose:
  print("{} elevado a {} es igual a {}".format(args.x, args.y, answer))
else:
  print("{}^{} == {}".format(args.x, args.y, answer))

Ejecutando con 'python argparse12.py -h' Obtenemos la siguiente salida

104
usage: argparse12.py [-h] [-v | -q] x y

Este programa calcula X^Y

positional arguments:
  x la base
  y el exponente

optional arguments:
  -h, --help show this help message and exit
  -v, --verbose
  -q, --quiet

105
8.7. Lab: Manipulacion E/S
A continuación repasaremos los conceptos aprendidos con unos ejercicios

8.7.1. Laboratorio 01

Crea un script python que lea de un fichero de texto pasado como argumento (obligatorio) línea a
línea. De cada línea, ha de eliminar símbolos de puntuación y pasar todo a minúsculas. Además, ha
de sacar cada línea por pantalla.

Para probarlo, lanza el intérprete de python3 desde consola, y usa cualquiera de los ficheros de
texto disponibles con estos cuadernos (ej: quijote.txt)

Pista: Para saber qué signos de puntuación eliminar, utiliza string.punctuation

Solucion

"""
Script que lee un fichero, elimina espacios en blanco y símbolos de puntuación, y pone todo en minúsculas.
"""
import argparse
import string

def main():
  parser = argparse.ArgumentParser()
  parser.add_argument("source_file", help="Fichero de entrada")
  args = parser.parse_args()
  filename = args.source_file

  with open(args.source_file, 'r') as f:


  for line in f:

  # Todos los caracteres en string.punctuation se mapean con un espacio en blanco


  line = line.translate(str.maketrans(string.punctuation, ' '*len(string.punctuation)))
  print(line.lower().strip())

if __name__ == '__main__':
  main()

8.7.2. Laboratorio 02

El fichero 'chebyshev.py' contiene código Python para graficar polinomios de Chebyshev. No te


preocupes de las matemáticas ni de las funciones gráficas. Lo que debes hacer es gestionar los
parámetros de entrada para que cumplan lo siguiente:

• Cuando el usuario lance el script con la opción de mostrar ayuda de los parámetros, lo primero
que le ha de aparecer es el texto: Crea gráfica de polinomios de Chebysev de órdenes M a N para
valores entre -X y +X

• Igualmente, lo último que ha de ver el usuario cuando pida ayuda es tu nombre y correo
electrónico: Autor: Fulanito de tal [email protected]

• El programa ha de exigir un único parámetro posicional, que será la ruta completa a un fichero

106
de imagen en formato PNG que será creado. Ejempo: grafica.png. La variable que almacenará el
valor de dicho fichero se llamará 'file', y será del tipo necesario para permitir que el programa
escriba en el fichero datos de tipo binario (como de hecho, hace). El texto de ayuda asociado a la
opción será Nombre del fichero para guardar la gráfica (obligatorio)

• El programa ha de aceptar un parámetro opcional -k (modo extendido: --npts) de tipo entero,


con valor por defecto 512, y con texto de ayuda: Número de puntos a graficar. La variable que
almacenará su valor se llamará 'npts'

• El programa ha de aceptar un parámetro opcional '-x' (modo extendido '--limit') de tipo 'float'
con valor por defecto 1.0 y con texto de ayuda: Rango de valores para x. La variable que
almacenará su valor se llamará 'limit'

• El programa ha de aceptar un parámetro opcional '-m' (modo extendido '--min') de tipo entero
con valor por defecto 1 y con texto de ayuda: Orden mínimo del polinomio. La variable que
almacenará su valor se llamará 'min'

• El programa ha de aceptar un parámetro opcional '-M' (modo extendido '--max') de tipo entero
con valor por defecto 3 y con texto de ayuda: Orden maximo del polinomio. La variable que
almacenará su valor se llamará 'max'

• El programa ha de aceptar un parámetro opcional '-t' (modo extendido '--title') de tipo string con
valor por defecto vacío y con texto de ayuda: Título de la gráfica. La variable que almacenará su
valor se llamará 'title'

Pista: Investiga el uso de argparse.FileType para ver cómo declarar argumentos de tipo válido para
que puedan ser tratados como ficheros

Pista: Investiga la clase argparse.ArgumentParser para averiguar cómo obtener lo que se pide en el
primer requisito

A continuación, se muestra el fichero 'chebyshev.py', listo para completar.

107
import argparse
import numpy

# Tu código aquí: construye un procesador de argumentos de entrada usando argparse.ArgumentParser

# Parse the command line


arguments = parser.parse_args()

# Create the graph


# Matplotlib is slow to load so put it here to not delay the parsing
import matplotlib.pyplot as pyplot

npts = arguments.npts
limit = arguments.limit
x = numpy.linspace(-1.0*limit, limit, npts)

# Run through the Chebyshev polynomials


M = arguments.min
N = arguments.max + 1
for order in range(M,N):
  y = numpy.where(
  numpy.abs(x) < 1.0,
  numpy.cos(order*numpy.arccos(x)),
  numpy.where(
  x > 0.0,
  numpy.cosh(order*numpy.arccosh(x)),
  (-1.0)**order*numpy.cosh(order*numpy.arccosh(numpy.abs(x)))
  )
  )

  pyplot.plot(x,y)

if arguments.title:
  pyplot.suptitle(arguments.title)

f = arguments.file
pyplot.savefig(f)
f.close()

Una llamada de ejemplo sería

python chebyshev.py -k 100 -x 1 -m 1 -M 3 -t "Grafica de prueba" g.png

Solución

108
import argparse
import numpy

# Tu código aquí: construye un procesador de argumentos de entrada usando argparse.ArgumentParser


help_text = '''Crea gráfica de polinomios de Chebysev de órdenes M a N para valores entre -X y +X.'''
sign_off = 'Author: Bob Dowling <[email protected]>'

parser = argparse.ArgumentParser(description=help_text, epilog=sign_off)

parser.add_argument('-k', '--npts', dest='npts', type=int, default=512, metavar='k', help='Número de puntos a


graficar')
parser.add_argument('-x', '--limit', dest='limit', type=float, default=1.0, metavar='X', help='Rango de valores para x.')
parser.add_argument('-m', '--min', dest='min', type=int, default=1, metavar='m', help='Orden mínimo del
polinomio.')
parser.add_argument('-M', '--max', dest='max', type=int, default=3, metavar='M', help='Orden maximo del
polinomio.')
parser.add_argument('-t', '--title', dest='title', type=str, default='', metavar='title', help='Título de la gráfica')
parser.add_argument( dest='file', type=argparse.FileType('wb'), metavar='fname', help='Nombre del
fichero para guardar la gráfica (obligatorio)')

# Parse the command line


arguments = parser.parse_args()

# Create the graph


# Matplotlib is slow to load so put it here to not delay the parsing
import matplotlib.pyplot as pyplot

npts = arguments.npts
limit = arguments.limit
x = numpy.linspace(-1.0*limit, limit, npts)

# Run through the Chebyshev polynomials


M = arguments.min
N = arguments.max + 1
for order in range(M,N):
  y = numpy.where(
  numpy.abs(x) < 1.0,
  numpy.cos(order*numpy.arccos(x)),
  numpy.where(
  x > 0.0,
  numpy.cosh(order*numpy.arccosh(x)),
  (-1.0)**order*numpy.cosh(order*numpy.arccosh(numpy.abs(x)))
  )
  )

  pyplot.plot(x,y)

if arguments.title:
  pyplot.suptitle(arguments.title)

f = arguments.file
pyplot.savefig(f)
f.close()

La consola puede arrojar mensajes de error relacionados con el archcos() pero la


 representación se esta guardando en el directorio en el que estemos situados.

8.7.3. Laboratorio 03

Escribir una aplicación de línea de comandos que devuelva la siguiente respuesta cuando se ejecuta
con el flag -h

109
usage: cli.py [-h] [-u USERNAME] [-p]

My connection client

optional arguments:
  -h, --help show this help message and exit
  -u USERNAME, --username USERNAME
  Username. If not provided it will be read from USERNAME env
var
  -p, --password Ask for password

Author: John Doe <[email protected]>

El funcionamiento de la aplicación es el siguiente:

• Si se le llama sin argumentos, deberá fallar con el mensaje: Error: user and password needed

• Si se le pasa el argumento -u (o --username), leerá el nombre de usuario a partir de dicho


argumento. Si no se le pasa, lo leerá de la variable de entorno USERNAME. Si tampoco lo
encuentra, estaríamos en el primer caso (programa sin argumentos)

• Si se le pasa el argumento -p (o --password), leerá el password a partir de dicho argumento. Si


no se le pasa, lo leerá de la variable de entorno PASSWORD. Si tampoco lo encuentra, estaríamos
en el primer caso (programa sin argumentos)

Si se leen correctamente username y password, se llamará a la función verify proporcionada. Dicha


función devolverá True si las credenciales de usuario son válidas. En ese caso, mostrará por
pantalla Correct credentials y terminará. Si son incorrectas, la función devolverá False y mostrará
por pantalla Wrong credentials antes de finalizar.

Se muestran a continuación ejemplos de la salida esperada en función de la entrada proporcionada:

110
# Llamada
python cli.py

# Salida
Traceback (most recent call last):
  File "cli.py", line 62, in <module>
  raise Exception("Error: user and password needed")
Exception: Error: user and password needed

# Llamada
USERNAME=johndoe python cli.py

# Salida
Traceback (most recent call last):
  File "cli.py", line 62, in <module>
  raise Exception("Error: user and password needed")
Exception: Error: user and password needed

# Llamada
USERNAME=johndoe PASSWORD=wrongpassword python cli.py

# Salida
Wrong credentials

# Llamada
USERNAME=johndoe PASSWORD=mypassword python cli.py

# Salida
Correct credentials

# Llamada
python cli.py -u johndoe -p

# Salida
Introduce password:

(en función de si metemos el password bien o mal, pondrá Correct credentials o Wrong
credentials)

Este es el esqueleto de la aplicación cli, para facilitar la labor

111
import argparse
import getpass
import hashlib
import os

# CONSTANTS
HELP_TEXT = 'My connection client'
SIGN_OFF = 'Author: John Doe <[email protected]>'

# FUNCTIONS
def users_dict():
  USERNAME = 'johndoe'
  PASSWORD = 'mypassword'

  salt = os.urandom(32) # A new salt for the user


  key = hashlib.pbkdf2_hmac('sha256', PASSWORD.encode('utf-8'), salt, 100000)
  users = {
  USERNAME: {
  'salt': salt,
  'key': key
  }
  }

  return users

def handle_arguments(description, epilog):


  # Tu codigo aqui

def verify(username, password):


  users = users_dict()
  if username not in users:
  raise Exception('Username not found')

  salt = users[username]['salt']
  key = users[username]['key']
  new_key = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000)

  return key == new_key

# MAIN PROGRAM
if __name__ == "__main__":
  args = handle_arguments(HELP_TEXT, SIGN_OFF)

  # Tu codigo aqui

Solución

112
import argparse
import getpass
import hashlib
import os

# CONSTANTS
HELP_TEXT = 'My connection client'
SIGN_OFF = 'Author: John Doe <[email protected]>'

# FUNCTIONS
def users_dict():
  USERNAME = 'johndoe'
  PASSWORD = 'mypassword'

  salt = os.urandom(32) # A new salt for the user


  key = hashlib.pbkdf2_hmac('sha256', PASSWORD.encode('utf-8'), salt, 100000)
  users = {
  USERNAME: {
  'salt': salt,
  'key': key
  }
  }

  return users

def handle_arguments(description, epilog):


  parser = argparse.ArgumentParser(description=description, epilog=epilog)

  parser.add_argument("-u", "--username", help="Username. If not provided it will be read from USERNAME env var")
  parser.add_argument("-p", "--password", action="store_true", help="Ask for password")

  args = parser.parse_args()

  return args

def verify(username, password):


  users = users_dict()
  if username not in users:
  raise Exception('Username not found')

  salt = users[username]['salt']
  key = users[username]['key']
  new_key = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000)

  return key == new_key

# MAIN PROGRAM
if __name__ == "__main__":
  args = handle_arguments(HELP_TEXT, SIGN_OFF)

  username = args.username or os.getenv('USERNAME')


  if args.password:
  password = getpass.getpass("Introduce password: ")
  else:
  password = os.getenv('PASSWORD')

  if not username or not password:


  raise Exception("Error: user and password needed")

113
  if verify(username, password):
  print("Correct credentials")
  else:
  print("Wrong credentials")

114
Capítulo 9. Test, depuración y loggin
En este capítulo vamos a hacer un workshop. Simularemos una sesión de testing y depuración
usando las herramientas que Python pone a nuestra disposición:

• El módulo 'unittest' y su interfaz de línea de comandos

• El módulo 'logging' para mostrar mensajes de error

• El depurador de Python, pdb

Vamos a utilizar el código disponible en este repositorio de Github. Se puede seguir el taller de dos
maneras:

• Descargando el código del repositorio en formato zip

• Clonando el repositorio

Si clonamos el repositorio en nuestra máquina, podemos ir alternando entre la


 rama master, en la que trabajaremos, y la rama solutionsp3, donde se encuentran
resueltos los ejercicios propuestos durante el taller

Existe también la rama 'solutions' que muestra las soluciones cuando trabajamos
 con Python 2 en vez de Python 3. La diferencia está únicamente en el último
ejemplo: test_chicago_result_loader

9.1. Tests unitarios


Vamos a empezar ejecutando unos cuantos tests unitarios

python -m unittest tests.test_basic

Veremos una salida como ésta

F..
======================================================================
FAIL: test_true_is_true (tests.test_basic.FailingTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/jorge/pronoide/nicar2016-python-testing-debugging-
exercises/tests/test_basic.py", line 13, in test_true_is_true
  self.assertEqual(False, True)
AssertionError: False != True

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)

115
Abriendo el fichero tests/test_basic.py, vemos el test que ha fallado. Es bastante sencillo

self.assertEqual(False, True)

En este punto, aun no hemos visto nada sobre tests unitarios en Python, pero mirando el código,
entendemos lo básico:

• El módulo que importamos se llama 'unittest'. Contiene lo necesario para implementar tests
unitarios

• La clase básica es 'TestCase'. De ahí heredaremos las nuestras

• Dentro de cada clase hija de 'TestCase', definiremos tantos métodos como pruebas queramos
hacer

• Compararemos los valores esperados con los valores devueltos mediante la familia de funciones
'assert'

Continuamos lanzando tests. Dentro una test suite podemos lanzar tests cases individuales

python -m unittest tests.test_basic.NoFailuresTestCase

Incluso funciones de tests individuales

python -m unittest tests.test_basic.NoFailuresTestCase.test_true_is_true

9.2. Capturando excepciones en tests


Es posible que se nos haya escapado capturar alguna excepción, y los tests ni siquiera lleguen a
ejecutarse por eso. Para provocar ese error, ejecutar:

python -m unittest tests.test_result_loader

Obtendremos algo así

116
E
======================================================================
ERROR: test_load_bad_json (tests.test_result_loader.SimpleResultLoaderTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/jorge/pronoide/nicar2016-python-testing-debugging-
exercises/tests/test_result_loader.py", line 13, in test_load_bad_json
  results = loader.load(sample_json)
  File "/home/jorge/pronoide/nicar2016-python-testing-debugging-
exercises/results/__init__.py", line 10, in load
  parsed = json.loads(s)
  File "/usr/lib/python3.5/json/__init__.py", line 319, in loads
  return _default_decoder.decode(s)
  File "/usr/lib/python3.5/json/decoder.py", line 339, in decode
  obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.5/json/decoder.py", line 357, in raw_decode
  raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 4 column 9 (char 42)

----------------------------------------------------------------------
Ran 1 test in 0.003s

FAILED (errors=1)

Como vemos, el error está en el fichero 'tests/test_result_loader.py'. Concretamente en la línea 13,


función 'test_load_bad_json'

Abrimos el fichero, y vemos que en esa línea se llama a una función que carga un dato de tipo JSON,
pero dentro de la función, no se comprueba si el dato ha sido correctamente cargado o no antes de
operar con él. Eso provoca un error al intentar acceder a uno de sus campos

Ejercicio: Arregla la situación para evitar que se produzca la excepción


 incontrolada.

Esto tipo de problemas casi siempre se da porque damos por sentadas cosas sin probarlas antes.
Por ejemplo, ¿qué pruebas ejecutarías en este trozo de código para tener controlados los errores?

def pruebame(items):
  return [items[1].upper(), items[0].upper()]

Ejercicio: Haz la función anterior segura ante potenciales errores por


 inconsistencia en los datos.

9.3. TDD
Un posible enfoque a la hora de desarrollar software es escribir primero el código de prueba, y
luego el código a probar, una vez has cubierto todos los casos que se necesitan. Sus defensores

117
mantienen que ayuda a escribir mejor código y más mantenible.

Vamos a intentar este enfoque con un ejercicio sencillo:

El fichero tests/test_names.py está vacío. Crea una clase de tests llamada 'TestNames'. Dentro, un
solo método, llamado 'test_parse_names'. En dicho método, llamarás a una función 'parse_names',
que implementarás dentro del fichero names/init.py.

La función recibirá como argumento una cadena con las partes del nombre de una persona,
separadas por comas. Por ejemplo:

Arévalo, de Soto, Jorge, Sr..

Como respuesta, devolverá un json como éste:

{
  "first_name": "Jorge",
  "last_name": "de Soto",
  "middle_name": "Arévalo",
  "suffix": "Sr"
}

Si el sufijo no existe, se devolverá 'None' en ese campo.

Por supuesto, habrás de implementar luego la clase 'parse_names', importarla, y llamarla con los
casos de prueba que se te hayan ocurrido.

Una vez termines tanto el código de tests como la función en si, podrás ejecutar el test de esta forma

python -m unittest tests.test_names

Y deberías obtener una salida similar a ésta

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

 Ejercicio: Implementa la clase de pruebas y la clase a probar

9.4. Refactorizando
A veces, diseñamos nuestro código de manera que es complicado de probar. Creamos clases o
funciones excesivamente complejas, cuando lo ideal sería crear pequeñas funciones que fueran

118
sencillas de probar.

Por ejemplo, corramos el siguiente test

python -m unittest tests.test_chicago_result_loader.TestChicagoResultLoader

Si abres el código del test, verás que se prueba el método ChicagoResultsLoader.load, que abre un
fichero de texto y carga datos, devolviendo una estructura de tipo JSON, cuyos valores se
comprueban en el test.

Sería más interesante que tuviéramos dos funciones:

• función 'load', para cargar los datos del fichero, línea a línea

• funcion 'parse_result', para transformar la línea leída en un elemento JSON

Y pudiéramos probar esas dos funciones por separado desde nuestra función de test.

Dado que en esas circunstancias pasaríamos a tener más de una función de test, hay ciertas tareas
repetitivas que ejecutamos en cada caso de test, y que podríamos extraer fuera (instanciar la clase a
probar, y obtener la ruta al fichero de datos).

Por último, podría ser interesante añadir funcionalidad de log al código a probar. De esta forma,
además de probarlo con los tests, también generaríamos en un fichero de texto la salida que la
función probada va generando

Ejercicio: Haz las siguientes tareas

• Refactoriza el código de la clase 'ChicagoResultsLoader.load', dividiendolo en


dos métodos: 'load' y 'parse_result', que probarás por separado

• En cada caso de test, habrá código repetitivo que podrás sacar fuera (instanciar
la clase a probar, y obtener la ruta al fichero de datos). Investiga el uso de la
 función unittest.TestCase.setUp para centralizar tareas repetitivas que se
tendrían que realizar en todos los tests

• Añade a los métodos refactorizados 'ChicagoResultsLoader.load' y


'ChicagoResultsLoader.parse_result' funcionalidad de logging. Deberán sacar el
log a un fichero de texto externo. Investiga el módulo logging para averiguar
cómo sacar log a un fichero externo

Recuerda: Si quieres que tu vida como desarrollador sea mejor, jamás


 refactorices nada que no tenga tests.

9.5. Depurando código


Por muchos tests que escribamos y muy bien que diseñemos nuestro código, en algún momento
tendremos que acabar depurando para encontrar algún error que se nos ha escapado. Vamos a ver
el uso básico del depurador de Python, 'pdb', y algunas funciones útiles.

119
A grandes rasgos, hay dos situaciones en las que voy a usar 'pdb':

*. Metiendo en mi código llamadas a 'pdb_set_trace' para que el intérprete pare en ese punto
(añadir puntos de parada)

import pdb

def debug_this(i1, i2):


  result = i1
  pdb.set_trace() # Va a parar aqui
  for i in range(5):
  result += i2
  return result

result = debug_this(10, 20)

*. Entrando en una sesión del intérprete de Python y depurando explícitamente la función mediante
una llamada a 'pdb.runcall'

import pdb

# Ahora la función no tiene puntos de parada


def debug_this(i1, i2):
  result = i1
  for i in range(5):
  result += i2
  return result

# Pero ya la llamo yo
pdb.runcall(debug_this, 10, 20)

En cualquiera de los dos casos, entraríamos en una sesión del depurador y podríamos ejecutar las
instrucciones paso a paso, entre otros comandos. Los más populares que podríamos usar son:

9.5.1. Comando (l)ist

Para ver las líneas de código donde nos encontramos

(Pdb) l
  1 def debug_this(i1, i2):
  2 -> result = i1
  3 for i in range(5):
  4 result += i2
  5 return result

120
9.5.2. Comando (w)here

Para ver dónde nos encontramoms dentro de la pila de llamadas (puede ser bastante profunda)

(Pdb) w
  /usr/lib/python3.5/bdb.py(465)runcall()
-> res = func(*args, **kwds)
> /home/jorge/cursos/python/sources/tdebug/tdebugej02.py(5)debug_this()
-> result = i1

9.5.3. Comando (s)tep

Para entrar en el código de una función

(Pdb) l
  1 def foo():
  2 a = "hola"
  3 b = "bola"
  4 -> bar()
  5
[EOF]
(Pdb) s
--Call--
> <ipython-input-4-d5b067988c12>(1)bar()
-> def bar():
(Pdb) l
  1 -> def bar():
  2 x = 2
  3 y = 3
  4 z = x + y
  5
[EOF]

9.5.4. Comando (b)reak

Para añadir un punto de parada. Acepta dos modalidades:

• b [n]: Siendo n una linea del fichero actual. Si es una sesión de depuración en vivo, es decir,
desde dentro del intérprete de Python, no habiendo cargado un fichero, simplemente indica el
número de línea.

• b [funcion]: Siendo funcion una función concreta. Añade un punto de parada en la primera línea
evaluable de dicha función.

121
pdb.runcall(foo)
> <ipython-input-5-22ce54bbfd1f>(2)foo()
-> a = "hola"
(Pdb) l
  1 def foo():
  2 -> a = "hola"
  3 b = "bola"
  4 bar()
  5
[EOF]
(Pdb) b bar
Breakpoint 2 at <ipython-input-4-d5b067988c12>:1

9.5.5. Comando (c)ontinue

Para continuar hasta el siguiente punto de parada, o hasta el final del programa, si no hay ninguno
más.

pdb.runcall(foo)
> <ipython-input-5-22ce54bbfd1f>(2)foo()
-> a = "hola"
(Pdb) l
  1 def foo():
  2 -> a = "hola"
  3 b = "bola"
  4 bar()
  5
[EOF]
(Pdb) b bar
Breakpoint 2 at <ipython-input-4-d5b067988c12>:1
(Pdb) c
> <ipython-input-4-d5b067988c12>(2)bar()
-> x = 2
(Pdb) l
  1 B def bar():
  2 -> x = 2
  3 y = 3
  4 z = x + y
  5
(Pdb) c
[EOF]

9.5.6. Inspeccionar valores

En cualquier momento podemos imprimir el valor de una variable

122
(Pdb) result
11

Si has creado una variable con un nombre que entra en conflicto con alguno de los
 comandos de pdb, puedes acceder a la misma poniéndole un símbolo de
exclamación delante: !c

9.5.7. Comando (q)uit

Sirve para salir de la sesión de depuración

Al igual que existe una versión mejorada del intérprete de Python, llamada
 iPython, existe una versión mejorada del depurador, llamada 'ipdb'. Puedes
instalarla con 'pip install ipdb'

9.6. Rastreando errores


Vamos a intentar encontrar un problema en el código usando 'pdb'. Ejecutemos:

python -m unittest tests.test_chicago_result_loader.TestBrokenChicagoResultLoader

Veremos una salida de error similar a ésta:

  File "/home/jorge/cursos/nicar2016-python-testing-debugging-
exercises/tests/test_chicago_result_loade
r.py", line 27, in test_load
  self.assertEqual(alvarez['contest_code'], '0079')
AssertionError: '0790' != '0079'
- 0790
? -
+ 0079
? +

----------------------------------------------------------------------
Ran 1 test in 0.005s

FAILED (failures=1)

Por lo que se deduce del error, unos datos de un diccionario parecen erróneos, o desplazados con
respecto al valor esperado.

El test ejecuta el método 'BrokenChicagoResultsLoader.load', que carga datos del fichero


tests/data/summary.txt. En dicho fichero hay un registro por línea (es un fichero TSV en realidad), y
parece que hay algún problema leyendo el campo 'contest_code'.

123
Ejercicio: Investiga mediante una sesión de depuración cuáles son los valores
esperados para ese campo, y cuáles son los valores que en realidad se están
leyendo y porqué. Una vez identificado el error, arréglalo y vuelve a pasar los
tests.
 Pista: En el método 'BrokenChicagoResultsLoader.load' explica el formato del
fichero Pista: Fíjate en el nombre que causa el error. Tal vez te interese centrarte
en ese nombre, generando una versión reducida del fichero para que te resulte
más sencillo

Recuerda que el error será diferente si usas Python 2 en lugar de Python 3. Puedes
 cambiar a la rama 'solutions' para verlo.

124
Capítulo 10. Selenium Mozilla IDE
Selenium IDE es un plugin para nuestro navegador que básicamente lo que nos va a permitir es
grabar nuestras interacciones con las páginas web para generar unos tests automáticos y
reproducirlos. Mientras estamos interactuando con la página, se van a ir generando unos comandos
(Selenese) que representan las acciones que hemos realizado en el navegador y que se han estado
grabando. Estos comandos se pueden reproducir y nos mostrarán todas las acciones que hemos ido
haciendo mientras se grababan.

Podemos instalarnos Selenium IDE desde la siguiente dirección link::https://addons.mozilla.org/en-


US/firefox/addon/selenium-ide/[https://addons.mozilla.org/en-US/firefox/addon/selenium-ide/].

Una vez instalado, dentro de firefox, podemos abrir el plugin de las siguientes dos formas:

• Desde el icono al lado de la barra de dirección del navegador image::selenium-python-mozilla-


ide-02.png[Abrir Selenium IDE]

• Pulsando las teclas ctrl + shift + s

125
Selenium nos va a permitir grabar lo que nosotros hacemos en el navegador, y según vamos
interactuando con él, se van a ir generando unos comandos de selenium (Selenese), y una vez que
paremos la grabación, podremos reproducir todos los comandos que se han ido guardando.

10.1. Grabación de casos de prueba


Para empezar a grabar nuestros casos de prueba tenemos que darle al botón que se muestra en la
siguiente imagen:

126
Despues de pinchar sobre la opción nos pedirá:

• Nombre del proyecto

• URL de la web sobre la que queremos realizar la grabación. El primer paso será que Selenium
abra la web.

Una vez pulsado, podemos empezar a interactuar con el navegador para que se vayan guardando
las acciones que vamos realizando. Y cuando ya hayamos reproducido nuestro caso de prueba
paramos la grabación volviendo a pulsar sobre el botón. La interfaz del plugin, nos mostrará algo
como en la siguiente imagen:

127
En la anterior imagen, podemos ver que todas las acciones que se han ido realizando se han
guardado en una tabla, con la información necesaria para poder reproducir nuestras acciones
automáticamente.

10.2. Reproducción de casos de prueba


Ya tenemos nuestro primer test grabado. Ahora vamos a ver como lo podemos reproduccir. Para
ello tenemos dos botones, el primero que se encarga de reproducir todos los casos de test, y el
segundo que reproduce solo el caso de test que se encuentra seleccionado.

Una vez que le damos a uno de los dos botones, se puede ver en el navegador como se van
realizando todas las acciones que hemos grabado nosotros.

Al lado de los botones de reproducción, podemos encontrar una barra para modificar la velocidad a
la que se reproducen nuestros tests.

128
10.3. Debugger
Selenium IDE nos permite debuggear los tests que se han grabado para poder ver las acciones una a
una. Para ello pulsamos con el botón derecho sobre el comando en el que queremos poner un
breakpoint y le damos Toggle Breakpoint. Ahora al darle a reproducir el test, se parará en ese
comando.

129
Una vez que se ha parado en el punto de interrupción, podemos ejecutar el resto de comandos paso
a paso, o darle al botón de continuar con la reproducción.

10.4. Verificación de casos de prueba


Hasta ahora solo hemos visto como grabar y reproducir nuestros casos de prueba, pero todavía no
hemos llegado a testear nada, solo hemos visto como llegar hasta ciertas páginas. Lo que queremos
hacer es comprobar que en estas páginas se muestra lo que estamos esperando que se muestre. Y
hay dos formas de comprobarlo:

• Aserciones: comprueban que algo es cierto, y en caso de no serlo se termina la ejecución de los
tests. Por ejemplo, si queremos testear que dentro de la documentación de React hay una
sección sobre como se crean los componentes, y el test carga la página de Angular, no tiene
sentido continuar reproduciendo el resto de comandos, porque para empezar ya no estamos en
la página correcta, entonces este sería un caso en el que se puede usar las aserciones.

130
• Verificaciones: comprueban que existe lo que se busca, pero en caso de no hacerlo, el resto de
tests se seguirán ejecutando aunque esa verificación no la pase. Por ejemplo, en la página de
React, buscamos que haya dos secciones, una de como pasar propiedades a los componentes, y
otra como inicializar el estado en un componente, y a una de ellas se la ha cambiado el nombre
y ya no coincide con lo que se está verificando, pero la otra sin embargo, si que pasará el test.

131
10.5. Bancos de pruebas
Los bancos de pruebas son colecciones de tests. Ahora mismo solo hemos creado uno, pero
supongamos que queremos probar las distintas funcionalidades de una aplicación, Selenium IDE
nos permite añadir más casos de prueba en los que cada uno debería de encargarse de testear una
funcionalidad distinta de la aplicación. Vamos a crear un nuevo caso de prueba como aparece en la
siguiente imagen:

132
Una vez que lo tenemos creado, grabamos la secuencia de acciones a testear, y una vez que lo
paramos, ya tenemos dos casos de prueba distintos en nuestro banco de tests.

10.6. Selenese
Selenese son los comandos de Selenium que se van guardando en la tabla y que sirven para
ejecutar los tests. Estos comandos no pertenecen a ningún lenguaje de programación. En esta tabla
se van guardando tres valores: el comando (obligatorio), el objetivo (opcional) y un valor (opcional).

133
A esta tabla de comandos le podemos añadir nuestros propios comandos de una forma muy
sencilla. Botón derecho y le damos a Añadir nuevo comando.

Y rellenamos los valores de las tres columnas.

134
Todos los comandos de Selenese que hay disponibles se pueden ver
link::https://docs.katalon.com/display/KD/Selenese+%28Selenium+IDE%29+Commands+Reference[a
quí].

Estos comandos se dividen en tres tipos:

• Acciones: realizan acciones en el navegador.

• Aserciones: verifican el estado esperado del navegador.

• Almacenamiento: almacenan los valores intermedios en variables.

10.6.1. Comandos de navegación y acción

• open: abre una página empleando una URL.

• click: simula la acción de click.

• clickAndWait: simula la acción de click y espera a que la nueva página se cargue.

• waitForPageToLoad: para la ejecución hasta que la página esperada se carga. Es llamada por
defecto automáticamente cuando se ejecuta clickAndWait.

• waitForElementPresent: para la ejecución hasta que el UIElement esperado esté presente en la


página en alguna etiqueta HTML.

• chooseOkOnNextConfirmation: predispone a seleccionar en la siguiente ventana de


confirmación el botón de Aceptar.

• chooseCancelOnNextConfirmation: predispone a seleccionar en la siguiente ventana de


confirmación el botón de Cancelar.

10.6.2. Comandos de verificación

• verifyTitle: verifica que el título de la página es el esperado.

• assertTitle: verifica que el título de la página es el esperado, y en caso de no serlo para la


ejecución.

• verifyTextPresent: verifica que el texto esperado está en alguna parte de la página.

• verifyElementPresent: verifica que el UIElement esperado está definido como etiqueta HTML
en alguna parte de la página.

• verifyText: verifica si el texto esperado y su etiqueta HTML están presentes en la página.

• assertAlert: verifica si sale un alert con el texto esperado.

135
• assertConfirmation: verifica si sale una ventana de confirmación con el texto esperado.

10.6.3. Comandos de almacenamiento

• store: almacena en la variable el valor.

• storeElementPresent: almacena true o false dependiendo de si el UIElement está presente o no.

• storeText: almacena el texto encontrado. Es usado para encontrar un texto en un lugar de la


página específico.

10.7. Exportar
Todos los comandos que se van guardando en la tabla, se pueden exportar a un archivo con el
código en distintos lenguajes (Java, Ruby, Python, JavaScript…) que ya usa WebDriver. Para ello solo
tenemos que darle export test suite as… y elegir el lenguaje.

Para que se vea más claro a que comando pertenece cada instrucción que hay en el archivo que
acabamos de exportar, le podemos indicar que los añada como comentarios. Para ello marcamos la
opción de Include step description as a separate comment

136
Y si lo volvemos a exportar nos queda el código de la siguiente forma:

# Generated by Selenium IDE


import pytest
import time
import json
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

class TestPruebaspython2():
  def setup_method(self, method):
  self.driver = webdriver.Firefox()
  self.vars = {}

  def teardown_method(self, method):


  self.driver.quit()

  def test_pruebaspython2(self):
  self.driver.get("https://www.imdb.com/")
  self.driver.set_window_size(1920, 899)
  self.driver.find_element(By.CSS_SELECTOR, ".ipc-button--core-baseAlt #iconContext-arrow-drop-down").click()
  self.driver.find_element(By.CSS_SELECTOR, "#language-option-es-ES > .language-menu-item-span").click()
  self.driver.find_element(By.ID, "suggestion-search").click()
  self.driver.find_element(By.CSS_SELECTOR, ".imdb-header__signin-text > .ipc-button__text").click()
  self.driver.find_element(By.CSS_SELECTOR, ".list-group > .list-group-item:nth-child(1) > .auth-provider-text").click
()
  self.driver.find_element(By.ID, "ap_email").click()
  self.driver.find_element(By.ID, "ap_email").send_keys("[email protected]")
  self.driver.find_element(By.ID, "ap_password").send_keys("curso")
  assert self.driver.find_element(By.ID, "suggestion-search").text == "Super Mario"
  self.driver.find_element(By.ID, "signInSubmit").click()

10.8. Generar informes HTML de los casos de prueba


Vamos a instalar el plugin de Test Results (Selenium IDE) de Firefox desde el siguiente enlace:
link::https://addons.mozilla.org/es/firefox/addon/test-results-selenium-

137
ide/[https://addons.mozilla.org/es/firefox/addon/test-results-selenium-ide/]

Una vez instalado, ejecutamos los tests, y despues de que hayan terminado, exportamos los
resultados desde la pestaña de Archivo.

Si abrimos el archivo que hemos exportado nos mostrará una tabla por cada caso de prueba con
sus comandos. En verde saldrá aquellos tests que se han pasado correctamente, y en rojo los que
han fallado.

138
139
10.9. Lab: Mozilla IDE
10.9.1. Ejercicio 1

• Usando Selenium IDE, graba un test en el que:

◦ Accedes a la página principal de la wikipedia.

◦ Compruebas que el título de la página es el esperado.

10.9.2. Ejercicio 2

• Usando Selenium IDE, graba un test en el que:

◦ Accedes a la página principal de la wikipedia.

◦ Accedes a crear cuenta.

◦ Compruebas que existen los campos de usuario, contraseña, email y captcha.

◦ Rellenas todos los campos necesarios.

◦ Intentas registrar el usuario.

◦ Compruebas que aparece un error al haber rellenado mal el captcha.

10.9.3. Ejercicio 3

• Usando Selenium IDE, genera 3 tests para testear las busquedas avanzadas de la Wikipedia:

◦ Carga la página de búsqueda avanzada y testea su título.

◦ Click en el botón de 'avanzado' y selecciona algún checkbox.

◦ Escribe 'selenium' en el cuadro de búsqueda y comprueba que devuelve más de un


resultado.

140
Capítulo 11. Selenium WebDriver con
Python
WebDriver es el motor de pruebas automatizadas de Selenium. Nos proporciona una API que nos
permite realizar las pruebas y automatizar los navegadores web. Necesitamos un entorno de
desarrollo y algunas dependencias externas para poder crear los tests con Selenium WebDriver.

11.1. Configuración del entorno


Para empezar a escribir nuestros propios tests con Selenium y Python necesitaremos instalar
Python. Para ello podemos descargarlo desde el siguiente enlace e instalarlo:
www.python.org/downloads/.

Una vez instalado Python, necesitaremos instalar la librería de Selenium WebDriver para Python
que viene en el paquete de Selenium. Para ello solo necesitamos usar el gestor de paquetes de
Python, pip. Para instalar el paquete lanzamos el siguiente comando en el proyecto en el que vamos
a usar Selenium.

$ pip install selenium

Con este comando podremos hacer uso de las clases necesarias para generar los tests. Una vez
instalado ya podemos empezar a escribir nuestros tests.

11.2. Objeto WebDriver


Tras la instalación de Selenium necesitamos descargar e instalar el Web Driver del navegador que
vayamos a usar. Como deciamos antes, nos permitira movernos por la web, ejecutar JavaScript y
demás. Selenium soporta multiples navegadores, para elegir con el que vamos a trabajar (Chrome)
debemos ir a la página de Selenium y descargar la última versión estable para el navegador que
vayas a utilizar.

El objeto WebDriver nos permite usar instrucciones para automatizar el navegador Web. A través
de este objeto, podemos acceder a todo el contenido Web. Y soporta varios lenguajes (Java, Ruby,
Python…).

Selenium tiene implementado diferentes WebDrivers para dar soporte a distintos navegadores.

Table 1. Propiedades de la clase WebDriver

Propiedad Descripción Ejemplo

current_url Obtiene la URL de la página driver.current_url


actual

title Obtiene el título de la página driver.title


actual

141
Propiedad Descripción Ejemplo

name Obtiene el nombre del driver.name


navegador

Table 2. Métodos de la clase WebDriver

Método Descripción Ejemplo

get(url) Navega y carga la página en el driver.get('http://www.google.co


navegador m')

quit() Cierra el navegador y el driver driver.quit()

maximize_window() Maximiza el tamaño de la driver.maximize_window()


ventana del navegador

refresh() Refresca la página actual del driver.refresh()


navegador

implicitly_wait(time_to_time) Establece el tiempo que hay que driver.implicitly_wait(10)


esperar para encontrar un
elemento o para que se
complete un comando

refresh() Refresca la página actual del driver.refresh()


navegador

11.3. Tests usando Firefox


Para ejecutar los tests en el navegador Firefox hay que inicializar el WebDriver con una instancia
del Driver de Firefox. Puedes descargar el driver desde este enlace

Una vez descargado lo descomprimimos y lo movemos al directorio /usr/local/bin

$ tar xf geckodriver-v0.32.0-linux64.tar.gz
$ sudo mv geckodriver /usr/local/bin/

/selenium/ejemplos/navegadores/ejemplo_firefox.py

from selenium import webdriver

driver = webdriver.Firefox()
driver.get('http://www.google.es')

11.4. Tests usando Chrome


Para ejecutar los tests en el navegador Google Chrome, hay que inicializar el WebDriver con una
instancia del Driver de Chrome.

Como en el caso de Firefox, debemos descargar su Driver, siempre en la versión mas estable desde

142
este enlace

Debemos descomprimirlo y moverlo a /usr/local/bin

$ unzip chromedriver_linux64.zip
$ sudo mv chromedriver /usr/local/bin/

/selenium/ejemplos/navegadores/ejemplo_chrome.py

from selenium import webdriver

driver = webdriver.Chrome()
driver.get('http://www.google.es')

Es posible que pueda existir una incompatibilidad de versión entre chromedriver y


chrome. Para actualizar el navegador debemos ejecutar el comando


$ sudo yum update google-chrome

11.5. Tests usando Internet Explorer


Para ejecutar los tests en el navegador Internet Explorer, hay que inicializar el WebDriver con una
instancia del Driver de Internet Explorer.

/selenium/ejemplos/navegadores/ejemplo_explorer.py

from selenium import webdriver

driver = webdriver.Ie()
driver.get('http://www.google.es')

11.6. Clase TestCase


Para crear los tests tenemos que crear una clase que herede de TestCase a la cual le añadiremos un
método para cada test que vayamos a generar. Añadiremos un método setUp (se ejecutará antes de
cada test) que será donde vamos a inicializar los objetos necesarios para usar en los tests, cargar
datos… y un método tearDown (se ejecutará después de cada test) que será el que se encargue de
limpiar los valores que se han inicializado en el anterior método.

143
import unittest
from selenium import WebDriver

class Tests(unittest.TestCase):
  def setUp(self):
  # Inicializamos lo necesario para ejecutar los tests

  def test1(self):
  # Test 1

  def tearDown(self):
  x# Limpiamos

11.7. Herramientas
Podemos usar algunas herramientas para inspeccionar los elementos y poder elegir uno de los
métodos de busqueda que se van a ver a continuación para obtener los elementos necesarios para
automatizar las pruebas.

En Firefox podemos usar Firebug.

Firebug tiene opciones que nos permiten obtener la ruta CSS del elemento, o incluso el XPath.

144
En Google Chrome podemos usar las Chrome Dev Tools que funcionan de forma parecida a
Firebug.

11.8. Objeto WebElement


La clase WebElement nos permite interactuar con los elementos de la página web.

Table 3. Propiedades de la clase WebElement

Propiedad Descripción Ejemplo

size Devuelve el tamaño del element.size


elemento

tag_name Devuelve el nombre de la element.tag_name


etiqueta HTML del elemento

text Devuelve el texto del elemento element.text

Table 4. Métodos de la clase WebElement

Método Descripción Ejemplo

clear() Limpia el contenido de un element.clear()


elemento como una caja de
texto

145
Método Descripción Ejemplo

click() Hace click en un elemento element.click()

get_attribute(name) Devuelve el valor de un element.get_attribute('value')


atributo del elemento

is_displayed() Comprueba que el elemento es element.is_displayed()


visible para el usuario

is_enabled() Comprueba que el elemento element.is_enabled()


está habilitado

is_selected() Comprueba que el elemento element.is_selected()


(checkbox o radio button) está
seleccionado

send_keys(value) Escribe en el elemento element.send_keys('react')

submit() Envia un formulario element.submit()

value_of_css_property(property Obtiene el valor de una element.value_of_css_property('


_name) propiedad CSS background-color')

11.9. Aserciones
Las aserciones son unos métodos que nos proporciona la librería unittest y nos van a permitir
validar alguna condición para decidir si el test se pasa o no se pasa. En caso de que el test no se
pase, se mostrará un error en la consola y se parará la ejecución.

Table 5. Métodos de las Aserciones

Método Condición

assertEqual(a, b) a == b

assertNotEqual(a, b) a != b

assertTrue(x) bool(x) is True

assertFalse(x) bool(x) is False

assertIsNot(a, b) a is not b

assertGreater(a, b) a>b

assertGreaterEqual(a, b) a >= b

assertLess(a, b) a<b

assertLessEqual(a, b) a⇐b

assertListEqual(a, b) a == b

11.10. Encontrar elementos web


Para poder testear la aplicación y poder simular todas las acciones que realizaría un usuario
mientras está en el navegador, es necesario poder acceder a los distintos elementos de las páginas.

146
Puede que a veces sea complicado obtener algún elemento, pero siempre hay algún método para
conseguirlo.

Para acceder a los elementos de la página web lo vamos a hacer a través de los objetos de
WebDriver y WebElement. Vamos a usar los métodos:

• find_element(By.xxx)

• find_elements(By.xxx)

Estos métodos devuelven una instancia de WebElement en caso de que hayan encontrado el
elemento buscado. En caso de no hacerlo, el primero lanza una excepción NoSuchElementFound,
mientras que el segundo devuelve una lista vacía.

11.11. Métodos de busqueda de Elementos Web


A continuación se muestran los distintos métodos que se pueden usar para obtener los elementos
de la página web.

11.11.1. Id

Busca elementos por el id. Este método es el más útil porque el id no se debería repetir entre los
elementos de una página, por lo que siempre vamos a obtener el que necesitamos.

Vamos a instalar un servidor http en nuestra máquina:

$ sudo dnf install -y httpd

Tras la instalación creamos el siguiente archivo:

147
/var/www/html/index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-
scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
<div id="lista-productos">
  <ul>
  <li><input type="text" value="Producto 1" autofocus enabled /></li>
  <span>ooo</span>
  <li><input type="text" value="Producto 2" disabled /></li>
  <li><input type="text" value="Producto 3" enabled /></li>
  <li><input type="text" value="Producto 4" disabled /></li>
  </ul>
</div>
  <img src="" alt="Imagen">
  <img src="">
  <form id="login-form">
  <div>
  <label for="username">Username</label>
  <input type="text" id="username" class="login-u" name="user">
  </div>
  <div>
  <label for="password">Password</label>
  <input type="password" id="password" class="login-p" name="pass">
  </div>
  <input type="submit" name="login" id="btn-login" class="btn" value="Enviar">
  </form>
</body>
</html>

Una vez creado el archivo, habilitamos y levantamos el servicio

$ sudo systemctl enable httpd.service --now

Ahora vamos a usar los métodos antes mencionados para obtener los elementos de la página.

148
/selenium/ejemplos/buscar_elementos/find_by_id.py

from selenium import webdriver


from selenium.webdriver.common.by import By
import unittest

class FindElementId(unittest.TestCase):
  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testFindId(self):
  username = self.driver.find_element(By.ID, 'username')
  password = self.driver.find_element(By.ID,'password')

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

11.11.2. Name

Busca por la propiedad name de las etiquetas HTML.

Ahora vamos a usar los métodos antes mencionados para obtener los elementos de la página.

/selenium/ejemplos/buscar_elementos/find_by_name.py

from selenium import webdriver


from selenium.webdriver.common.by import By
import unittest

class FindElementName(unittest.TestCase):
  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testFindName(self):
  username = self.driver.find_element(By.NAME,'user')
  password = self.driver.find_element(By.NAME,'pass')

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

149
11.11.3. ClassName

Busca elementos por la clase. En caso de que haya varios elementos con la misma clase, tendremos
que especificar cual queremos.

Ahora vamos a usar los métodos antes mencionados para obtener los elementos de la página.

/selenium/ejemplos/buscar_elementos/find_by_class.py

from selenium import webdriver


from selenium.webdriver.common.by import By
import unittest

class FindElementClass(unittest.TestCase):
  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testFindClass(self):
  username = self.driver.find_element(By.CLASS_NAME,'login-u')
  password = self.driver.find_element(By.CLASS_NAME,'login-p')

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

11.11.4. TagName

Este busca el elemento por el nombre de la etiqueta HTML. En este caso seguro que tenemos que
especificar a que etiqueta nos referimos porque estará repetida por toda la página.

Ahora vamos a usar los métodos antes mencionados para obtener los elementos de la página. En
este caso como podemos recibir una lista de elementos, la podemos recorrer como se muestra en el
siguiente código:

150
/selenium/ejemplos/buscar_elementos/find_by_tag_name.py

from selenium import webdriver


from selenium.webdriver.common.by import By
import unittest

class TagName(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testTagName(self):
  labels = self.driver.find_elements(By.TAG_NAME,'label')
  for label in labels:
  self.assertIsNotNone(label)
  print(label.text)
  self.assertEqual(2, len(labels))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

WebElement también soporta el método find_element(By.xxx) por lo que podemos buscar entre los
hijos de un elemento web, filtrando los resultados.

151
/selenium/ejemplos/buscar_elementos/find_by_tag_name.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class TagName(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testTagName(self):
  labels = self.driver.find_elements(By.TAG_NAME,'label')
  for label in labels:
  self.assertIsNotNone(label)
  print(label.text)
  self.assertEqual(2, len(labels))

  form = self.driver.find_element(By.TAG_NAME,'form')
  username_label = form.find_element(By.TAG_NAME,'label')

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

11.11.5. LinkText

Busca los Links que contengan un texto dado.

152
/selenium/ejemplos/buscar_elementos/find_by_link_text.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class LinkText(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://www.google.com')

  def testLinkText(self):
  gmail_link = self.driver.find_element(By.LINK_TEXT,'Gmail')
  self.assertEqual('https://mail.google.com/mail/&ogbl', gmail_link
.get_attribute('href'))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

11.11.6. PartialLinkText

Busca los Links que contengan parte del texto dado. Se suele usar para aquellos enlaces que no son
fijos, sino que cambian el texto que muestran (por ejemplo un enlace que te muestre cuantas tareas
te quedan por hacer, 13 tareas).

153
/selenium/ejemplos/buscar_elementos/find_by_partial_link_text.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class PartialLinkText(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://www.google.com')

  def testPartialLinkText(self):
  gmail_link = self.driver.find_element(By.PARTIAL_LINK_TEXT,'Gma')
  self.assertEqual('https://mail.google.com/mail/&ogbl', gmail_link
.get_attribute('href'))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

11.11.7. XPath

Este es otro método avanzado que será el que vamos a usar cuando no nos sirve ninguno de los
anteriores. XPath permite construir expresiones que recorren y procesan un documento XML. La
mayor diferencia entre usar XPath y los usar los selectores CSS, es que XPath nos permite navegar
por el XML en todas las direcciones: de hijos a padres y de padres a hijos.

Terminología:

• Nodo: cada elemento del XML:

◦ <html>: nodo raíz.

◦ <div>: elemento nodo.

◦ id="identificador": atributo o valor de un nodo.

• Valor atómico: nodos sin hijos, ni padres (el texto de un nodo).

• Padres: cada elementos nodo tiene un nodo padre del que cuelga.

• Hijos: nodos que pertenecen al mismo nodo padre.

• Siblings: nodos que se encuentran al mismo nivel (hermanos).

XPath tiene las siguientes expresiones que nos permiten acceder a los elementos buscados:

• nodename: selecciona todos los nodos con el que coinciden con el nombre dado.

• /: si aparece el primero indica la ruta absoluta(/html), mientras que si aparece en el medio

154
indica la relación entre padres e hijos (html/body/table).

• //: indica la ruta relativa al nodo. //table (todos los elementos table), //a//img (todos los elementos
img dentro de un elemento a).

• .: representa el nodo de la posición actual.

• ..: representa el nodo padre de la posición actual.

• @: se usa para indicar que tiene que tener un atributo, //img/@alt (todos los elementos img que
tengan el atributo alt).

Con la ruta absoluta indicamos la ruta completa hasta acceder al nodo que queremos, pero esto
tiene una desventaja, y es que si el nodo cambia de lugar, no lo va a encontrar.

/selenium/ejemplos/buscar_elementos/find_by_xpath_abs.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class XPath(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testXPathAbsolute(self):
  submit_button = self.driver.find_element(By.XPATH,'/html/body/form/input')
  self.assertEqual('Enviar', submit_button.get_attribute('value'))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

Con la ruta relativa podemos acceder a un elemento independientemente de donde se encuentre


dentro del DOM.

155
/selenium/ejemplos/buscar_elementos/find_by_xpath_rel.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class XPath(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('https://es.wikipedia.org')

  def testXPathRelative(self):
  logo = self.driver.find_element(By.XPATH,"//*[@id='p-logo']")
  self.assertIsNotNone(logo)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

XPath puede usar también predicados (expresiones de filtro).

/selenium/ejemplos/buscar_elementos/find_by_xpath_attrs.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class XPath(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testXPathWithAttr(self):
  password = self.driver.find_element(By.XPATH,
"/html/body/form/div[2]/input[@class='login-p']")
  self.assertIsNotNone(password)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

Con XPath podemos usar funciones para refinar las busquedas. Algunas de estas funciones son las

156
siguientes:

Table 6. Funciones XPath

Función Ejemplo Descripción

starts-with() input[starts-with(@id, 'ctrl')] Busca elementos input cuyo id


empiece por ctrl.

ends-with() input[ends-with(@id, 'product')] Busca elementos input cuyo id


termine por product.

contains() input[contains(@id, 'product')] Busca elementos input cuyo id


contiene product.

not() button[not(@disabled)] Busca elementos button que no


están deshabilitados.

count() num=count(div) Devuelve el número de


elementos input que hay dentro
del elemento div.

round() num=round(price) Redondea el valor del elemento


price al entero más cercano.

floor() num=floor(price) Redondea a la baja el valor del


elemento price. mencionados
antes.

last() num=last(listItems) Devuelve la posición del úlitmo


elemento de la lista listItems.

157
/selenium/ejemplos/buscar_elementos/find_by_xpath_functions.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class XPath(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testXPathWithFunctions(self):
  btn1 = self.driver.find_element(By.XPATH,"//input[@type='submit' and
@value='Enviar']")
  btn2 = self.driver.find_element(By.XPATH,"//input[@type='submit' or
@value='Enviar']")
  images_with_alt = self.driver.find_elements(By.XPATH,'//img[not(@alt)]')

  self.assertEqual('Enviar', btn1.get_attribute('value'))
  self.assertEqual('Enviar', btn2.get_attribute('value'))
  self.assertEqual(1, len(images_with_alt))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

Usando un valor cualquiera para obtener un elemento.

158
/selenium/ejemplos/buscar_elementos/find_by_xpath_any_attr.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class XPath(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testWithAnyAttr(self):
  username_input = self.driver.find_element(By.XPATH,"//input[@*='user']")
  self.assertIsNotNone(username_input)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

Table 7. Obtener un valor determinado XPath

Expresión Descripción

/table/tr[1] Obtiene el primer elemento tr hijo del elemento


table.

/table/tr[last()] Obtiene el último elemento tr hijo del elemento


table.

/table/tr[last() - 1] Obtiene el penúltimo elemento tr hijo del


elemento table.

/table/tr[position() > 4] Obtiene los elementos tr hijos del elemento table


y cuya posición sea mayor que 4.

//tr[td > 40] Obtiene todos los elementos tr que tienen un


hijo td cuyo valor es mayor de 40.

11.11.8. Selectores CSS

CSS es un lenguaje de estilos que sirve para modificar la apariencia de los elementos HTML o XML.
Este es un método avanzado porque podemos buscar un elemento por los selectores que se han
usado para darle los estilos.

• Ruta absoluta:

159
/selenium/ejemplos/buscar_elementos/find_by_css_selectors_abs.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class CssSelectors(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testCssSelectorsAbsolute(self):
  username_label_1 = self.driver.find_element(By.CSS_SELECTOR,"html body form
div label")
  username_label_2 = self.driver.find_element(By.CSS_SELECTOR,"html > body >
form > div > label")
  self.assertEqual('Username', username_label_1.text)
  self.assertEqual('Username', username_label_2.text)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

• Ruta relativa (devuelve el primer elemento):

/selenium/ejemplos/buscar_elementos/find_by_css_selectors_rel.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class CssSelectors(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testCssSelectorsRelative(self):
  username_label = self.driver.find_element(By.CSS_SELECTOR,"label")
  self.assertEqual('Username', username_label.text)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

160
• Usando la clase y el id:

/selenium/ejemplos/buscar_elementos/find_by_css_selectors_id_and_class.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class CssSelectors(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testCssSelectorsIdAndClass(self):
  username_input = self.driver.find_element(By.CSS_SELECTOR,"input.login-u")
  password_input_1 = self.driver.find_element(By.CSS_SELECTOR,"input#password")
  password_input_2 = self.driver.find_element(By.CSS_SELECTOR,"#password")

  self.assertIsNotNone(username_input)
  self.assertIsNotNone(password_input_1)
  self.assertIsNotNone(password_input_2)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

• Otros atributos:

161
/selenium/ejemplos/buscar_elementos/find_by_css_selectors_attrs.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class CssSelectors(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testCssSelectorsAttrs(self):
  username_input = self.driver.find_element(By.CSS_SELECTOR,"input[name='user']
")
  img = self.driver.find_element(By.CSS_SELECTOR,"img[alt='Imagen']")

  self.assertIsNotNone(username_input)
  self.assertIsNotNone(img)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

• Expresiones regulares en atributos:

Table 8. Expresiones regulares

Sintáxis Ejemplo Descripción

^= input[id^='ctrl'] Obtiene el elemento input cuyo


id empieza por ctrl.

$= input[id$='product'] Obtiene el elemento input cuyo


id termina por product.

*= input[id*='user'] Obtiene el elemento input cuyo


id contiene user.

Table 9. Búsquedas de hijos

Pseudo-Clases Ejemplo Descripción

:first-child form#loginForm:first-child Obtiene el primer elemento hijo


del formulario con id
loginForm.

:last-child form#loginForm:last-child Obtiene el último elemento hijo


del formulario con id
loginForm.

162
Pseudo-Clases Ejemplo Descripción

:nth-child form#loginForm:nth-child(1) Obtiene el elemento hijo que


está en la posición 1 del
formulario con id loginForm.

/selenium/ejemplos/buscar_elementos/find_by_css_selectors_pseudo_classes.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class CssSelectors(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testCssSelectorsPseudoClasses(self):
  username_label = self.driver.find_element(By.CSS_SELECTOR,"form#login-form >
div :first-child")
  password_label = self.driver.find_element(By.CSS_SELECTOR,"form#login-form >
div:nth-child(2) :first-child")

  self.assertIsNotNone(username_label)
  self.assertIsNotNone(password_label)
  self.assertEqual('Username', username_label.text)
  self.assertEqual('Password', password_label.text)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

• Selectores CSS para búsquedas de hermanos

Table 10. Búsquedas de hermanos

Sintáxis Ejemplo Descripción

p+p div#top10 > p + p Obtiene el elemento p que se


encuentra justo al lado del
primer elemento p (el top2).

p+*+p div#top10 > p + * + p Obtiene el segundo elemento p


hermano del primer elemento p
(el top3).

163
/selenium/ejemplos/buscar_elementos/find_by_css_selectors_siblings.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class CssSelectors(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testCssSelectorsSiblings(self):
  list_item_2 = self.driver.find_element(By.CSS_SELECTOR,"div#lista-productos >
ul > li + li > input")

  self.assertIsNotNone(list_item_2)
  self.assertEqual('Producto 2', list_item_2.get_attribute('value'))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

• Selectores CSS con pseudo-clases

Table 11. Expresiones regulares

Pseudo-Clases Ejemplo Descripción

:enabled input:enabled Obtiene los elementos input que


están habilitados.

:disabled input:disabled Obtiene los elementos input que


están deshabilitados.

:checked input:checked Obtiene los elementos input que


están marcados.

164
/selenium/ejemplos/buscar_elementos/find_by_css_selectors_pseudo_classes.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class CssSelectors(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testCssSelectorsPseudoClasses(self):
  input_focus = self.driver.find_element(By.CSS_SELECTOR,"input:focus")

  self.assertIsNotNone(input_focus)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

11.11.9. jQuery

También es posible usar la API de jQuery con Selenium para buscar los elementos. Para ello, solo
hay que crear una instancia de JavaScript del WebDriver y con ella usar el método
execute_script(código) para inyectar ese código JS.

165
/selenium/ejemplos/buscar_elementos/find_by_jquery.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class JQuery(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get(
'http://es.wikipedia.org/w/index.php?title=Especial:Buscar&search=&fulltext=Buscar&pro
file=advanced')

  def testJQuery(self):
  checked = self.driver.execute_script("return jQuery.find(':checked')")
  not_checked = self.driver.execute_script("return
jQuery.find('input:checkbox:not(:checked)')")
  self.assertEqual(3, len(checked))
  self.assertEqual(27, len(not_checked))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

En caso de testear aplicaciones sobre las que no tenemos ningún control, tendremos que apañarnos
con estos métodos para obtener los elementos que necesitamos, mientras que si tenemos acceso al
código de la aplicación que vamos a testear y podemos modificarla, podemos aprovechar a añadir
ids a todos aquellos elementos a los que se les pueda poner para poder automatizar los tests de una
forma más sencilla y rápida.

166
11.12. Lab: Python Webdriver
11.12.1. Ejercicio 1

• Navegar a 'http://es.wikipedia.org

• Obtener los enlaces que hay dentro del elemento con id 'p-navigation'

• Comprobar que los enlaces son los correctos

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio1(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org
  self.driver.get('http://es.wikipedia.org')

  def testCorrectLinks(self):
  # Obtener los enlaces que hay dentro del elemento con id 'p-navigation'
  portada = self.driver.find_element(By.ID,'n-mainpage-description')
  portal = self.driver.find_element(By.ID,'n-portal')
  ayuda = self.driver.find_element(By.ID,'n-help')
  donaciones = self.driver.find_element(By.ID,'n-sitesupport')

  # Comprobar que los enlaces son los correctos


  self.assertEqual('Portada', portada.text)
  self.assertEqual('Portal de la comunidad', portal.text)
  self.assertEqual('Ayuda', ayuda.text)
  self.assertEqual('Donaciones', donaciones.text)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

11.12.2. Ejercicio 2

• Navegar a 'http://es.wikipedia.org

• Obtener los enlaces que hay dentro del elemento con id 'p-navigation'

• Comprobar que se obtiene el número correcto de enlaces

167
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio2(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org
  self.driver.get('http://es.wikipedia.org')

  def testCorrectNumberOfLinks(self):
  # Obtener los enlaces que hay dentro del elemento con id 'p-navigation'
  navigation_div = self.driver.find_element(By.ID,'p-navigation')
  body = navigation_div.find_element(By.CLASS_NAME,'body')
  links = body.find_elements(By.TAG_NAME,'a')

  # Comprobar que se obtiene el número correcto de enlaces


  for link in links:
  self.assertNotEqual(None, link)
  self.assertEqual(9, len(links))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

11.12.3. Ejercicio 3

• Navegar a 'http://es.wikipedia.org

• Encuentra todos los links que hay en el div con id 'p-personal' y comprueba que existen

• Encuentra algún link que contenga texto dinámico y comprueba que existe

168
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio3(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org
  self.driver.get('http://es.wikipedia.org')

  def testCorrectLinks(self):
  # Encuentra todos los links que hay en el div con id 'p-personal' y comprueba que existen
  discussion = self.driver.find_element(By.LINK_TEXT,'Discusión')
  contributions = self.driver.find_element(By.LINK_TEXT,'Contribuciones')
  account = self.driver.find_element(By.LINK_TEXT,'Crear una cuenta')
  access = self.driver.find_element(By.LINK_TEXT,'Acceder')
  self.assertIsNotNone(discussion)
  self.assertIsNotNone(contributions)
  self.assertIsNotNone(account)
  self.assertIsNotNone(access)

  # Encuentra algún link que contenga texto dinámico y comprueba que existe
  month = self.driver.find_element(By.PARTIAL_LINK_TEXT,'enero')
  self.assertIsNotNone(month)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

11.12.4. Ejercicio 4

• Navegar a 'http://es.wikipedia.org

• Encuentra los títulos de todos los 'mw-panel' usando un XPATH absoluto

• Encuentra los títulos de todos los 'mw-panel' usando un XPATH relativo

169
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio4(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org
  self.driver.get('http://es.wikipedia.org')

  def testFindTitlesWithXPath(self):
  # Encuentra los títulos de todos los 'mw-panel' usando un XPATH absoluto
  print = self.driver.find_element(By.XPATH,'/html/body/div[4]/div[2]/div[3]/h3')
  other_projects = self.driver.find_element(By.XPATH,'/html/body/div[4]/div[2]/div[4]/h3')
  tools = self.driver.find_element(By.XPATH,'/html/body/div[4]/div[2]/div[5]/h3')
  languages = self.driver.find_element(By.XPATH,'/html/body/div[4]/div[2]/div[6]/h3')
  self.assertIsNotNone(print)
  self.assertIsNotNone(other_projects)
  self.assertIsNotNone(tools)
  self.assertIsNotNone(languages)

  # Encuentra los títulos de todos los 'mw-panel' usando un XPATH relativo


  print = self.driver.find_element(By.XPATH,"//*[@id='p-coll-print_export-label']")
  other_projects = self.driver.find_element(By.XPATH,"//*[@id='p-wikibase-otherprojects-label']")
  tools= self.driver.find_element(By.XPATH,"//*[@id='p-tb-label']")
  languages = self.driver.find_element(By.XPATH,"//*[@id='p-lang-label']")
  self.assertIsNotNone(print)
  self.assertIsNotNone(other_projects)
  self.assertIsNotNone(tools)
  self.assertIsNotNone(languages)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

11.12.5. Ejercicio 5

• Navegar a 'http://www.w3schools.com/html/html_tables.asp'

• Encontrar la primera tabla de la página

• Comprueba que la tabla existe

• Comprueba que tiene el número de filas correcto

• Comprueba que la última fila tiene el número de celdas correcto

• Comprueba que después de la quinta fila, hay dos filas más

• Comprueba que todas las celdas tienen contenido

170
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio5(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://www.w3schools.com/html/html_tables.asp'
  self.driver.get('http://www.w3schools.com/html/html_tables.asp')

  def testTable(self):
  # Encontrar la primera tabla de la página
  table = self.driver.find_element(By.XPATH,"//*[@id='customers']")
  # Comprueba que la tabla existe
  self.assertIsNotNone(table)
  # Comprueba que tiene el número de filas correcto
  rows = table.find_elements(By.TAG_NAME,'tr')
  self.assertEqual(7, len(rows))
  # Comprueba que la última fila tiene el número de celdas correcto
  lastRowCells = self.driver.find_elements(By.XPATH,"//*[@id='customers']/tbody/tr[last()]/td")
  self.assertEqual(3, len(lastRowCells))
  # Comprueba que después de la quinta fila, hay dos filas más
  last2Rows = self.driver.find_elements(By.XPATH,"//*[@id='customers']/tbody/tr[position() > 5]")
  self.assertEqual(2, len(last2Rows))
  # Comprueba que todas las celdas tienen contenido
  cells = table.find_elements(By.TAG_NAME,'td')
  for cell in cells:
  self.assertFalse(len(cell.text) == 0)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

11.12.6. Ejercicio 6

• Navegar a
'http://es.wikipedia.org/w/index.php?title=Especial:Buscar&search=&fulltext=Buscar&profile=ad
vanced'

• Encuentra los checkboxes de la página

• Comprueba que hay 30 checkboxes

• Encuentra el campo de texto para realizar búsquedas y el botón de buscar

• Comprueba que existen tanto el campo de búsqueda como el botón

• Selectores CSS para búsquedas de hijos

171
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio6(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org/w/index.php?title=Especial:Buscar&search=&fulltext=Buscar&profile=advanced'
  self.driver.get(
'http://es.wikipedia.org/w/index.php?title=Especial:Buscar&search=&fulltext=Buscar&profile=advanced')

  def testCheckboxes(self):
  # Encuentra los checkboxes de la página
  checkboxes = self.driver.find_elements(By.CSS_SELECTOR,"#mw-searchoptions input[name^='ns']")

  # Comprueba que hay 30 checkboxes


  self.assertEqual(30, len(checkboxes))

  # Encuentra el campo de texto para realizar búsquedas y el botón de buscar


  search_box = self.driver.find_element(By.CSS_SELECTOR,'#searchText .oo-ui-inputWidget-input')
  search_button = self.driver.find_element(By.CSS_SELECTOR,"button[type='submit']")

  # Comprueba que existen tanto el campo de búsqueda como el botón


  self.assertIsNotNone(search_box)
  self.assertIsNotNone(search_button)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

172
Capítulo 12. Interacciones
Ahora vamos a ver como podemos interactuar con los diferentes elementos de la Web como pueden
ser las cajas de texto, los checkboxes, los desplegables…

12.1. Cajas de texto


Para limpiar el texto usamos la función clear() del WebElement. Para introducir texto hay que usar
el método send_keys(). Para enviar el formulario se usar submit().

/selenium/ejemplos/interactions/text_boxes.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class TextBoxesInteractions(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://www.google.com')

  def testGoogleSearch(self):
  input = self.driver.find_element(By.NAME,"q")
  input.clear()
  input.send_keys('Angular')
  input.clear()
  input.send_keys('React')
  input.submit()

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

También se puede buscar el botón de enviar y hacer click() sobre él.

173
/selenium/ejemplos/interactions/text_boxes.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class TextBoxesInteractions(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://www.google.com')

  def testGoogleSearch(self):
  input = self.driver.find_element(By.NAME,"q")
  input.clear()
  input.send_keys('Angular')
  input.clear()
  input.send_keys('React')

  button = self.driver.find_element(By.XPATH,
"/html/body/div[1]/div[3]/form/div[1]/div[1]/div[4]/center/input[1]")
  button.click()

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

Con text podemos obtener el texto de cualquier elemento web.

/index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-
scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div>
  <input type="text" id="message" value="Hola a todos!" align="justify" />
  </div>
</body>
</html>

174
/selenium/ejemplos/interactions/interactions_get_text.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class TextBoxesInteractions(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testGetTextFromInput(self):
  input = self.driver.find_element(By.ID,'message')
  message = input.get_attribute("value")
  print(message)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

Podemos recoger cualquier atributo de los elementos a través del método get_attribute(). Y si
queremos obtener el valor de una propiedad CSS del elemento usamos value_of_css_property().

/styles.css

input {
  color: rgba(0, 120, 120, 0.5);
  width: 150px;
}

175
/selenium/ejemplos/interactions/interactions_attr_and_props_css.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class AttributtesAndPropertiesCss(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testGetAttrAndPropCss(self):
  input = self.driver.find_element(By.ID,'message')
  self.assertEqual('rgba(0, 120, 120, 0.5)', input.value_of_css_property('color
'))
  self.assertEqual('justify', input.get_attribute('align'))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

12.2. Desplegables
Selenium WebDriver nos proporciona una manera para poder trabajar con las listas desplegables
creadas como elementos HTML <select>. Para ello tiene la clase Select con la que podemos
interactuar directamente con estos elementos.

Table 12. Métodos de los Desplegables

Función Descripción

select_by_visible_text(text) Selecciona la opción cuyo texto es text.

select_by_value(value) Selecciona la opción cuya propiedad value es


value.

select_by_index(index) Selecciona la opción cuyo índice es index.

options Devuelve todas las opciones

is_multiple Indica si el desplegable permite la selección


múltiple

first_selected_option Devuelve la opción que está seleccionada

Si solo podemos seleccionar un elemento del desplegables:

176
/index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-
scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="car-list">
  <select name="cars" id="car-options">
  <option value="bmw">BMW</option>
  <option value="tesla">Tesla</option>
  <option value="audi">Audi</option>
  <option value="mercedes">Mercedes</option>
  </select>
  </div>
</body>
</html>

177
/selenium/ejemplos/interactions/interactions_dropdowns.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select

class DropdownInteractions(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testDropdowns(self):
  cars = Select(self.driver.find_element(By.NAME,"cars"))
  self.assertFalse(cars.is_multiple)
  self.assertEqual(4, len(cars.options))

  cars.select_by_visible_text('Tesla')
  self.assertEqual('Tesla', cars.first_selected_option.text)

  cars.select_by_value('audi')
  self.assertEqual('Audi', cars.first_selected_option.text)

  cars.select_by_index(0)
  self.assertEqual('BMW', cars.first_selected_option.text)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

Si podemos seleccionar varios elementos del desplegables:

Table 13. Métodos de los Desplegables con opciones múltiples

Función Descripción

deselect_by_visible_text(text) Deselecciona la opción cuyo texto es text.

deselect_by_value(value) Deselecciona la opción cuya propiedad value es


value.

deselect_by_index(index) Deselecciona la opción cuyo índice es index.

all_selected_options Devuelve todas las opciones seleccionadas

178
/index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-
scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="color-list">
  <select name="colors" id="color-options" multiple>
  <option value="red">Red</option>
  <option value="yellow">Yellow</option>
  <option value="blue">Blue</option>
  <option value="black">Black</option>
  </select>
  </div>
</body>
</html>

179
/selenium/ejemplos/interactions/interactions_dropdowns_multiple.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select

class DropdownInteractions(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testDropdownsMultipleOptions(self):
  colors = Select(self.driver.find_element(By.NAME,"colors"))
  self.assertTrue(colors.is_multiple)
  self.assertEqual(4, len(colors.options))

  colors.select_by_visible_text('Yellow')
  colors.select_by_value('red')
  colors.select_by_index(3)
  self.assertEqual(3, len(colors.all_selected_options))

  colors.deselect_by_index(0)
  self.assertEqual(2, len(colors.all_selected_options))

  colors.deselect_by_value('yellow')
  self.assertEqual(1, len(colors.all_selected_options))

  colors.deselect_by_visible_text('Black')
  self.assertEqual(0, len(colors.all_selected_options))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

Cuando seleccionamos/deseleccionamos por índice tenemos que tener cuidado porque puede que
los elementos sean dinámicos y obtengamos fallos inesperados al ejecutar los tests.

También podemos testear si todas las opciones del desplegable son las que esperamos.

180
/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="styles.css">
  <title>Document</title>
</head>
<body>
  <div id="month-list">
  <select name="months" id="month-options" multiple>
  <option value="1">January</option>
  <option value="2">February</option>
  <option value="3">March</option>
  <option value="4">April</option>
  <option value="5">May</option>
  <option value="6">June</option>
  </select>
  </div>
</body>
</html>

181
/src/test/java/com/selenium/examples/Interactions.java

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select

class DropdownListInteractions(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testDropdownsOptions(self):
  expected_months = ["January", "February", "March", "April", "May", "June"]
  select_months = Select(self.driver.find_element(By.CSS_SELECTOR,"#month-
options"))
  months = [month.text for month in select_months.options]
  self.assertListEqual(expected_months, months)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

12.3. Radio Buttons


Selenium permite automatizar los radio buttons a través de los métodos del WebElement click()
para seleccionarlos y is_selected() para comprobar si está seleccionados o no.

182
/index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-
scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="working-radios">
  <input type="radio" id="opt1" name="working" value="Yes">
  <label for="opt1">Yes</label>
  <input type="radio" id="opt2" name="working" value="No" checked>
  <label for="opt2">No</label>
  </div>
</body>
</html>

/selenium/ejemplos/interactions/interactions_radio_buttons.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class RadioButtonInteractions(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testRadioButtons(self):
  working = self.driver.find_element(By.XPATH,"//input[@type='radio' and
@value='Yes']")

  if not working.is_selected():
  working.click()

  self.assertTrue(working.is_selected())

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

183
12.4. Checkbox
De la misma manera que utilizamos los radio buttons podemos usar los elementos de checkbox de
una página.

/index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-
scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="hobbie-list">
  <input type="checkbox" id="cine" name="hobbies">
  <label for="cine">Cine</label>
  <input type="checkbox" id="deportes" name="hobbies">
  <label for="deportes">Deportes</label>
  <input type="checkbox" id="musica" name="hobbies">
  <label for="musica">Musica</label>
  </div>
</body>
</html>

184
/selenium/ejemplos/interactions/interactions_checkboxes.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class CheckboxesInteractions(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testCheckboxes(self):
  cine = self.driver.find_element(By.XPATH,"//input[@id='cine']")

  if not cine.is_selected():
  cine.click()

  self.assertTrue(cine.is_selected())

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

12.5. Alerts
WebDriver nos da acceso a los elementos Alerts que se generan mediante código JavaScript para
mostrar mensajes de error o warnings.

Con Selenium podemos acceder a la siguientes propiedades de los Alerts.

Table 14. Propiedades de Alert

Función Descripción

text Obtiene el texto que se muestra en el alert.

Y podemos usar los siguientes métodos para interactuar con estos pop-ups.

Table 15. Métodos de Alert

Función Descripción

accept() Pulsa el botón de aceptar del alert.

dismiss() Pulsa el botón de cancelar del alert.

send_keys() Escribe en el elemento.

185
/index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-
scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <button type="button" id="show-alert">Pulsa para mostrar alert</button>
</body>
<script>
  window.onload = () => {
  var btn = document.getElementById('show-alert');
  btn.addEventListener('click', () => {
  alert('Has pulsado el botón...');
  });
  }
</script>
</html>

186
/selenium/ejemplos/interactions/interactions_alerts.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class AlertsInteractions(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testAlerts(self):
  btn = self.driver.find_element(By.ID,'show-alert')
  btn.click()

  alert = self.driver.switch_to.alert
  self.assertEqual('Has pulsado el botón...', alert.text)
  alert.accept()

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

187
12.6. Lab: Interacciones
12.6.1. Ejercicio 7

• Navegar a 'http://es.wikipedia.org/wiki/Wikipedia:Portada'

• Encuentra el elemento input

• Vacía su contenido y busca 'Selenium'

• Envia el formulario y pulsa el botón

• Comprobar que vamos a la página correcta

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio7(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org/wiki/Wikipedia:Portada'
  self.driver.get('http://es.wikipedia.org/wiki/Wikipedia:Portada')

  def testCheckboxes(self):
  # Encuentra el elemento input
  search_input = self.driver.find_element(By.CSS_SELECTOR,"#searchInput")

  # Vacía su contenido y busca 'Selenium'


  search_input.clear()
  search_input.send_keys('Selenium')
  # search_input.submit()
  search_button = self.driver.find_element(By.CSS_SELECTOR,'#searchButton')
  search_button.click()

  # Comprobar que vamos a la página correcta


  self.assertEqual('Selenium - Wikipedia, la enciclopedia libre', self.driver.title)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

12.6.2. Ejercicio 8

• Navegar a 'http://es.wikipedia.org/wiki/Wikipedia:Portada'

• Encuentra el elemento input

• Comprobar que el placeholder del input es correcto

188
• Comprobar que el tamaño de la letra del input es la correcta

• Buscar 'Selenium' en la Wikipedia

• Comprobar que vamos a la página correcta

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio8(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org/wiki/Wikipedia:Portada'
  self.driver.get('http://es.wikipedia.org/wiki/Wikipedia:Portada')

  def testCheckboxes(self):
  # Encuentra el elemento input
  search_input = self.driver.find_element(By.CSS_SELECTOR,"#searchInput")

  # Comprobar que el placeholder del input es correcto


  place_holder = search_input.get_attribute('placeholder')
  self.assertEqual('Buscar en Wikipedia', place_holder)

  # Comprobar que el tamaño de la letra del input es la correcta


  font_size = search_input.value_of_css_property('font-size')
  self.assertEqual('13px', font_size)

  # Buscar 'Selenium' en la Wikipedia


  search_input.clear()
  search_input.send_keys('Selenium')
  # searchInput.submit()
  search_button = self.driver.find_element(By.CSS_SELECTOR,'#searchButton')
  search_button.click()

  # Comprobar que vamos a la página correcta


  self.assertEqual('Selenium - Wikipedia, la enciclopedia libre', self.driver.title)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

12.6.3. Ejercicio 9

• Navegar a 'http://es.wikipedia.org/wiki/Especial:Contribuciones'

• Encuentra el desplegable

• Comprobar si permite seleccionar varias opciones o no

189
• Comprobar que el número de opciones es el correcto

• Seleccionar del desplegable la opción de 'Usuario' y comprobar que está seleccionada

• Seleccionar del desplegable la opción de 'MediaWiki' y comprobar que está seleccionada

• Seleccionar del desplegable la opción de 'Ayuda' y comprobar que está seleccionada

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select

class Ejercicio9(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org/wiki/Especial:Contribuciones'
  self.driver.get('http://es.wikipedia.org/wiki/Especial:Contribuciones')

  def testCheckboxes(self):
  # Encuentra el desplegable
  select = Select(self.driver.find_element(By.CSS_SELECTOR,"#namespace"))

  # Comprobar si permite seleccionar varias opciones o no


  self.assertFalse(select.is_multiple)

  # Comprobar que el número de opciones es el correcto


  self.assertEqual(32, len(select.options))

  # Seleccionar del desplegable la opción de 'Usuario' y comprobar que está seleccionada


  select.select_by_visible_text('Usuario')
  self.assertEqual('Usuario', select.first_selected_option.text)

  # Seleccionar del desplegable la opción de 'MediaWiki' y comprobar que está seleccionada


  select.select_by_value('8')
  self.assertEqual('MediaWiki', select.first_selected_option.text)

  # Seleccionar del desplegable la opción de 'Usuario' y comprobar que está seleccionada


  select.select_by_index(13)
  self.assertEqual('Ayuda', select.first_selected_option.text)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

12.6.4. Ejercicio 10

• Navegar a 'http://es.wikipedia.org/wiki/Especial:Contribuciones'

• Obtener los radio buttons

190
• Comprobar que uno está seleccionado y el otro no

• Seleccionar el que no está seleccionado

• Comprobar que el que no estaba seleccionado ahora lo está, y el otro no lo está

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio10(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org/wiki/Especial:Contribuciones'
  self.driver.get('http://es.wikipedia.org/wiki/Especial:Contribuciones')

  def testRadioButtons(self):
  # Obtener los radio buttons
  newbie = self.driver.find_element(By.CSS_SELECTOR,"#newbie")
  user = self.driver.find_element(By.CSS_SELECTOR,"#user")

  # Comprobar que uno está seleccionado y el otro no


  self.assertFalse(newbie.is_selected())
  self.assertTrue(user.is_selected())

  # Seleccionar el que no está seleccionado


  newbie.click()

  # Comprobar que el que no estaba seleccionado ahora lo está, y el otro no lo está


  self.assertTrue(newbie.is_selected())
  self.assertFalse(user.is_selected())

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

12.6.5. Ejercicio 11

• Navegar a
'http://es.wikipedia.org/w/index.php?title=Especial:Buscar&search=&fulltext=Buscar&profile=ad
vanced'

• Obtener el primer checkbox seleccionado

• Comprobar que el está seleccionado

• Obtener todos los checkboxes

• Seleccionar todos los checkboxes que no lo esten

191
• Comprobar que todos los checkboxes están seleccionados

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio11(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org/w/index.php?title=Especial:Buscar&search=&fulltext=Buscar&profile=advanced'
  self.driver.get(
'http://es.wikipedia.org/w/index.php?title=Especial:Buscar&search=&fulltext=Buscar&profile=advanced')

  def testCheckboxes(self):
  # Obtener el primer checkbox seleccionado
  check1 = self.driver.find_element(By.CSS_SELECTOR,"#mw-search-ns0")

  # Comprobar que el está seleccionado


  self.assertTrue(check1.is_selected())

  # Obtener todos los checkboxes


  checkboxes = self.driver.find_elements_by_css_selector("#mw-searchoptions input[name^='ns']")

  # Seleccionar todos los checkboxes que no lo esten


  for check in checkboxes:
  if not check.is_selected():
  check.click()

  # Comprobar que todos los checkboxes están seleccionados


  for check in checkboxes:
  self.assertTrue(check.is_selected())

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

192
Capítulo 13. Selenium API
Es una buena práctica detectar la presencia de un elemento dentro de una web antes de realizar
alguna acción sobre él. Para ellos vamos a escribir un método muy simple que capture la excepción
NoSuchElementException si no encuentra el elemento.

13.1. Comprobar el estado de los WebElement


Muchas veces un test falla porque intentamos interactuar con un elemento no visible de la página o
que se encuentra deshabilitado. Para corregir esto, Selenium nos permite detectar el estado del
elemento web mediante los siguientes métodos:

Table 16. Estado WebElement

Función Descripción

is_enabled() Este método comprueba si el elemento está


habilitado (devuelve true) o si no lo está
(devuelve false).

is_selected() Este método comprueba si un elemento está


seleccionado (devuelve true) o si no lo está
(devuelve false).

is_displayed() Este método comprueba si un elemento se


encuentra visible (devuelve true) o si no lo está
(devuelve false).

13.2. Doble click

193
/selenium/ejemplos/selenium_api/double_click.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains

class DoubleClick(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://cookbook.seleniumacademy.com/DoubleClickDemo.html')

  def testDoubleClick(self):
  message = self.driver.find_element(By.ID,'message')

  self.assertEqual("rgba(0, 0, 255,0)", message.value_of_css_property(


'background-color'))

  ActionChains(self.driver).double_click(message).perform()

  self.assertEqual("rgba(255, 255, 0, 1)", message.value_of_css_property(


'background-color'))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

13.3. Drag and Drop


Utilizamos la función drag_and_drop de la clase ActionChains a la que le pasaremos como
parámetros el origen y el destino, los cuales deben de ser WebElements.

194
/selenium/ejemplos/selenium_api/drag_and_drop.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains

class DragAndDrop(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://cookbook.seleniumacademy.com/DragDropDemo.html')

  def testDragAndDrop(self):
  source = self.driver.find_element(By.ID,'draggable')
  target = self.driver.find_element(By.ID,'droppable')

  ActionChains(self.driver).drag_and_drop(source, target).perform()

  self.assertEqual("Dropped!", target.text)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

13.4. JavaScript
Podemos ejecutar código JavaScript igual que lo haciamos cuando buscábamos elementos con
jQuery, usando el método execute_script().

195
/selenium/ejemplos/selenium_api/javascript.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains

class JavaScript(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://www.google.com')

  def testJavaScript(self):
  title = self.driver.execute_script("return document.title")
  self.assertEqual('Google', title)

  num_links = self.driver.execute_script("var links =


document.getElementsByTagName('a'); return links.length")
  self.assertEqual(43, num_links)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

13.5. ScreenShots
También es posible sacar capturas de pantalla de la página web usando save_screenshot(). Esto nos
permite ver en que estado se encontraba la página cuando ha fallado un test.

196
/selenium/ejemplos/selenium_api/screenshots.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains

class Screenshots(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://www.google.com')

  def testScreenshots(self):
  self.driver.save_screenshot('screenshot.png')

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

13.6. Manejo de cookies


Selenium nos permite obtener, añadir y borrar cookies a través de los siguientes métodos:

Table 17. Métodos para manejar las cookies

Función Descripción

add_cookie(cookie) Este método añade una cookie.

get_cookie(name) Este método devuelve la cookie con el nombre


dado.

get_cookies() Este método devuelve todas las cookies del


actual dominio.

delete_cookie(name) Este método elimina la cookie.

delete_all_cookies() Este método elimina todas las cookies del


dominio actual.

13.7. Eventos del WebDriver


Selenium nos proporciona la clase EventFiringWebDriver que escucha eventos durante la
ejecución del test, por ejemplo cuando se pulsa un link, se busca un elemento o algún valor cambia.

Table 18. Eventos de WebDriver

197
Función Descripción

before_navigate_to Este método se llama antes que el método


get(url).

after_navigate_to Este método se llama despues que el método


get(url).

before_find Este método se llama antes que los métodos


find_element(By.xxx) y find_elements(By.xxx).

after_find Este método se llama despues que los métodos


find_element(By.xxx) y find_elements(By.xxx).

before_change_value_of Este método se llama antes que los métodos


clear() y send_keys().

after_change_value_of Este método se llama despues que los métodos


clear() y send_keys().

before_click Este método se llama antes que el método


click().

after_click Este método se llama después que el método


click().

before_script Este método se llama antes que el método


execute_script().

after_script Este método se llama despues que el método


execute_script().

on_exception Este método se llama cuando se va a lanzar una


excepción.

/selenium/ejemplos/selenium_api/event_firing_webdriver.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.events import EventFiringWebDriver
from selenium.webdriver.support.events import AbstractEventListener

class TraceListener(AbstractEventListener):
  def on_exception(self, exception, driver):
  driver.save_screenshot('exception.png')
  print("Screenshot saved")

  def before_click(self, element, driver):


  print('Before click on: ' + element.get_attribute('value'))

  def before_change_value_of(self, element, driver):


  print('Before change value of: ' + element.get_attribute('value'))

  def before_find(self, by, value, driver):

198
  print('Before find: ' + value)

  def before_navigate_to(self, url, driver):


  print('Before navigate to: ' + url)

  def after_navigate_to(self, url, driver):


  print('After navigate to: ' + url)

class TestEventFiringWebDriver(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver = EventFiringWebDriver(self.driver, TraceListener())
  self.driver.get(
'http://es.wikipedia.org/w/index.php?title=Especial:Buscar&search=&fulltext=Buscar&pro
file=advanced')

  def testTestEventFiringWebDriver(self):
  checkboxes = self.driver.find_elements(By.CSS_SELECTOR,"#mw-searchoptions
input[name^='ns']")
  for check in checkboxes:
  if not check.is_selected():
  check.click()

  search_box = self.driver.find_element(By.CSS_SELECTOR,'#searchText.oo-ui-
inputWidget-input')
  search_box.clear()
  search_box.send_keys('Selenium')
  search_box.submit()

  self.driver.find_element(By.CSS_SELECTOR,"#no-existe")

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

199
13.8. Lab: Selenium API
13.8.1. Ejercicio 12

• Navegar a 'http://es.wikipedia.org/w/index.php?title=Especial:Crear_una_cuenta'

• Comprobamos que un elemento no está presente y mostramos un mensaje

• Comprobar que un elemento no está presente y mostramos una excepción con error
personalizado

• Comprobamos que un elemento está presente y se está mostrando

Gracias a la API de Selenium podemos automatizar interacciones avanzadas, como automatizar un


doble click o drag and drop. Para ello disponemos de la clase Actions, en la que podemos
automatizar una acción o un grupo de acciones.

200
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By

class Ejercicio12(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org/w/index.php?title=Especial:Crear_una_cuenta'
  self.driver.get('http://es.wikipedia.org/w/index.php?title=Especial:Crear_una_cuenta')

  def testCheckboxes(self):
  # Comprobamos que un elemento no está presente y mostramos un mensaje
  if not self.is_element_present(By.ID, 'este-no-esta'):
  print('No estaba')

  # Comprobar que un elemento no está presente y mostramos una excepción con error personalizado
  if not self.is_element_present(By.NAME, 'tampoco'):
  self.fail('Tampoco estaba')

  # Comprobamos que un elemento está presente y se está mostrando


  if self.is_element_present(By.CSS_SELECTOR, '#wpEditToken'):
  edit_token = self.driver.find_element(By.CSS_SELECTOR,'#wpEditToken')
  self.assertFalse(edit_token.is_displayed())

  def tearDown(self):
  self.driver.quit()

  def is_element_present(self, by, locator):


  try:
  self.driver.find_element(by=by, value=locator)
  except NoSuchElementException:
  return False
  return True

if __name__ == "__main__":
  unittest.main()

13.8.2. Ejercicio 13

• Navegar a
'http://es.wikipedia.org/w/index.php?title=Especial:Buscar&search=&fulltext=Buscar&profile=ad
vanced'

• Obtener el checkbox principal y comprobar que está seleccionado

• Obtener todos los checkboxes

• Capturar pantalla y guardar la imagen

• Seleccionar todos los checkboxes que no lo estén

• Capturar pantalla y guardar la imagen

201
import unittest, datetime, time
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio11(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org/w/index.php?title=Especial:Buscar&search=&fulltext=Buscar&profile=advanced'
  self.driver.get(
'http://es.wikipedia.org/w/index.php?title=Especial:Buscar&search=&fulltext=Buscar&profile=advanced')

  def testCheckboxes(self):
  # Obtener el checkbox principal y comprobar que está seleccionado
  check1 = self.driver.find_element(By.CSS_SELECTOR,"#mw-search-ns0")
  self.assertTrue(check1.is_selected())

  # Obtener todos los checkboxes


  checkboxes = self.driver.find_elements(By.CSS_SELECTOR,"#mw-searchoptions input[name^='ns']")

  # Capturar pantalla y guardar la imagen


  t = datetime.datetime.fromtimestamp(time.time()).strftime('%d%m%Y_%H%M')
  file_name = 'before' + t + '.png'
  self.driver.save_screenshot(file_name)

  # Seleccionar todos los checkboxes que no lo estén


  for check in checkboxes:
  if not check.is_selected():
  check.click()

  # Capturar pantalla y guardar la imagen


  t = datetime.datetime.fromtimestamp(time.time()).strftime('%d%m%Y_%H%M')
  file_name = 'after' + t + '.png'
  self.driver.save_screenshot(file_name)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

13.8.3. Ejercicio 14

• Navegar a 'http://es.wikipedia.org/wiki/Wikipedia:Portada'

• Comprobar que hay una cookie con el nombre 'GeoIP'

• Obtener todas las cookies

• Comprobar que hay cookies

• Eliminar todas las cookies

• Comprobar que no hay cookies

202
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class Ejercicio14(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org/wiki/Wikipedia:Portada'
  self.driver.get('http://es.wikipedia.org/wiki/Wikipedia:Portada')

  def testCheckboxes(self):
  # Comprobar que hay una cookie con el nombre 'GeoIP'
  geo_ip_cookie = self.driver.get_cookie("GeoIp")
  self.assertIsNotNone(geo_ip_cookie)

  # Obtener todas las cookies


  cookies = self.driver.get_cookies()

  # Comprobar que hay cookies


  self.assertTrue(len(cookies) > 0)

  # Eliminar todas las cookies


  self.driver.delete_all_cookies()

  # Comprobar que no hay cookies


  cookies = self.driver.get_cookies()
  self.assertTrue(len(cookies) == 0)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

203
Capítulo 14. Sincronización
La sincronización nos va a permitir indicarle al navegador que le de tiempo a los elementos a que
aparezcan en la página.

14.1. Wait implícito


Cuando definimos este tipo de wait, dentro de nuestros tests, el sistema esperará una determinada
cantidad de tiempo, mientras busca el elemento en el DOM de la página que le preceda. En caso de
haber pasado ese tiempo y que el elemento no haya aparecido, entonces lanza una excepción
NoSuchElementException.

/index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-
scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
  </style>
  <title>Document</title>
</head>
<body>
  <div id="container">
  </div>
</body>
<script>
  setTimeout(() => {
  var boton = document.createElement('button');
  boton.setAttribute('id', 'btn');
  boton.setAttribute('type', 'button');
  boton.appendChild(document.createTextNode('Pulsa aquí'));
  document.getElementById('container').appendChild(boton);
  }, 2000)
</script>
</html>

204
/selenium/ejemplos/syncronization/implicit_wait.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By

class ImplicitWait(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testImplicitWait(self):
  self.driver.implicitly_wait(6)
  button = self.driver.find_element(By.CSS_SELECTOR,'#btn')
  self.assertIsNotNone(button)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

Aunque no pongamos este wait, el sistema por defecto espera 10 segundos si no encuentra el
elemento dentro de un find_element_by_xxx().

14.2. Wait explícito


Proporciona una manera mucho más óptima de sincronización. Para ello disponemos de
WebDriverWait y ExpectedCondition. Si la condición se cumple antes de tiempo, deja de esperar.

Table 19. Condiciones predefinidas

Función Descripción

element_to_be_clickable(locator) El elemento es visible y está habilitado.

element_to_be_selected(element) El elemento es seleccionable.

presence_of_element_located(locator) El elemento está presente.

text_to_be_present_in_element(locator, text) El texto está presente en un elemento.

title_contains(text) El title contiene el texto.

title_is(text) El title es igual que el texto.

205
/selenium/ejemplos/syncronization/explicit_wait.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By

class ExplicitWait(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testExplicitWait(self):
  wait = WebDriverWait(self.driver, 6)
  wait.until(expected_conditions.presence_of_element_located((By.ID, 'btn')))
  button = self.driver.find_element(By.CSS_SELECTOR,'#btn')
  self.assertIsNotNone(button)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

14.3. FluentWait
Nos permite indicar al sistema una cantidad de tiempo a esperar y la frecuencia con la que va a
buscar un elemento en el DOM de la página web. También podemos indicarle que ignore cierto tipo
de excepciones como NoSuchElementException.

206
/selenium/ejemplos/syncronization/fluent_wait.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException

class FluentWait(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://localhost')

  def testFluentWait(self):
  wait = WebDriverWait(self.driver, 6, poll_frequency=0.2, ignored_exceptions=
[NoSuchElementException])
  wait.until(lambda s: s.find_element(By.ID,'btn').is_displayed())
  button = self.driver.find_element(By.CSS_SELECTOR,'#btn')
  self.assertIsNotNone(button)

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

207
Capítulo 15. DataDriven
Gracias al testing basado en los datos, podemos usar el mismo código de test para testear diferentes
condiciones (datos). Podemos testear, por ejemplo toda una tabla, sin tener que hacer un test por
cada registro. Selenium por si solo no proporciona ningún mecanismo para realizar este tipo de
test, pero vamos a ver diferentes formas de hacerlo.

Para poder parametrizar los tests, vamos a necesitar usar la librería ddt. Con ella vamos a poder
definir un conjunto de datos y pasarselos a los tests, que se ejecutarán repetidas veces con los
distintos datos.

Vamos a instalar la librería con el siguiente comando:

$ pip install ddt

Para usar esta librería, vamos a usar unos decoradores en la clase y en los métodos de tests.

• @ddt: permite ejecutar un test múltiples veces, pero con distintos datos.

• @data(args): recibe como argumentos los datos que se les va a pasar a los tests como
parámetros.

• @unpack: separa las tuplas o listas en múltiples argumentos.

/selenium/ejemplos/data_driven/data_driven.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from ddt import ddt, data, unpack

@ddt
class DataDriven(unittest.TestCase):

  @data([2, 5, 10], [3, 10, 30], [1, 7, 7])


  @unpack
  def testDataDriven(self, num1, num2, res):
  resultado = num1 * num2
  print(resultado)
  self.assertEqual(res, resultado)

15.1. CSV
Estos datos que hemos hardcodeado en el script de python, los podemos leer desde un archivo CSV.
Para ello solo tendremos que implementar un método que se encargue de leer estos datos y
devolver una lista con ellos. Este método se le pasará como parámetro al decorador @data.

Para poder leer el archivo necesitaremos los siguientes métodos:

208
• open(filename, mode): para abrir el archivo file en modo mode (lectura, escritura…). Este
método devuelve un objeto file.

• reader(file): este método devuelve un objeto reader que va a ir iterando sobre las líneas del
archivo csv.

• next(reader, None): pasa a la siguiente iteración.

/selenium/ejemplos/data_driven/data_driven_csv.py

import unittest
import csv
from selenium import webdriver
from selenium.webdriver.common.by import By
from ddt import ddt, data, unpack
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException

filename = "/home/python/workspace/registerData.csv"

def get_data(filename):
  rows = []
  data_file = open(filename, 'r')
  reader = csv.reader(data_file)
  next(reader, None)
  for row in reader:
  rows.append(row)
  print(rows)
  return rows

@ddt
class DataDrivenCsv(unittest.TestCase):
  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get(
'http://es.wikipedia.org/w/index.php?title=Especial:Crear_una_cuenta')

  @data(*get_data('registerData.csv'))
  @unpack
  def testDataDrivenCsv(self, username_value, password_value,
confirm_password_value, email_value, captcha_value):
  username = self.driver.find_element(By.CSS_SELECTOR,'#wpName2')
  username.clear()
  username.send_keys(username_value)

  password = self.driver.find_element(By.CSS_SELECTOR,'#wpPassword2')
  password.clear()
  password.send_keys(password_value)

  confirm_pass = self.driver.find_element(By.CSS_SELECTOR,'#wpRetype')
  confirm_pass.clear()

209
  confirm_pass.send_keys(confirm_password_value)

  email = self.driver.find_element(By.CSS_SELECTOR,'#wpEmail')
  email.clear()
  email.send_keys(email_value)

  captcha = self.driver.find_element(By.CSS_SELECTOR,'#mw-input-captchaWord')
  captcha.clear()
  captcha.send_keys(captcha_value)

  button = self.driver.find_element(By.CSS_SELECTOR,'#wpCreateaccount')
  button.click()

  wait = WebDriverWait(self.driver, 10, poll_frequency=2, ignored_exceptions=


[NoSuchElementException])
  wait.until(lambda s: s.find_element(By.CSS_SELECTOR,'.error').is_displayed())

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

15.2. Excel
Los datos los podemos obtener de un archivo de excel. Para poder leer los datos de un archivo en
excel, necesitamos la librería xlrd que se instala con el siguiente comando:

$ pip install xlrd

Para poder leer el excel, vamos a necesitar los siguientes métodos y propiedades:

• open_workbook(file): abrimos el archivo excel.

• sheet_by_index(index): abrimos la hoja nº index.

• row_values(row-index, init, end): nos devuelve una lista con los valores de la fila row-index
desde la columna init a la end.

• nrows: nos devuelve el número de las filas que hay en la hoja de excel.

• ncols: nos devuelve el número de las columnas que hay en la hoja de excel.

/selenium/ejemplos/data_driven/data_driven_excel.py

import unittest, xlrd


from selenium import webdriver
from selenium.webdriver.common.by import By
from ddt import ddt, data, unpack
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException

210
filename = '/home/python/workspace/python-selenium-datadriven/registerData.xlsx'

def get_data(filename):
  rows = []
  book = xlrd.open_workbook(filename, 'r')
  sheet = book.sheet_by_index(0)
  for row_idx in range(1, sheet.nrows):
  rows.append(list(sheet.row_values(row_idx, 0, sheet.ncols)))
  return rows

@ddt
class DataDrivenExcel(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get(
'http://es.wikipedia.org/w/index.php?title=Especial:Crear_una_cuenta')

  @data(*get_data('registerData.xlsx'))
  @unpack
  def testDataDrivenExcel(self, username_value, password_value,
confirm_password_value, email_value, captcha_value):
  username = self.driver.find_element(By.CSS_SELECTOR,'#wpName2')
  username.clear()
  username.send_keys(username_value)

  password = self.driver.find_element(By.CSS_SELECTOR,'#wpPassword2')
  password.clear()
  password.send_keys(password_value)

  confirm_pass = self.driver.find_element(By.CSS_SELECTOR,'#wpRetype')
  confirm_pass.clear()
  confirm_pass.send_keys(confirm_password_value)

  email = self.driver.find_element(By.CSS_SELECTOR,'#wpEmail')
  email.clear()
  email.send_keys(email_value)

  captcha = self.driver.find_element(By.CSS_SELECTOR,'#mw-input-captchaWord')
  captcha.clear()
  captcha.send_keys(captcha_value)

  button = self.driver.find_element(By.CSS_SELECTOR,'#wpCreateaccount')
  button.click()

  wait = WebDriverWait(self.driver, 10, poll_frequency=2, ignored_exceptions=


[NoSuchElementException])
  wait.until(lambda s: s.find_element(By.CSS_SELECTOR,'.error').is_displayed())

211
  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

212
15.3. Lab: DataDriver
15.3.1. Ejercicio 15

• Navegar a 'http://es.wikipedia.org/wiki/Wikipedia:Portada'

• Pulsar el botón de buscar

• Espera ecplícita hasta que el títle de la página contenga 'Buscar'

• Pulsar sobre el link de 'Multimedia'

• Obtener el input y buscar 'Selenium'

• Crea una espera con timeout de 10s y que realice la búsqueda del elemento cada 200ms,
ignorando las excepciones

• Espera hasta que el elemento buscado se haya mostrado

• Pulsar en el primer resultado

• Comprobar que el title de la página acaba en '- Wikipedia, la enciclopedia libre'

213
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.common.exceptions import NoSuchElementException

class Ejercicio14(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  # Navegar a 'http://es.wikipedia.org/wiki/Wikipedia:Portada'
  self.driver.get('http://es.wikipedia.org/wiki/Wikipedia:Portada')

  def testCheckboxes(self):
  # Pulsar el botón de buscar
  search_button = self.driver.find_element(By.CSS_SELECTOR,'#searchButton')
  search_button.click()

  # Espera ecplícita hasta que el títle de la página contenga 'Buscar'


  explicit_wait = WebDriverWait(self.driver, 10)
  explicit_wait.until(expected_conditions.title_contains('Buscar'))

  # Pulsar sobre el link de 'Multimedia'


  link = self.driver.find_elements_by_css_selector('#search .search-types li')[1]
  link.click()

  # Obtener el input y buscar 'Selenium'


  search_box = self.driver.find_element(By.CSS_SELECTOR,'#searchText .oo-ui-inputWidget-input')
  search_box.clear()
  search_box.send_keys('Selenium')
  search_box.submit()

  # Crea una espera con timeout de 10s y que realice la búsqueda del elemento cada 200ms, ignorando las excepciones
  custom_wait = WebDriverWait(self.driver, 10, poll_frequency=0.2, ignored_exceptions=[NoSuchElementException])

  # Espera hasta que el elemento buscado se haya mostrado


  custom_wait.until(lambda s: s.find_element(By.CSS_SELECTOR,'.results-info').is_displayed())

  # Pulsar en el primer resultado


  self.driver.find_elements_by_css_selector('.mw-search-results li')[0].click()

  # Comprobar que el title de la página acaba en '- Wikipedia, la enciclopedia libre'


  self.assertTrue(self.driver.title.endswith('- Wikipedia, la enciclopedia libre'))

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

15.4. Ejercicio 16
• Usar ddt para pasar datos como parámetros a los tests

• Obtener el campo de usuario y rellenarlo

• Obtener el campo de password y rellenarlo

• Obtener el campo de repetir password y rellenarlo

• Obtener el campo de email y rellenarlo

214
• Obtener el campo de captcha y rellenarlo

• Obtener el botón y enviar el formulario

• Esperar hasta que el elemento de error se muestre

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from ddt import ddt, data, unpack
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException

@ddt
class DataDriven(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.get('http://es.wikipedia.org/w/index.php?title=Especial:Crear_una_cuenta')

  @data(['robb', '1234', '1234', '[email protected]', 'asflhjdfs'], ['arya', '1222', '1222', '[email protected]',


'ydufskd'], ['rickon', '3882', '3882', '[email protected]', 'adsadip'])
  @unpack
  def testDataDriven(self, username_value, password_value, confirm_password_value, email_value, captcha_value):
  username = self.driver.find_element(By.CSS_SELECTOR,'#wpName2')
  username.clear()
  username.send_keys(username_value)

  password = self.driver.find_element(By.CSS_SELECTOR,'#wpPassword2')
  password.clear()
  password.send_keys(password_value)

  confirm_pass = self.driver.find_element(By.CSS_SELECTOR,'#wpRetype')
  confirm_pass.clear()
  confirm_pass.send_keys(confirm_password_value)

  email = self.driver.find_element(By.CSS_SELECTOR,'#wpEmail')
  email.clear()
  email.send_keys(email_value)

  captcha = self.driver.find_element(By.CSS_SELECTOR,'#mw-input-captchaWord')
  captcha.clear()
  captcha.send_keys(captcha_value)

  button = self.driver.find_element(By.CSS_SELECTOR,'#wpCreateaccount')
  button.click()

  wait = WebDriverWait(self.driver, 10, poll_frequency=2, ignored_exceptions=[NoSuchElementException])


  wait.until(lambda s: s.find_element(By.CSS_SELECTOR,'.error').is_displayed())

  def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
  unittest.main()

215
Capítulo 16. Pruebas Multi Browser y
Selenium Grid
Hasta ahora hemos realizado nuestros test trabajando con un solo navegador. En este punto del
temario vamos a ver como podemos realizar las pruebas en paralelo en varios navegadores, en
nuestro caso Firefox y Chrome.

Además también exploraremos Selium Grid, que nos permitirá realizar nuestros test en varios
dispositivos con diferentes sistemas operativos y navegadores diferentes.

16.1. Multibrowse
En ocasiones necesitaremos probar nuestra aplicación en diferentes navegadores. Está claro que si
estamos realizando un test sobre una página sencilla escrita en HTML no veremos diferencia entre
un navegador y otro. Pero si nuestra aplicación es mas complicada, aunque el código sea el mismo
para todos los navegadores, los resultados de los procesos de renderización son diferentes. Por
ejemplo podemos tener botones que no estén correctamente alineados ya que el navegador no está
renderizando como esperamos.

Veamos como podemos ejecutar un test en varios navegadores.

216
import unittest
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options

class PythonOrgSearchChrome(unittest.TestCase):

  def setUp(self):
  chrome_options = webdriver.ChromeOptions()
  self.driver = webdriver.Chrome()
  self.driver.set_window_size(1920, 1080)
  self.driver.maximize_window()

  def test_search_in_python_chrome(self):
  driver = self.driver
  driver.get('http://www.google.com')
  driver.find_element(By.XPATH,"/html/body/div[2]/div[2]/div[3]/span/div/div/div/div[3]/div[1]/button[2]/div")
.click()
  self.assertIn("Google", driver.title)
  time.sleep(5)
  search_box = driver.find_element(By.NAME,'q')
  search_box.send_keys('Pronoide')
  #nos aseguramos de que la pagina de resultados devuelve algo
  assert "No results found." not in driver.page_source
  search_box.submit()
  time.sleep(5)
  driver.save_screenshot('screenshot-deskto-chrome.png')

  def tearDown(self):
  self.driver.close()

class PythonOrgSearchFireFox(unittest.TestCase):

  def setUp(self):
  self.driver = webdriver.Firefox()
  self.driver.set_window_size(1920, 1080)
  self.driver.maximize_window()

  def test_search_in_python_firefox(self):
  driver = self.driver
  driver.get('http://www.google.com')
  driver.find_element(By.XPATH,"/html/body/div[2]/div[2]/div[3]/span/div/div/div/div[3]/div[1]/button[2]/div")
.click()
  self.assertIn("Google", driver.title)
  time.sleep(5)
  search_box = driver.find_element(By.NAME,'q')
  search_box.send_keys('Pronoide')
  assert "No results found." not in driver.page_source
  search_box.submit()
  time.sleep(5)
  driver.save_screenshot('screenshot-deskto-firefox.png')

  def tearDown(self):
  self.driver.close()

if __name__ == "__main__":
  unittest.main()

217
16.2. Selenium Grid
Gracias a Selenium Grid podemos realizar nuestros test en diferentes máquinas que se encuentran
en diferentes localizaciones y con diferentes caractéristicas (navegador, sistema operativo).

Selenium Grid trabaja con un modelo de HUB/NODO. De esta forma arranca el test en el HUB, pero
la ejecución se hace en los nodos

HUB

• Es un servidor proxy que nos facilita ejecutar test en paralelo en los diferentes nodos

• Acepta peticiones de acceso desde el WebDriver

• Enruta los comandos (JSON) a los driver remotos en los nodos

• Recoge las instrucciones desde el cliente y las ejecuta de manera remota en varios nodos.

Nodo

• Dispositivo remoto con un sistema operativo y un WebDriver remoto.

• Recibe las peticiones desde el HUB en formato Json y las ejecuta usando el webdriver

Una vez hemos levantado nuestro servidor tenemos que modificar nuestros scripts de testing.
Podemos usar WebDriver de forma remota de la misma manera que trabajamos en local, pero
tenemos que tener en cuenta un par de detalles:

• En primer lugar debemos configurar Remote Webdriver para que pueda ejecutarse en una

218
máquina remota.

• Esto es debido a que un Remote Webdriver esta compuesto de dos piezas: un cliente y un
servidor

◦ Cliente → Nuestro webdriver

◦ Servidor → un servlet de java

• Para ejecutar un remote webdriver haremos uso de RemoteWebDriver, para ello apuntaremos a
la url del server

from selenium import webdriver

firefox_options = webdriver.FirefoxOptions()
driver = webdriver.Remote(
  command_executor='http:url_server',
  options=firefox_options
)
driver.get("http://www.google.com")
driver.quit()

Podemos seguir costumizando nuestro test, con las opciones del navegador. Si por ejemplo
quisieramos ejecutar Chrome en un Windows 11, usando una determinada versión de chrome:

from selenium import webdriver

chrome_options = webdriver.ChromeOptions()
chrome_options.set_capability("browserVersion", "67")
chrome_options.set_capability("platformName", "Windows XP")
driver = webdriver.Remote(
  command_executor='http://www.example.com',
  options=chrome_options
)
driver.get("http://www.google.com")
driver.quit()

16.2.1. Transferencia de archivos

Para poder transferir archivos entre el cliente y el servidor usaremos LocalFileDetector. Esto es
util cuando por ejemplo tenemos que subir un archivo a a una aplicación web. Gracias al
webdriver esta transferencia del desde la máquina local al servidor se realiza en tiempo de
ejecución.

Para habilitar esta opción debemos importar LocalFileDetector

219
from selenium.webdriver.remote.file_detector import LocalFileDetector

driver.file_detector = LocalFileDetector()

driver.get("http:www.paginatest.com")

driver.find_element(By.ID, "myfile").send_keys("/ruta/a/nuestro/archivo")

16.2.2. Configurando Selenium Grid

Lo primero que debemos hacer es descargar e instalar nuestro servidor selenium. Creamos un
directorio llamado selenium y lo descargamos allí

$ mkdir selenium; cd selenium


$ wget https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.5.0/selenium-server-4.5.3.jar

Este servidor nos permite:

• Trabajar en modo "standalone" → En la misma instancia tendremos el servidor y el cliente

• Trabajar en una configuración hub/nodos en la misma máquina → Estableceremos diferentes


nodos asignandoles un puerto diferente

• Con el Hub y los nodos instalados en diferentes máquinas.

En este curso vamos a configurar las dos primeras opciones, pera ver las opciones relativas a la
tercera opción puedes visitar este enlace

Configuración Standalone

Una vez descargado el archivo en el directiro lo ejecutamos mediante el siguiente comando.

$ java -jar selenium-server-4.5.3.jar standalone

Hub y diferentes nodos en la misma máquina

Para levantar el Hub

$ java -jar selenium-server-4.5.3.jar hub

Para levantar los nodos abriremos dos terminales nuevas

$ java -jar selenium-server-4.5.3.jar node --port 5555

220
$ java -jar selenium-server-4.5.3.jar node --port 6666

Con nuestro Hub y sus nodos levantados ya podemos realizar un test

import unittest
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options

class PythonOrgSearchChrome(unittest.TestCase):

  def setUp(self):
  chrome_options = webdriver.ChromeOptions()
  self.driver = webdriver.Remote(
  command_executor="http://localhost:4444",
  options= chrome_options
  )
  self.driver.set_window_size(1920, 1080)
  self.driver.maximize_window()

  def test_search_in_python_chrome(self):
  driver = self.driver
  driver.get('http://www.google.com')
  driver.find_element(By.XPATH,"/html/body/div[2]/div[2]/div[3]/span/div/div/div/div[3]/div[1]/button[2]/div")
.click()
  self.assertIn("Google", driver.title)
  time.sleep(5)
  search_box = driver.find_element(By.NAME,'q')
  search_box.send_keys('Pronoide')
  #nos aseguramos de que la pagina de resultados devuelve algo
  assert "No results found." not in driver.page_source
  search_box.submit()
  time.sleep(5)
  driver.save_screenshot('screenshot-deskto-chrome.png')

  def tearDown(self):
  self.driver.close()

class PythonOrgSearchFireFox(unittest.TestCase):

  def setUp(self):
  firefox_options = webdriver.FirefoxOptions()
  self.driver = webdriver.Remote(
  command_executor="http://localhost:4444",
  options=firefox_options
  )
  self.driver.set_window_size(1920,1080)
  self.driver.maximize_window

  def test_search_in_python_firefox(self):
  driver = self.driver
  driver.get('http://www.google.com')
  driver.find_element(By.XPATH,"/html/body/div[2]/div[2]/div[3]/span/div/div/div/div[3]/div[1]/button[2]/div")
.click()
  self.assertIn("Google", driver.title)
  time.sleep(5)
  search_box = driver.find_element(By.NAME,'q')
  search_box.send_keys('Pronoide')
  assert "No results found." not in driver.page_source
  search_box.submit()

221
  time.sleep(5)
  driver.save_screenshot('screenshot-deskto-firefox.png')
  def tearDown(self):
  self.driver.close()

if __name__ == "__main__":
  unittest.main()

222
Capítulo 17. Web Scraping
17.1. ¿Qué es el Web Scraping?
A través del web scraping examinamos el código fuente de las páginas web en busca de patrones
concretos. Para extraer esta información se útilizan programas que reciben el nombre de web
scrapers, crawlers, spiders o bots.

Los datos que obtenemos a partir de esos programas son posteriormente resumidos, evaluados,
combinados o almacenados.

Todo lo anterior se puede resumir en un sencillo esquema:

• Abrir la pagina web que a través del URL

• Extraer automaticamente los datos a través de los patrones

• Resumir, almacenar, evaluar o combinar los datos extraidos.

17.2. ¿Para qué podemos usar web scraping?


Las aplicaciones del web scraping son bastante amplias, aunque la mas conocida sea para la
indexación de buscadores existen mas casos en los que nos puede ayudar:

• Controlar y comparar ofertas online

• Crear bases de datos de contactos

• Reunir datos de distintas fuentes online

• Seguimiento de la presencia y reputación online

• Seguimiento del cambio de contenido en paginas web

• Realizar data mining

17.3. ¿Por qué Python es adecuado para el web


scraping?
Las paginas web cambian su contenido a menudo y también, aunque en periodos mas largos,
cambia su diseño.

Un web scraper se desarrolla teniendo en cuenta la estructura específica de una página web, por lo
tanto si esa estructura cambia tambien debe modificarse el programa. Esto es algo que con Python
resulta sencillo.

Además Python tiene como fortalezas dos de las bases técnicas del web scraping:

• Procesamiento de texto

• Apertura de recursos web

• Lenguaje consolidado en materia de analisis y procesamiento de datos.

223
Junto a todo lo anterior hay que tener en cuenta que herramientas como Scrapy, Selenium y
BeatifulSoup están diseñadas para poder usar Python a la hora de hacer web scraping.

17.4. Scrapy
Scrapy como deciamos anteriormente es una plataforma en la que podemos aplicar tecnicas de web
scraping usando Python: * Cuenta con un pipeline integrado para procesar los datos extraidos. * La
apertura de páginas se produce de forma asincrona, dicho de otro modo esto permite la descarga
de varias páginas de forma simultanea. * Es una buena opción cuando hay que procesar grandes
volúmenes de páginas.

Scrapy utiliza un analizador sintáctico o parser HTML para extraer los datos buscados del
código fuente de la web (HTML).

La clave de Scrapy son los web spiders: * Progrmas sencillos basados en Scrapy. * Cada uno de
estos programas está creado para trabajar sobre una web concreta. * Van "Descolgandose" página a
página * Se desarrollan en POO → Cada Spider es una clase de Python

Al instalar Scrapy, ademas del paquete y módulos de Python se incluye una herramienta en linea de
comandos, la Scrapy Shell para el control de los spiders.

También existe la posibilidad de almacenar los spiders ya creados en la Scrapy Cloud. De esta
forma, ya que se ejecutan en tiempos establecidos, podemos trabajar sobre sitios complejos sin
necesidad de ordenador o conexión a internet.

17.4.1. Empezando a trabajar con Scrapy

Para trabajar con Scrapy primero debemos instalarlo, si no lo tenemos lo haremos mediante pip

$ pip install Scrapy

Los pasos a seguir para comenzar a trabajar son los siguientes:

1. Crear un proyecto de Scrapy

2. Escribir nuestro spider para un sitio concreto y extraer sus datos

3. Exportar los datos mediante la terminal

4. Cambiar el spider para utilizarlo en links recursivamente

5. Usar los argumentos del spider.

224
Crear el proyecto
Desde el directorio en que vayamos a almacenar nuestro código ejecutamos::

$ scrapy startproject proyecto

Lo anterior habrá creado un directorio proyecto con el siguiente contenido

.
├── proyecto # Módulo de Python del proyecto. Podemos importarlo
│ ├── __init__.py
│ ├── items.py #Definición de los items del proyecto
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders #Subpaquete donde poodemos almacenar nuestros spiders.
│ └── __init__.py
└── scrapy.cfg #Archivo de configuración

Escribir nuestro spider para un sitio concreto y extraer sus datos


Para esta parte, y mas adelante en el curso vamos a usar la web https://quotes.toscrape.com/ que
contiene citas y el nombre de los famosos que se las atribuyeron.

Nos situamos, si no lo estamos ya, sobre nuestro directorio proyecto

$ cd /home/python/Desktop/proyecto/spiders

Antes de comenzar a escribir nuestro código vamos a ver como funciona scrapy a través de la shell,
esto nos ayudara a comprender mejor lo que pasa en los siguientes ejemplos.

225
$ scrapy shell https://quotes.toscrape.com/page/1/

>>> response.css('title') # Devuelve un SelectorList, una lista de selectores XML/HTML


[<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]
>>> response.css('title::text').getall() #Extraemos el texto de la etiqueta "title"
['Quotes to Scrape']
>>> response.css('title').getall() # Extraemos todo el contenido de la etiqueta
['<title>Quotes to Scrape</title>']
>>> response.css('title').get() #Extraemos solo la primera coincidencia
'<title>Quotes to Scrape</title>'
>>> response.css('title::text').get() #Extraemos solo la primera coincidencia que sea texto
'Quotes to Scrape'
>>> response.css('title::text').re(r'Quotes.*') # Con "re" podemos usar expresiones regulares
['Quotes to Scrape']
>>> response.css('title::text').re(r'Q\w+')
['Quotes']
>>> response.css('title::text').re(r'(\w+) to (\w+)')
['Quotes', 'Scrape']

Para extraer las citas y los autores nos basaremos en el siguiente codigo fuente de la página, cada
una de las citas tendrá esta estructura

<div class="quote">
  <span class="text">“The world as we have created it is a process of our
  thinking. It cannot be changed without changing our thinking.”</span>
  <span>
  by <small class="author">Albert Einstein</small>
  <a href="/author/Albert-Einstein">(about)</a>
  </span>
  <div class="tags">
  Tags:
  <a class="tag" href="/tag/change/page/1/">change</a>
  <a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a>
  <a class="tag" href="/tag/thinking/page/1/">thinking</a>
  <a class="tag" href="/tag/world/page/1/">world</a>
  </div>
</div>

Para obtener los selectores para la cita

>>> response.css("div.quote")

Cada uno de estos selectores es cada una de las citas que hay en el web. Vamos a asignar el primer
selector a un varible para poder trabajar sobre ella.

>>> quote = response.css(div.quote)[0]

226
Vamos a extraer text, author, tags de la primera cita almacenada en "quote".

>>> text = quote.css("span.text::text").get()


>>> text
'“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'

>>> author = quote.css("small.author::text").get()


>>> author
'Albert Einstein'

>>> tags = quote.css("div.tags a.tag::text").getall()


>>> tags
['change', 'deep-thoughts', 'thinking', 'world']

Ya podemos crear nuestro primer spider

import scrapy

class QuotesSpider(scrapy.Spider):
  name = "quotes"

  def start_requests(self):
  urls = [
  'https://quotes.toscrape.com/page/1/',
  'https://quotes.toscrape.com/page/2/',
  ]
  for url in urls:
  yield scrapy.Request(url=url, callback=self.parse)

  def parse(self, response):


  page = response.url.split("/")[-2]
  filename = f"quotes-{page}.html"
  with open(filename,"wb") as f:
  f.write(response.body)
  self.log(f"Saved file{filename}")

Nuestra clase QuotesSpider es una subclase de de scrapy.Spider que define los atributos y
métodos usados:

• name → Identifica el Spider y debe ser único en cada proyecto.

• start_request() → Debe devolvernos un iterable de solicitudes desde el que el spider comience


a rastrear. La solicitudes posteriores se generarán a partir de las iniciales.

• parse() → Es el método responsable de manejar las respuestas que se han descargado para cada
una de las solicitudes

◦ parse generalmente analiza la respuesta, extrae los datos como un diccionario,encuentra

227
nuevas URL para seguir y crea nuevas solicitudes a partir de ellas.

◦ El parametro response es una instancia del TextResponse, que almacena el contenido de la


página y tiene métodos utiles para su manejo.

Para ejecutar nuestro spider debemos hacerlo desde el directorio superior, en nuestro caso:

$ cd /home/python/Documents/proyecto

Una vez allí ejecutamos en linea de comandos

$ scrapy crawl quotes

El resultado mostrará lo siguiente entre sus líneas:

...
2022-07-20 10:28:02 [scrapy.core.engine] INFO: Spider opened
2022-07-20 10:28:02 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2022-07-20 10:28:02 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2022-07-20 10:28:03 [scrapy.core.engine] DEBUG: Crawled (404) <GET https://quotes.toscrape.com/robots.txt> (referer:
None)
2022-07-20 10:28:03 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://quotes.toscrape.com/page/1/> (referer: None)
2022-07-20 10:28:03 [quotes] DEBUG: Saved filequotes-1.html
2022-07-20 10:28:03 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://quotes.toscrape.com/page/2/> (referer: None)
2022-07-20 10:28:03 [quotes] DEBUG: Saved filequotes-2.html
2022-07-20 10:28:03 [scrapy.core.engine] INFO: Closing spider (finished)

Se han creado dos archivos, tal y como le indicabamos en nuestro parse. Si consultamos en nuestro
directorio, los podemos ver

$ cd /home/python/Documents/proyecto/proyecto
$ ls

__init__.py middlewares.py __pycache__ quotes-2.html spiders


items.py pipelines.py quotes-1.html settings.py

Hasta aquí simplemente le hemos dicho a nuestro spider que creara los archivos, pero todavía no
hemos extraido ningun dato. Vamos integrar la lógica que necesitamos para que esto suceda en
nuestro spider.

228
import scrapy

class QuotesSpider(scrapy.Spider):
  name = "quotes"
  start_urls = [
  'https://quotes.toscrape.com/page/1/',
  'https://quotes.toscrape.com/page/2/',
  ]

  def parse(self, response):


  for quote in response.css('div.quote'):
  yield{
  'text': quote.css('span.text::text').get(),
  'author': quote.css('small.author::text').get(),
  'tags': quote.css('div.tags a.tag::text').getall(),
  }

En la salida de la terminal vemos como empiezan a aparecer los datos:

...
2022-07-20 10:52:10 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com/page/2/>
{'text': '“Good friends, good books, and a sleepy conscience: this is the ideal life.”', 'author': 'Mark Twain', 'tags':
['books', 'contentment', 'friends', 'friendship', 'life']}
2022-07-20 10:52:10 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com/page/2/>
{'text': '“Life is what happens to us while we are making other plans.”', 'author': 'Allen Saunders', 'tags': ['fate',
'life', 'misattributed-john-lennon', 'planning', 'plans']}
...

Pero todavía nos queda almacenarlos, eso lo haremos a través de la terminal a la hora de la
ejecución. Podemos usar dos opciones:

• -O → Generaremos un archivo .json que contendra nuestros datos

scrapy crawl quotes -O quotes.json

• -o → Añadiremos nuevo contenido a un archivo creado previamente.

◦ Al añadir contenido es recomendable usar el formato JSON Lines

scrapy crawl quotes -o quotes.jl

Hasta ahora hemos recorrido solo dos páginas. ¿Como podemos descolgarnos por todas las
páginas?.

• Lo primero es localizar y extraer el link de la página a la que nos queremos dirigir. En nuestras
páginas de ejemplo encontramos lo siguiente

229
<ul class="pager">
  <li class="next">
  <a href="/page/2/">Next <span aria-hidden="true">&rarr;</span></a>
  </li>
</ul>

En nuestro caso queremos obtener el elemento "href", vamos a probarlo con la scrapy shell

$ scrapy shell https://quotes.toscrape.com/page/1/


...
>>> response.css('li.next a').get()
'<a href="/page/2/">Next <span aria-hidden="true">→</span></a>'
>>> response.css('li.next a::attr(href)').get()
'/page/2/'

Esto nos lleva a modificar nuestro spider

import scrapy

class QuotesSpider(scrapy.Spider):
  name = "quotes"
  start_urls = [
  'https://quotes.toscrape.com/page/1/',
  ]

  def parse(self, response):


  for quote in response.css('div.quote'):
  yield{
  'text': quote.css('span.text::text').get(),
  'author': quote.css('small.author::text').get(),
  'tags': quote.css('div.tags a.tag::text').getall(),
  }

  next_page = response.css('li.next a::attr(href)').get()


  if next_page is not None:
  next_page = response.urljoin(next_page)
  yield scrapy.Request(next_page, callback=self.parse)

17.5. Selenium
Selenium es un framework diseñado para realizar test automatizados de software sobre
aplicaciones web. Aunque como decimos se creó para poner a prueba paginas y aplicaciones web,
su WebDriver puede usarse con Python para el scraping.

Selenium trabaja de manera diferente a como lo hace Scrapy:

• Carga la página en un navegador sin interfaz de usuario (No trabaja con el código fuente)

230
• El navegador interpreta el código fuente y crea un DOM (Document Object Model)

• La interfáz estandarizada permite probar las interacciones del usuario (clicks, rellenar
formularios). Los cambios se reflejarán en el DOM.

• Podemos hacer scraping en páginas web creadas con JavaScript ya que el DOM se genera de
manera dinámica.

• El acceso a contenidos dinámicos es la caracteristica mas importante de Selenium. Esto nos


permite trabajar junto a Scrapy o BeatifulSoup.

17.5.1. Empezando a trabajar con Selenium

Al igual que el caso de Scrapy lo primero será instalar Selenium en nuestro equipo

$ pip install selenium

Tras su instalación necesitamos descargar e instalar el Web Driver del navegador que vayamos a
usar. Como deciamos antes, nos permitira movernos por la web, ejecutar JavaScript y demás.
Selenium soporta multiples navegadores, para elegir con el que vamos a trabajar (Chrome)
debemos ir a la página de Selenium y descargar la última versión estable.

En nuestro caso debemos elegir la versión de linux64

231
Una vez que lo tengamos descargado debemos descomprimirlo y ejecutarlo desde la terminal.

$ cd Downloads/
$ ls
chromedriver_linux64.zip
$ unzip chromedriver_linux64.zip
Archive: chromedriver_linux64.zip
  inflating: chromedriver
$ ls
chromedriver chromedriver_linux64.zip
$ ./chromedriver
Starting ChromeDriver 103.0.5060.134 (8ec6fce403b3feb0869b0732eda8bd95011d333c-refs/branch-heads/5060@{#1262}) on port
9515
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.

El Web Driver de Selenium nos ofrece varias funciones que nos ayudaran a localizar los elementos
en la web. Vamos a verlos a continuacion

• Localizar elementos usando su ID"

◦ Lo usaremos cuando conozcamos el ID del elemento en la web.

◦ devolverá el primer elemento que coincida con el atributo ID

driver.find_element(By.ID, 'id')

• Localizar elementos usando el nombre de la Clase"

◦ Lo usaremos cuando conozcamos el nombre de la clase de los atributos de la web.

◦ Nos devolverá la primera coincidencia con el nombre. Si no existiera la coincidencia lanzara


la excepcion: NoSuchElementException.

find_element(By.CLASS_NAME, "class name")

• Localizar elementos usando el Nombre"

◦ Nos devolverá la primera coincidencia con el nombre del atributo.

◦ Es útil cuando queremos hacer scraping de titulos, campos y etc

232
find_element(By.NAME, "name")

• Localizar elementos usando Xpath

◦ El lenguaje Xpath nos permite localizar nodos en un documento XML, por ello al usar
selenium tenemos acceso a los atributos de un HTML (HTML es una implementación de
XML)

◦ Podemos hacer scraping sobre varios datos al mismo tiempo

◦ Podemos escribir el XPath de un elemento ya se de manera absoluta o relativa respecto al


DOM

find_element(By.XPATH, "xpath")

• Podemos resumir su sintaxis de la siguiente manera

◦ Xpath = //tagname[@Attribute='Value']

▪ // → Selecciona el nodo actual

▪ tagname → nombre de la etiqueta: div, td, li etc

▪ @ → Selecciona el atributo

▪ Attribute → Selecciona el nombre del atributo: class, Id…

▪ Value → Valor del atributo

▪ Localizar elementos usando el texto de un link

• Se usa cuando conocemos el texto que contiene la etiqueta <a></a>

find_element(By.LINK_TEXT, "link text")

• Si queremos usar una parte del texto del link a modo de patrón podemos usar

find_element(By.PARTIAL_LINK_TEXT, "partial link text")

• Localizando elementos por el nombre de la etiqueta

◦ Es útil cuando queremos encontrar la primera ocurrencia de una etiqueta en un elemento

find_element(By.TAG_NAME, "tag name")

• Localizar elementos usando Selectores CSS

◦ Podemos localizar un elemento a tráves de un selector CSS

find_element(By.CSS_SELECTOR, "css selector")

233
En los casos anteriores hemos usado find_element pero también podemos usar find_elements,
que nos devolvera una lista con múltiples elementos que coincidan con nuestra busqueda.

Junto a estas opciones para localizar elementos no hay que olvidar que Selenium nos permite
interactuar con ellos, y con la web en general. Así que logicamente, lo que haremos después de
localizar un elemento es justamente eso, interactuar con él.

Para ello importamos Keys desde la libreria selenium

from selenium.webdriver.common.keys import Keys

Un ejemplo de su uso es el siguiente:

element.send_keys("some text") ## introduce texto en el elemento


element.send_keys("some text", Keys.ARROW_DOWN)
element.send_keys(Keys.ENTER) ##presiona enter

Hoy en día nos encontramos con que cualquier web usa ajax para cargar su contenido, diferentes
partes de la web cargarán a diferentes velocidades. Esto es un problema a la hora de hacer scraping
en ellas. Para resolver esto debemos usar Wait, que nos permite definir un intervalo entre dos
acciones consecutivas.

• Wait explicito

◦ Es un fragmento de código que espera a que suceda cierta condición antes de comenzar a
ejecutar

from selenium.webdriver.support.ui import WebDriverWait


try:
  element = WebDriverWait(driver, 10).until(
  EC.presence_of_element_located((By.ID, "myDynamicElement"))
  )
finally:
driver.quit()

• Wait implicito

◦ Es un fragmento de código que para la ejecución del programa durante un intervalo de


tiempo.

◦ Indica al Web Driver que sondee el DOM durante un cierto período de tiempo cuando este
intentando encontrar elemento o elementos que no estén disponibles de inmediato.

driver.implicitly_wait(10) # 10 second wait

Lo siguiente que cabe preguntarse es si podemos encadenar un serie de acciones. Eso lo


conseguimos gracias a las Action Chains

234
from selenium.webdriver.common.action_chains import ActionChains
actions= ActionChains(driver)
actions.send_keys(Keys.TAB) ## Presiona una tecla
actions.send_keys(Keys.ENTER) ## Presiona una tecla
actions.send_keys(username) ## input de una variable
actions.send_keys(Keys.Enter) ## presiona una tecla
actions.send_keys(Keys.Tab) ## presiona una tecla
actions.perform() ## Ejecuta las acciones una a una.

235
17.6. Lab: Web Scraping
17.6.1. Lab 1

Vamos a usar Selenium para realizar scraping en la pagina web de Box Office Mojo que contiene un
listado de 200 películas. Nuestro objetivo es conseguir los datos de estás películas y almacenarlas en
un csv

• Importa las librerias y modulos necesarios.

from selenium import webdriver


from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import pandas as pd

• Establece el driver para el navegador y abre la web

path = ('/home/python/Downloads/chromedriver')
s = Service(path)
driver = webdriver.Chrome(service=s)
driver.get('https://www.boxofficemojo.com/chart/top_lifetime_gross/?area=XWW')

• Obten los nombres de las películas, ten en cuenta que tienen esta estructura. Localizalos
mediante XPATH

<td class="a-text-left mojo-field-type-title">


  <a class="a-link-normal" href="/title/tt2488496/?ref_=bo_cso_table_4">Star Wars: Episode VII - The Force Awakens</a>
</td>

# Accedemos al nombre con Xpath


movies_names = driver.find_elements(By.XPATH, '//td[@class="a-text-left mojo-field-type-title"]/a[@class="a-link-
normal"]')
movie_name_list = []
for movie in range(len(movies_names)):
  movie_name_list.append(movies_names[movie].text)
print(movie_name_list)

• Busca los años de estreno y almacenalos en una lista. Su código es el siguiente

<td class="a-text-left mojo-field-type-year"><a class="a-link-normal"


href="/year/world/2015/?ref_=bo_cso_table_12">2015</a></td>

236
release_year = driver.find_elements(By.XPATH,'//td[@class="a-text-left mojo-field-type-year"]/a[@class="a-link-normal"]')
release_year_list = []
for year in range(len(release_year)):
  release_year_list.append(release_year[year].text)
print(release_year_list)

• Busca la recaudación y almacenala en una lista. Puedes guiarte por este código

<td class="a-text-right mojo-field-type-money">$1,332,698,830</td>

lifetime_gross = driver.find_elements(By.XPATH,'//td[@class="a-text-right mojo-field-type-money"]')


lifetime_gross_list = []
for i in range(len(lifetime_gross)):
  lifetime_gross_list.append(lifetime_gross[i].text)
print(lifetime_gross_list)

• Une las tres listas anteriores y exportalo a un csv con los nombres de los campos como cabecera
('Movie Name', 'Release Date','Lifetime Earnings')

data =list( zip(movie_name_list, release_year_list, lifetime_gross_list))


df = pd.DataFrame(data,columns=['Movie Name', 'Release Date','Lifetime Earnings'])
df.to_csv('top_200_movies_with_lifetime_gross.csv',index=False)

237
Capítulo 18. Planificando tareas con Python
Siempre es útil poder planificar tareas o ejecuciones en un momento determinado. Esto lo logramos
en Python gracias a los módulos Sched y Time

Lo primero que debemos hacer una vez importados los módulos y funciones necesarias es declarar
un programador el objeto de Python sobre el que vamos a aplicar diferentes métodos.

programador = scheduler(timefunc="funcion para obtner la hora actual", delayfunc="función tiempos de espera")

Una vez declarado este programador, sobre él podemos usar los siguientes métodos:

• enterabs() → Define los eventos de un programador para que se ejecute en un momento


específico

• enter() → Establece el inicio de la ejecución después de un tiempo de espera

Ambos comparten los mismos argumentos

# Definir un evento para ejecutar en un momento específico

scheduler.enterabs(time, priority, action, argument=(), kwargs={})

# Definir un evento para ejecutar después de un tiempo de espera

scheduler.enter(delay, priority, action, argument=(), kwargs={})

Donde:

• time(enterabs)/delay(enter) → Definen el tiempo de ejecución del evento o el tiempo de espera.


El valor debe ser compatible con el valor devuelto por la función que sea el primer argumento
del constructor.

• priority → establece la prioridad de ejecución si dos o mas eventos tienen el mismo momento de
ejecución. A menor valor, mayor prioridad

• action → la función que queremos ejecutar

• argument → Es un paramentro opcional. Habitualmente es una tupla con los valores de la


función introducida en Action.

• kwargs → Es un parametro opcional. Diccionario que se puede pasar como argumento de la


función introducida en action

Otros métodos que podemos usar para planificar tareas son los siguientes:

• cancel() → Siempre que el evento no haya iniciado su ejecución usará el valor devuelto por
enterabs y enter para cancelarlo con posterioridad

• empty() → Se utiliza para conocer si quedan eventos pendientes

• run() → Este método pone en marcha el programador una vez que hayamos definido los

238
eventos. Hace que el programador espere a que los eventos se vayan ejecutando uno a uno, en
el tiempo establecido y hasta que no quede ninguno pendiente.

Con todos los métodos vistos podremos:

• Programar eventos para ejecutarlos en un momento determinado

• Programar eventos con posibilidad de cancelación

• Programar eventos para que se ejecuten después de un tiempo de espera

• Programar eventos para que se ejecuten según una determinada prioridad

Veamoslo con un ejemplo

import sched
import time

def ejecutar_programa(estado):
  if estado:
  print("Comenzado la ejecución...")
  else:
  print("Ejecución terminada...")

programador = sched.scheduler(time.time, time.sleep)

comienzo = int(time.time())

t1 = comienzo + 1
t2 = t1 + 4

print("Iniciando programador", comienzo)

programador.enterabs(t1, 1, ejecutar_programa, (1,))


programador.enterabs(t2, 1, ejecutar_programa, (0,))

programador.run()
print("Programador finalizado", int(time.time()))

Iniciando programador 1661958686


Comenzado la ejecución...
Ejecución terminada...
Programador finalizado 1661958691

El ejemplo anterior es la versión mas sencilla. Podemos añadir la opción de cancelar en la función
que es llamada por los eventos, y manejar eventos con prioridad

239
import sched
import time
import random

def ejecutar_programa(estado, mantener):


  print("HORA:", int(time.time()))
  if mantener and not programador.empty():
  programador.cancel(ev2)

  if estado:
  print("Iniciando ejecución...")
  else:
  print("Terminando ejecución...")

#Vamos a obtener un número aleatorio entre 1 y 5


# Si el valor es impar finalizará la ejecución

numero = random.randint(1, 5)

if numero %2:
  print("No terminar ejecución")
  continuar_programa = True
else:
  print("Terminar ejecución")
  continuar_programa = False

#Declarar el programador
programador = sched.scheduler(time.time, time.sleep)

comienzo = int(time.time())

t1 = comienzo + 1 #Tiempo para comenzar ejecución


t2 = t1 + 4 #Tiempo para terminar ejecución

print("Iniciando programador", comienzo)

# Vamos a definir la prioridad de dos eventos


# Indicamos el momento de ejecución, prioridad, función a la que se llama
# y valores que se pasan a los argumentos de la función

ev1 = programador.enterabs(t1, 1, ejecutar_programa, (1, ejecutar_programa))


ev2 = programador.enterabs(t2, 1, ejecutar_programa, (0, ejecutar_programa))

#Ponemos en marcha el programador

programador.run()
print("Programador finalizado", int(time.time()))

240
18.1. Lab: Planificando tareas con Python
18.1.1. Laboratorio 1

Escribe un script con un programador con tres eventos que ejecuten la misma tarea después de
esperar 1, 2 y 3 segundos respectivamente cada uno. Para ello debes usar los módulos sched y time.

Solución

import sched
import time

def tarea(nombre, inicio):


  tiempo_actual = time.time()
  diferencia = int(tiempo_actual - inicio)
  print('Hora :', int(tiempo_actual),
  'Diferencia:', diferencia, 'segundos',
  'Tarea:', nombre)

programador = sched.scheduler()
inicio = time.time()
print('inicio:', inicio)

programador.enter(1, 1, tarea, ('TAREA_1', inicio))


programador.enter(2, 1, tarea, ('TAREA_2', inicio))
programador.enter(3, 1, tarea, ('TAREA_3', inicio))
programador.run()

print('FINAL :', time.time())

18.1.2. Laboratorio 2

Escribe un script con un programador con tres eventos que ejecuten la misma tarea, el primer
evento se ejecutará después de un segundo, los otros dos eventos se ejecutaran en el mismo tiempo
(3 Segundos). Deberas establecer un la prioridad de estos dos últimos para que el tercero se ejecute
antes que el segundo.

Puedes formatear el tiempo de salida de esta manera '%d-%m-%Y %H:%M:%S'.

Solución

241
import sched
import time

def tiempo():
  formato = '%d-%m-%Y %H:%M:%S'

  # Obtiene fecha/hora local como tupla struct_time


  st_tiempo = time.localtime()

  # Convierte fecha/hora a segundos


  tiempo = time.mktime(st_tiempo)

  # Convierte fecha/hora a cadena


  str_tiempo = time.strftime(formato, st_tiempo)
  return tiempo, str_tiempo

def tarea(nombre, comienzo):


  ahora, str_ahora = tiempo()
  diferencia = int(ahora - comienzo)
  print('MOMENTO :', str_ahora,
  'Diferencia:', diferencia, 'segundos',
  'Tarea:', nombre)

programador = sched.scheduler(time.time, time.sleep)


comienzo, str_comienzo = tiempo()
print('COMIENZO:', str_comienzo)

programador.enter(1, 1, tarea, ('TAREA_1', comienzo))


programador.enter(3, 2, tarea, ('TAREA_2', comienzo))
programador.enter(3, 1, tarea, ('TAREA_3', comienzo))
programador.run()

final, str_final = tiempo()


print('FINAL :', str_final)

242
Capítulo 19. Envío de Email con Python
Una vez más nos ayudaremos de módulos específicos para el envío de correos electrónicos con
Python. En este caso vamos a usar los módulos smtplib y email.

• smtplib → Módulo de Python que define un objeto de sesión de cliente SMTP que se puede usar
para enviar un correo a cualquier máquina de internet con un daemon de escucha SMTP o
ESMTP.

• email → Módulo externo a Python, que nos permitirá añadir formatos a nuestro correo, así
como archivos o poder identificar al remitente, destinatario o asunto del mensaje. Dentro de
este módulo podemos encontrar las siguientes funciones:

◦ MIMEMultipart → Crea la instancia de un objeto mensaje vacío

◦ MIMEText → Nos permite definir el texto y su tipo

◦ MIMEImage → Nos pemirte incluir imágenes en el cuerpo del mensaje.

Vamos a ver como enviar un correo en el siguiente laboratorio

243
19.1. Lab: Enviando un mail con Python
Para poder realizar este laboratorio lo primero que vamos a hacer es abrir una cuenta en Gmail, en
la que activaremos la doble autenticación y generaremos una contraseña para aplicaciones.

Una vez que nos hayamos registrado debemos activar la verificación en dos pasos, para ello iremos
al menú de configuración de nuestra cuenta.

Cuando estemos en el menu debemos seleccionar la opción de Seguridad

Dentro de Seguridad debemos desplazarnos a "Iniciar sesión en Google"

244
Después de activar la doble verificación debemos generar nuestra contraseña para aplicaciones.
Para ello debemos elegir las siguientes opciones en los menús desplegables y pulsar el botón
general. Tras ello nos mostrara una contraseña que deberemos almacenar, ya que la usaremos mas
adelante.

Ahora que ya la tenemos configurada podemos empezar a trabajar con Python.

Vamos a enviar un pequeño email de prueba usando solo el módulo smtplib

245
import smtplib

# declaramos el smtp que vamos a usar, en nuestro caso google

smtp_address = 'smtp.gmail.com'
smtp_port = 587

# Definimos, nuestra direccion de envío, nuestra contraseña para app y el destino

email_address = 'dirección envio'


email_password = "contraseña para aplicaciones"
email_receiver = "dirección de destino"

# Creamos nuestro servidor con smtplib

with smtplib.SMTP(smtp_address, smtp_port) as server:


  server.starttls() #iniciamos starttls
  server.login(email_address, email_password) #nos logueamos

  server.sendmail(email_address, email_receiver,"Este es el contenido del correo") #enviamos el correo

Sí todo ha ido bien recibiremos un correo en la bandeja de entrada de la dirección indicada.

Es un correo demasiado sencillo, ¿verdad?. Podemos enriquecer nuestros mensajes gracias al


módulo email.

Lo primero sera añadir a nuestro código las funciones que vamos a utilizar.

from email.mime.text import MIMEText


from email.mime.multipart import MIMEMultipart
import smtplib

Por supuesto mantendremos la configuración del ejemplo anterior.

from email.mime.text import MIMEText


from email.mime.multipart import MIMEMultipart
import smtplib

smtp_address = 'smtp.gmail.com'
smtp_port = 587
email_address = 'dirección envio'
email_password = "contraseña para aplicaciones"
email_receiver = "dirección de destino"

A partir de aquí empezamos a crear nuestro mensaje

246
# Creamos la instancia del objeto menssage

msg = MIMEMultipart()

message = "Hola es un correo de prueba enviado con Python"

# Configuramos los parametros del mensaje


msg["FROM"] = email_address
msg['To'] = email_receiver
msg['Subject'] = "Prueba Curso"

# Añadimos nuestro mensaje al cuerpo del correo e indicamos el tipo de texto


msg.attach(MIMEText(message, "plain"))

#Creamos nuestro servidor


with smtplib.SMTP(smtp_address, smtp_port) as server:
  server.starttls()
  server.login(msg['FROM'], email_password)

  server.sendmail(msg['FROM'], msg['To'],msg.as_string())
  print(f"Correo enviado a {msg['To']}")

Lo siguiente sera añadir una imagen adjunta al mensaje. Para ello importaremos la función
MIMEImage

from email.mime.image import MIMEImage

Para poder adjuntar la imagen debemos abrirla como un archivo y usar .attch sobre nuestro
mensaje para añadirla

with open ("imagen.jpg", "rb") as file:


  msg.attach(MIMEImage(file.read()))

Nuestro código completo quedará de esta forma

247
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.mime.text import MIMEText
import smtplib

smtp_address = 'smtp.gmail.com'
smtp_port = 587
email_address = 'dirección envio'
email_password = "contraseña para aplicaciones"
email_receiver = "dirección de destino"

# Creamos la instancia del objeto menssage

msg = MIMEMultipart()

message = "Hola es un correo de prueba enviado con Python"

# Configuramos los parametros del mensaje


msg["FROM"] = email_address
msg['To'] = email_receiver
msg['Subject'] = "Prueba Curso"

# Añadimos nuestro mensaje al cuerpo del correo


msg.attach(MIMEText(message, "plain"))

with open ("imagen.jpg", "rb") as file:


  msg.attach(MIMEImage(file.read()))

#Creamos nuestro servidor


with smtplib.SMTP(smtp_address, smtp_port) as server:
  server.starttls()
  server.login(msg['FROM'], email_password)

  server.sendmail(msg['FROM'], msg['To'],msg.as_string())
  print(f"Correo enviado a {msg['To']}")

Ahora ya tenemos la imagen pero nuestro texto sigue siendo plano. Vamos a añadir texto en HTML
para enriquecer nuestro correo. Para ello creamos un string con nuestro código HTML

html = """<h2 style="text-align: left;">Correo de Prueba</h2>


  <div class="separator" style="clear: both; text-align: center;"><br /></div>Este es un correo de prueba\
  escrito en <b>HTML</b> para demostrar que Python puede soportar este leguaje en los <span style="color: #01ffff;">\
  <i><b>correos electrónicos.</b></i></span><div><span style="color: #01ffff;"><b><i><br /></i></b></span></div>
  <div>De esta forma ya podemos enviar un correo sin tener que estar limitados al texto plano.<br />\
  <div class="separator" style="clear: both; text-align: center;"><br /></div><br />\
  <div class="separator" style="clear: both; text-align: center;"><br /></div><br />\
  </div>"""

Finalmente lo añadiremos a nuestro mensaje

248
msg.attach(MIMEText(html,"html"))

De esta forma nuestro código quedará de esta manera:

from email.mime.multipart import MIMEMultipart


from email.mime.image import MIMEImage
from email.mime.text import MIMEText
import smtplib

smtp_address = 'smtp.gmail.com'
smtp_port = 587
email_address = 'dirección envio'
email_password = "contraseña para aplicaciones"
email_receiver = "dirección de destino"

# Creamos la instancia del objeto message

msg = MIMEMultipart()

message = "Hola es un correo de prueba enviado con Python"

# Configuramos los parametros del mensaje


msg["FROM"] = email_address
msg['To'] = email_receiver
msg['Subject'] = "Prueba Curso"

# Creamos un string con nuestro codigo

html = """<h2 style="text-align: left;">Correo de Prueba</h2>


  <div class="separator" style="clear: both; text-align: center;"><br /></div>Este es un correo de prueba\
  escrito en <b>HTML</b> para demostrar que Python puede soportar este leguaje en los <span style="color: #01ffff;">\
  <i><b>correos electrónicos.</b></i></span><div><span style="color: #01ffff;"><b><i><br /></i></b></span></div>
  <div>De esta forma ya podemos enviar un correo sin tener que estar limitados al texto plano.<br />\
  <div class="separator" style="clear: both; text-align: center;"><br /></div><br />\
  <div class="separator" style="clear: both; text-align: center;"><br /></div><br />\
  </div>"""

# Añadimos nuestro mensaje al cuerpo del correo


msg.attach(MIMEText(message, "plain"))
msg.attach(MIMEText(html,"html"))

with open ("imagen.jpg", "rb") as file:


  msg.attach(MIMEImage(file.read()))

#Creamos nuestro servidor


with smtplib.SMTP(smtp_address, smtp_port) as server:
  server.starttls()
  server.login(msg['FROM'], email_password)

  server.sendmail(msg['FROM'], msg['To'],msg.as_string())
  print(f"Correo enviado a {msg['To']}")

249
Capítulo 20. Librerias SciKit y OpenCV
20.1. SciKit
SciKit es el módulo de Python desarrollado para poder realizar analisis predictivos sobre nuestros
datos.

• Forma parte del ecosistema Python al estar construido sobre Numpy, SciPy y Matplotlib

• Al compartir con Pandas trabajar sobre Numpy tienen una buena interacción entre ambos.

• Amplio soporte por parte de la comunidad, lo cual ha permitido el desarrollo de varias


herramientas como es scikit-image para el tratamiento de imagenes.

• Es el estandar de facto en Python para machine learning.

20.1.1. Las ventajas de su nivel de abstracción

SciKit implementa una gran cantidad de algoritmos de machine learning, procesamiento de datos y
selección de modelos.

• Su implementación es lo suficientemente abstracta para que el usuario solamente necesite


aplicar cambios menores cuando cambie de un algoritmo a otro.

◦ Esta es una de sus características mas importantes, muchas veces tendremos que iterar
entre diferentes algoritmos mientras desarrollamos nuestros modelos para decidir cual es el
que mejor se ajusta.

◦ A la vez, esta abstracción no nos impide poder configurar los algoritmos, en resumén
podemos decir que tenemos el control absoluto de parametros y configuración.

SciKit nos permite trabajar los siguientes métodos sobre nuestros datos.

Clasificación

• Identificamos a que categoria pertenece un objeto

• Se puede aplicar a detección de spam o identificación de imagenes.

• Trabaja con algoritmos como SVM, nearest neighbors, random forest…

Regresión

• Predecimos los valores futuros a través de nuestros datos

250
• Lo podemos aplicar por ejemplo a evolución de precios

• Trabaja con algoritmos como SVR, nearest neighbors, random forest

Clustering

• Agrupamos automaticamente objetos similares en conjuntos

• Entre sus aplicaciones se encuentran la segmentación de clientes o la agrupación de resultados.

• Los algoritmos con los que trabaja, entre otros, son k-Means, spectral clustering, mean-shift

Reducción de dimensionalidad * Reducimos el número de variables aleatorias a considerar * Nos


interesa usarlo cuando queremos visualizar mejor los datos o incrementar la eficiencia * Entre sus
algoritmos están PCA o feature selection

251
Selección de modelos

• Comparación, validación y elección de parametros y modelos.

• Buscamos el mejor modelo usando diversos ajustes en los parametros

• Funciona con algoritmos como grid search, cross validation o metrics

Preprocesamiento

• Extracción y normalización de los datos

252
• Podemos preprocesar nuestros datos cuando por ejemplo queremos transformar nuestros
datos, por ejemplo un texto para su uso posterior con algoritmos de machine learning

• Lo conseguimos gracias a algoritmos como preprocessing o feature extraction entre otros.

Filtros Morfologicos

• Operaciones no lineales que nos ayudan al procesamiento de imágenes

• Lo usamos para conseguir bordes, esqueletos, identificar formas o resaltar zonas

• Podemos usar herramientas con algoritmos como rolling-ball, algoritmos de comparación de


segmentación

20.2. OpenCV
OpenCV es una libreria de Python diseñada para resolver problemas de visualización

• Hace uso de Numpy, todos las estructuras de tipo array de OpenCV pueden ser convertidas en

253
arrays de Numpy y viceversa.

• Es facil integrala en el ecosistema de Python

• Podemos usar OpenCV para realizar operaciones simples con imagénes:

◦ Abrir y guardar

◦ Dibujar formas simples en las imagenes

◦ Escribir en Imagenes

◦ Para entender bien como funciona OpenCV debemos pensar en las imagenes como Matrices

20.2.1. Una imagen, una matriz

Para nosotros una imagen es una matriz de estandar de Numpy que contiene píxeles de puntos de
datos:

• A mas píxeles mayor resolución de la imagen

• Cada pixel es un pequeño bloque de información con disposición de cuadricula 2D

• La profundidad del pixel se refiere a la información de color presente en ella

• Para poder procesar una imagen debemos pasarla a forma binaria, para ello necesitamos
calcular el color de una imagen mediante la siguiente formula:

Nº Colores/sombra = 2^bpp (bpp = bits por pixel)

• Según la formula anterior a mas cantidad de píxeles mas colores puede tener nuestra imagen.
Por ejemplo para 16 bits/pixel tendremos 65000 posibles colores.

20.3. Tipos de imágenes


20.3.1. Imágenes binarias

• Solo consta de 1 bit/pixel

• Solo podremos tener dos valores posibles:

◦ Blanco - valor 1

◦ Negro - valor 2

254
20.3.2. Escala de grises

• Consta de 8 bits/pixel

• Puede tener 256 "sombras" diferentes

• Sus valores son variables

◦ Blanco - valor 255

◦ Negro - valor 0

20.3.3. Imagen a color

• Son una combinzacion de los colores rojo, azul y verde. El resto de colores se consigue con la
mezcla de estos en distintas proporciones.

• Podemos representar 256 todos de color diferentes

• Sus valores son variables

◦ Blanco - valor 255

◦ Negro - valor 0

255
20.4. Comandos y herramientas de OpenCV
20.4.1. Comandos básicos

Para poder trabajar con OpenCV debemos importar el propio módulo y los relacionados con él.

import cv2
import matplotlib.pyplot as plt
import numpy as np

Todas las imagenes que vamos a usar en estos ejemplos estan en el directorios
workspace/python/python-scikit

• Leer una imagen Para leer una imagen usaremos la función imread (convierte la imagen en
un array de numpy)

new_img = cv2.imread("imagen")

De esta forma estamos transformando la imagen en un array de numpy. Con shape podemos
conocer sus dimensiones y canales

• m → dimension

• n → dimension

256
• c → nº de canales

new_img.shape
(m,n,c)

• Mostrar una imagen

Podremos visualizar la imagen cargada anteriormente haciendo uso de

plt.imshow(new_img)
plt.show()

Existe una discrepancia entre como usan los colores primarios OpenCV y
Matplotlib:

• OpenCV lee las imágenes en forma BGR

• Matplotlib lee las imágenes en orden RGB.

 Para solventar esto usamos la funcion cvtColor transformando el canal a la forma


en que los espera Matplotlib

new_img = cv2.cvtColor(new_ing, cv2.COLOR_BRG2RGB)


plt.imshow(new_img)

• Guardar una imagen

Podemos guardar la imagen en el directorio en el que estemos trabajando con la función imwrite

cv2.imwrite('image_name.png',new_img)

Donde:

• image_name es el nombre de la imagen que se guardará.

20.4.2. Dibujando en imagenes

Podemos dibujar formas sobre imágenes ya creadas:

cv2.forma(line, rectangle etc)(imagen, Pt1, Pt2, color, grosor)

Donde:

• forma → line, rectangle, circle

• imagen → Imagen sobre la que dibujaremos las formas

257
• coordendas → Se dibuja desde Pt1 (Esquina superior izquierda) hasta Pt2(Esquina inferior
derecha)

• color → Color para la forma. Debemos pasarlo como una tupla por ejemplo (150,0,0). Si usamos
escala de grises será la escala de brillo

• grosor → Grosor de la figura geométrica.

20.4.3. Escribiendo en imágenes

Agregar texto a las imágenes es muy parecido a dibujar formas. Pero antes de hacerlo debemos
indicar algunos argumentos antes de empezar

• Texto que queremos escribir

• Coordenadas donde queremos situar el texto como tupla

• Tipo de fuente y escala

• Grosor, tipo de línea.

font = cv2.FONT_HERSHEY_SIMPLEX
text = cv2.putText(img, 'texto',(10,500), font, 4,(255,255,255),2,cv2.LINE_AA)
plt.imshow(text)
plt.show()

20.5. Scikit-Image
Scikit-image es una colección de algoritmos para el procesamiento de imágenes.

Las imagenes en scikit-image están representadas como un array de numpy, por lo tanto muchas de
las modíficaciones que podemos realizar sobre ellas las podemos hacer usando simplemente las
funciones que está libreria pone a nuestra disposición.

Es una libreria externa a Python por lo tanto lo primero que debemos hacer es instalarla:

pip install scikit-image

Una vez instalada, es necesario importarlo:

import skimage

Como deciamos las imagenes son interpretadas como un array de numpy, podemos comprobarlo
con una de las imagenes de ejemplo que incluye la libreria, camera

from skimage import data


camera = data.camera()

258
Podemos comprobar como el objeto generado es un array de numpy

type(camera)

numpy.ndarray

Es importante, como en los casos anteriores conocer las dimensiones y el tamaño de nuestro array:

camera.shape
(512,512)

camera.size

262144

Hay que tener en cuenta que las dimensiones sigue las reglas de las matrices,
 origen (0,0) estará en esquina superior izquierda

Este es el aspecto original de nuestra imagen

Vamos a empezar a realizar modificaciones sobre ella simplemente usando numpy

• Primero vamos a crear una mascara que comprenda todos los elementos del array cuyo valor
sea menor a 87, y la vamos a aplicar a nuestra imagen para convertir todos esos elementos a

259
"blanco" (255)

mask = camera <87


camera[mask] = 255
camera = cv2.cvtColor(camera, cv2.COLOR_BGR2RGB)
plt.imshow(camera)

• Este será el resultado de nuestra primera modificacion

• Vamos a añadir mas modificaciones al código anterior. Vamos a convertir a negro los elementos
que cumplan las condiciones dadas para cada dimensión.

camera = data.camera()
type(camera)
camera = cv2.cvtColor(camera, cv2.COLOR_BGR2RGB)
mask = camera <87
camera[mask] = 255
inds_r = np.arange(len(camera))
inds_c = 4 * inds_r % len(camera)
camera[inds_r,inds_c]=0

• Esta modificacion hara que nos aparezcan lineas diagonales de color negro en la imagen

260
• Por último vamos a crear un marco negro circular usando la ecuación de la circunferencia

from skimage import data


camera = data.camera()
mask = camera <87
camera[mask] = 255
inds_r = np.arange(len(camera))
inds_c = 4 * inds_r % len(camera)
camera[inds_r,inds_c]=0
nrows, ncols = camera.shape
row,col = np.ogrid[:nrows,:ncols]
cnt_row = nrows / 2
cnt_col = ncols /2
outer_disk_mask = ((row - cnt_row)**2 + (col - cnt_col)**2 > (nrows / 2)**2)
camera[outer_disk_mask] = 0
camera = cv2.cvtColor(camera, cv2.COLOR_BGR2RGB)
plt.imshow(camera)
plt.show()

Este será el resultado final

261
262
20.6. Lab: SciKit y OpenCV
Antes de comenzar el laboratorio es necesario instalar el módulo OpenCV

pip install opencv-pythoncv2

20.6.1. Laboratorio: Operaciones simples en OpenCV

Usando openCV abre y visualiza, con los colores correctos, la imagen muestra-01.png que
encontraras en directorio workspace/python-scikit.

Guardala como imagen_ok.png

import numpy as np
import cv2
import matplotlib.pyplot as plt

new_img = cv2.imread("/home/python/workspace/python-scikit/muestra-01.png")
new_img = cv2.cvtColor(new_img, cv2.COLOR_BGR2RGB)
cv2.imwrite('imagen_ok.png',new_img)
plt.imshow(new_img)
plt.show()

263
20.6.2. Laboratorio: Formas y texto sobre una imagen

Crea una imagen completamente negra a través de numpy y openCV de 500x500 pixeles, dibuja
sobre ella:

• Una diagonal azul que recorra la imagen desde la esquina inferior izquierda a la esquina
superior derecha. (color:(0,0,255))

• Un rectangulo verde en la esquina inferior izquierda. (color:(0,255,0))

• Un circulo amarillo dentro del rectangulo anterior. (color:(255,255,0))

• Escribe "Python" en la parte inferior izquierda.

• El resultado debe ser parecido a la siguiente imagen:

264
import numpy as np
import matplotlib.pyplot as plt
import cv2

# Definimos la imagen 500x500

black_img = np.zeros(shape = (500,500,3), dtype=np.int16)


plt.imshow(black_img)

# Dibujamos la diagonal azul

blue_line = cv2.line(black_img,(500,0),(0,500),(0,0,255),5)
plt.imshow(blue_line)

# Dibujamos el rectangulo verde

green_rectangle = cv2.rectangle(black_img,(0,300),(200,500),(0,255,0),5)
plt.imshow(green_rectangle)

plt.show()

# Dibujamos el circulo amarillo

yellow_circle = cv2.circle(black_img,(100,400),100,(255,255,0), 2)
plt.imshow(yellow_circle)

2
plt.show()

20.6.3. Modificando una imagen con skimage

Para este laboratorio vamos a usar la imagen muestra-02.jpg la puedes encontrar en el directorio
workspace/python-scikit

• Convierte la imagen a un array de numpy y redimensionala.

◦ Ayuda para ello puedes usar la función cv2.resize como se muestra a continuancion:

265
import cv2
import skimage

img_new = skimage.io.imread("muestra-02.jpg")

# Vamos a redimensionar la imagen haciendo uso de la funcion cv2.resize

scale_percent = 50 #Porcentaje de redimensión de la imagen

width = int(img_new.shape[1] * scale_percent /100)


height = int(img_new.shape[0] * scale_percent / 100)

dsize = (width, height)

output = cv2.resize(img_new, dsize)

cv2.imwrite("muestra-02-resize.png", output)

• Una vez redimensionada el resultado final debe ser parecido a este:

266

También podría gustarte