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

Fundamentos_Python_v0.1.4

El documento es un módulo de aprendizaje sobre Python, orientado a la programación básica y su aplicación en Big Data y Data Science. Incluye temas como tipos de datos, control de flujo, funciones, manejo de excepciones, y librerías como Pandas y NumPy. Se enfatiza en la práctica gradual y efectiva, con ejercicios para facilitar el aprendizaje desde el inicio.

Cargado por

jmgarcia228
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)
10 vistas

Fundamentos_Python_v0.1.4

El documento es un módulo de aprendizaje sobre Python, orientado a la programación básica y su aplicación en Big Data y Data Science. Incluye temas como tipos de datos, control de flujo, funciones, manejo de excepciones, y librerías como Pandas y NumPy. Se enfatiza en la práctica gradual y efectiva, con ejercicios para facilitar el aprendizaje desde el inicio.

Cargado por

jmgarcia228
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/ 172

Master Big Data y Data Science

Aplicaciones al comercio, empresa y


finanzas

Fundamentos de programación en
Python

Javier Domínguez Gómez


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

v 0.1.4, Actualizado en diciembre de 2020


Tabla de contenido
Introducción 6

Qué es Python 6

Plataformas de desarrollo 6

Tipos de datos 7

Variables y constantes 7

Números enteros y flotantes (decimales) 7

Cadenas de carácteres o strings 8

Listas 12

Operadores y expresiones 18

Operadores relacionales 18

Operadores lógicos 20

Expresiones anidadas 21

Operadores de asignación 24

Control de flujo 26

Sentencia if 26

Sentencia while 30

Sentencia for 34

Colecciones 38

Tuplas 39

Conjuntos 41

Diccionarios 45

Pilas 49

2
Colas 50

Entradas y salidas de datos 52

Entrada de datos por teclado 52

Entrada de datos por argumentos 54

Salida de datos 56

Funciones 60

Definición de funciones 60

Retorno de valores 63

Enviando valores 65

Argumentos y parámetros 66

Pasar información por valor y por referencia 67

Argumentos indeterminados 70

Funciones recursivas 71

Funciones integradas en Python 72

Manejo de excepciones 76

Errores 76

Excepciones 79

Múltiples excepciones 81

Invocación de excepciones 84

Clases y objetos 84

Programación estructurada Vs. POO 85

Clases y objetos 91

Atributos y métodos de clase 92

Métodos especiales 96

Objetos dentro de objetos 97

3
Encapsulación de atributos 99

Herencia 101

Clases heredadas 105

Herencia múltiple 109

Métodos de las colecciones 110

Métodos en cadenas de texto 111

Métodos en listas 114

Métodos en conjuntos 116

Métodos en diccionarios 119

Módulos y paquetes 120

Módulos 120

Collections 122

Datetime 124

Math 127

Random 129

Paquetes 132

Manejo de ficheros 133

Creación 134

Apertura o lectura 135

Modificación 136

Manejo del puntero 136

Ficheros y objetos con pickle 138

Pandas 139

Importación de Pandas 139

Ordenación de los datos 141

4
Visualización con Matplotlib 142

Ejemplo Standard & Poor's 500 146

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

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

Conceptos estadísticos básicos 158

Variables discretas y continuas 158

Cálculo de la media y de la mediana 158

Varianza y desviación de una variable 161

NumPy 165

Importación de NumPy 166

Ejemplo Índice de Masa Corporal (IMC) 167

Ejemplo cálculo áreas de triángulos 168

Arrays de dos dimensiones en NumPy 169

Cálculo estadístico con NumPy 170

Generación de datos random con NumPy 171

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

Qué es Python
Python es un lenguaje de programación interpretado con licencia de código
abierto y con una curva de aprendizaje muy sencilla. Se creó a finales de los
años 80 por Guido Van Rossum. Actualmente es uno de los lenguajes más
populares y utilizados en la industria tecnológica. Tiene una sintaxis que hace
que el código sea muy legible. Soporta la Orientación a Objetos y
programación imperativa y en menor medida programación funcional. Para la
realización de este módulo se ha utilizado Python en su versión 3.

Plataformas de desarrollo
Se recomienda usar cualquier editor de texto, pero existen IDEs como ​Atom​,
Sublime o ​Visual Studio Code que nos permitirán programar en Python con
mayor facilidad, ya que disponen de varias herramientas que nos pueden
servir para comprobar la sintaxis y detectar los posibles errores del código
automáticamente. Para el aprendizaje del lenguaje es altamente recomendable
utilizar algún entorno de desarrollo como ​Anaconda​, ya que incorpora una
aplicación web llamada ​Jupiter Notebook que nos puede venir muy bien para
practicar y realizar pruebas con nuestro código de una manera muy fácil y
sencilla.

El alumno deberá traer un ordenador portátil a clase, y será necesario tener


instalado ​Python 3 (preferiblemente versiones ​3.7 o superior) en el sistema
operativo y como mínimo ​Anaconda con ​Jupiter Notebook​. Sin estas
plataformas o software no será posible realizar los ejercicios que iremos
viendo en clase, por lo que es obligatorio este requisito.

6
Tipos de datos

Variables y constantes

En Python, como en la mayoría de lenguajes de programación, se puede


asignar un tipo de dato a una variable o una constante. Ambos son un nombre
o alias que almacenará un tipo de dato en memoria durante la ejecución del
programa. La diferencia técnica es que el valor de una variable puede variar
durante la ejecución, sin embargo el valor de una constante no debería
cambiar.

Los nombres de variables en Python solo admite caracteres alfanuméricos y el


guión bajo "_", no pueden empezar por un número y se suelen escribir en
minúsculas. Si el nombre de una variable se compone de varias palabras es
una buena práctica separarlas por un guión bajo:

marca_de_coche ​=​ ​'Tesla'

También se suele utilizar un sistema de escritura denominado ​camelcase,​ en el


que las palabras se escriben todas juntas y solo se escribe en mayúscula la
primera letra de cada palabra, empezando siempre en minúscula:
cincoNumerosPrimos ​=​ [​2​, ​3,
​ ​5​, 7
​ ​, ​11​]

En cambio las constantes se suelen escribir con todas las letras mayúsculas y
si se componen de varias palabras, estas separadas por guión bajo:

NUMERO_PI​ ​=​ ​3.14159

A continuación veremos los diferentes tipos de datos que se le pueden asignar


como valor a una variable o constante.

Números enteros y flotantes (decimales)

Hay un tipo de datos para los números, concretamente para los números
enteros y los flotantes o decimales. El intérprete de Python permite realizar con
este tipo de datos cálculos simples como sumar, restar, multiplicar, dividir,
operaciones módulo o potencias. Algunos ejemplos:

>>>​ suma ​=​ ​1​+​2


>>>​ suma
3
>>>​ resta ​=​ ​7​-​2
>>>​ resta
5

7
>>>​ resta_negativa ​=​ ​5-
​ ​9
>>>​ resta_negativa
-​4
>>>​ multiplicacion ​=​ ​3*
​ ​4
>>>​ multiplicación
12
>>>​ multiplicacion_decimal ​=​ ​0.3​*​4
>>>​ multiplicacion_decimal
1.2
>>>​ division ​=​ ​28​/​7
>>>​ division
4.0
>>>​ operacion_modulo ​=​ ​25​%5

>>>​ operacion_modulo
0
>>>​ potencia ​=​ ​3​**​2
>>>​ potencia
9
Se pueden realizar operaciones concatenadas más complejas. El orden
correcto siempre será de izquierda a derecha, primero las multiplicaciones y
divisiones, y luego las sumas y restas. Python se encargará de respetar el
orden de los operadores automáticamente, por ejemplo:

>>>​ operaciones_seguidas ​=​ ​3​-​2+


​ ​4​*​10
>>>​ operaciones_seguidas
41

Como se puede ver, primero ha realizado la operación de multiplicación ​4*10​,


que da como resultado ​40​, luego la operación resta ​3-2​, que da como
resultado 1​ ​, y finalmente la suma ​1+40​, que da como resultado ​41​.

Y por supuesto se puede hacer operaciones con tan solo el nombre de las
variables, de modo que se utilizará los valores que estas tengan almacenado
en memoria.

>>>​ numero_a ​=​ 5 ​


>>>​ numero_b =​ ​ 3 ​
>>>​ numero_a * ​ ​ numero_b
15

Cadenas de carácteres o strings

Otro tipo de datos son las cadenas de caracteres o ​string.​ Estas cadenas se
pueden escribir dentro de unas comillas dobles (") o simples ('), por ejemplo:

>>>​ ​'Hola mundo!'


'Hola mundo!'
>>>​ ​"Hola mundo!"
'Hola mundo!'

8
Los dos ejemplos anteriores muestran un resultado por pantalla cuando se
escribe una cadena de texto entre las comillas dobles o simples en el
intérprete de Python, pero para poder tratar mejor estas cadenas de
carácteres usaremos una función propia de Python llamada ​print()​. No solo
nos permitirá mostrar por pantalla el mensaje de texto que estamos
escribiendo, si no que además es capaz de interpretar caracteres especiales
como son las tabulaciones \​ t​, los saltos de línea \​ n​ y otros muchos más, de
momento veremos estos dos, por ejemplo:

>>>​ ​print​(​'Hola\tmundo!'​)
Hola mundo!
>>>​ ​print​(​'Hola\nmundo!'​)
Hola
mundo!

La función ​print()​ permite indicar si se quieren procesar o no los caracteres


especiales como las tabulaciones o saltos de línea que hemos visto antes, ya
que podría darse la casualidad de que nuestra cadena de texto incluya entre
los carácteres \​ n​ o cualquier otro caracter especial. En este caso se le puede
indicar que se quiere mostrar la cadena de texto en crudo o ​raw​ añadiendo
una letra r​ ​ delante de las comillas simples o dobles que van dentro del
paréntesis de la función p​ rint()​, véase el ejemplo:

>>>​ ​print​(​r​'C:​\n​ombre​\d​e​\u​n​\d​irectorio'​)
C:\​nombre\de\un\directorio
Otra opción es escapar los caracteres que no queremos que se procesen, para
ello se utiliza la contrabarra o ​backslash​ ​\​, véase el ejemplo:

>>>​ ​print​(​'C:\\nombre\\de\\un\\directorio'​)
C:\​nombre\de\un\directorio

Dentro de la función ​print()​ también se pueden realizar algunas operaciones


con este tipo de dato de cadenas de carácteres, por ejemplo multiplicar un
número de veces una cadena de texto como ​Hola​:

>>>​ ​print​(​'Hola'​*​3​)
HolaHolaHola

O sumar dos cadenas, en realidad no se realiza una suma algebraica, si no una


concatenación de una cadena con otra, véase el ejemplo:

>>>​ ​print​(​'Hola'​ +
​ ​ '​ mundo'​)
Holamundo
>>>​ ​print​(​'Hola'​ +​ ​ '​ '​ ​+​ ​'mundo'​)
Hola mundo

9
Para trabajar más cómodamente y poder estudiar algunas de las opciones que
permite realizar Python con las cadenas de texto o ​strings​ usaremos variables.

Por ejemplo, se puede acceder fácilmente a un carácter en concreto de una


cadena de carácteres, pues estos están colocados en una posición de la
cadena. En este ejemplo la variable ​nombre​ contiene como valor una cadena de
carácteres ordenados mediante el siguiente índice:

>>>​ nombre ​=​ ​'Javier'


J a v i e r
| | | | | |
[0] [1] [2] [3] [4] [5]

Accederemos a ellos mediante el uso de índices en el que se indicará la


posición del carácter a mostrar, véase el siguiente ejemplo:

>>>​ nombre[​0​] ​# Carácter en la posición 0


'J'
>>>​ nombre[​1​] ​# Carácter en la posición 1
'a'
>>>​ nombre[​3​] ​# Carácter en la posición 3
'i'
>>>​ nombre[​4​] ​# Carácter en la posición 4
'e'

También existen los índices negativos, por ejemplo para acceder al último
carácter de la cadena sin que sea necesario conocer la posición del mismo:

>>>​ nombre[​-​1​] #
​ Último caracter de nombre
'r'
>>>​ nombre[​-​2​] #​ Penúltimo caracter de nombre
'e'
>>>​ nombre[​-​3]​ # ​ Antepenúltimo caracter de nombre
'i'

También existe la posibilidad de extraer porciones de cadenas de texto, se


consigue mediante una opción de los índices llamada ​slicing​, en la que se ha
de indicar dentro de los corchetes una posición de inicio, seguido del símbolo
de dos puntos ":" y una posición de final. Por ejemplo, para extraer solo los
tres primeros carácteres de la variable n​ ombre​ de ha indicar la posición ​0​, que
es la primera, dos puntos :​ ​ y el carácter en el que queremos que se pare y no
muestre, en este caso es el cuarto carácter, es decir la posición 3​ ​:

>>>​ nombre[​0​:​3​]
'Jav'

10
Como se puede ver, el último caracter indicado, en el ejemplo anterior el ​3​,
nunca se incluye. Pero si queremos incluir el último caracter bastaría con no
indicar nada, en este otro ejemplo se puede ver cómo extraer los carácteres
desde el tercero hasta el último:

>>>​ nombre[​2​:]
'vier'

Y también se puede no indicar ningún caracter de inicio, por ejemplo, para


mostrar desde el inicio de la cadena hasta el tercer carácter:
>>>​ nombre[:​2​]
'Ja'
O bien todos los elementos de la cadena menos el último o los dos últimos:

>>>​ nombre[:​-​1​]
'Javie'
>>>​ nombre[:​-​2​]
'Javi'

La variable ​nombre​ contiene una cadena de 6 carácteres, eso es un índice que


va desde el ​0​ hasta el ​5,​ pero ¿Qué sucederá si indicamos índices mayores de
los disponibles? Por ejemplo, desde el índice ​21​ hasta el final:

>>>​ nombre[​21​:]
''

En este caso no muestra nada, pues no existe nada desde la posición ​21​ del
índice hasta un final. Pero y ¿si indicamos que nos muestre desde el inicio de
la cadena hasta la posición ​21​?

>>>​ nombre[:​21​]
'Javier'

En este otro caso mostrará toda la cadena, pues a pesar de que el índice no
tiene ​21​ posiciones muestra todas las disponibles.
Ahora que ya se conoce el funcionamiento de las cadenas de carácteres y el
acceso a cada elemento de la cadena mediante índices, es posible que el
alumno se pregunte si es posible modificar el valor de algún elemento de la
cadena de carácteres. Para el tipo de dato ​string​ o cadena de carácteres no es
posible hacerlo directamente, véase un ejemplo y el error que devuelve:

>>>​ nombre[​0​] ​=​ ​'X'


Traceback (most recent call last):
File ​"<stdin>"​, line ​1​, ​in​ ​<m
​ odule​>
TypeError​: ​'str'​ ​object​ does ​not​ support item assignment

11
Como se puede ver, el error es claro, indica que para el tipo de dato ​str​ (​string)​
no es posible la asignación de valores a un ​item​ o elemento de la cadena. Pero
si queremos modificar solo el primer carácter de la cadena '​ J'​ por el
carácter '​ X'​ es posible mediante la siguiente operación:

>>>​ nombre ​=​ ​'X'​ ​+​ nombre[​1​:]


>>>​ nombre
'Xavier'

Lo que ha sucedido es que se ha reasignado un valor completo a la


variable ​nombre​. En este caso hemos indicado que el nuevo valor será el
carácter '​ X'​ seguido de todos los carácteres que tiene la variable ​nombre​ antes
de la reasignación de valor, pero solo desde el segundo carácter hasta el final,
es decir el índice con ​slice​ [​ 1:]​.

El tipo de datos ​string​ o cadena de carácteres permite utilizar una función de


Python llamada ​len()​ a la que se le debe indicar entre los paréntesis la cadena
de carácteres, de modo que devuelve la longitud en número de carácteres.
Véase algunos ejemplos:

>>>​ nombre ​=​ ​'Javier'


>>>​ apellidos ​=​ ​'Domínguez'
>>>​ ​len​(nombre)
6
>>>​ ​len​(apellidos)
9

La función ​len()​ no está disponible para todos los tipos de datos, más
adelante veremos en qué otras ocasiones se puede utilizar.

Listas

Las listas son un tipo de datos en Python que permite agrupar diferentes
elementos o ​items​, e incluso siendo estos de diferentes tipos de datos, como
carácteres, números enteros y flotantes, cadenas de caracteres, e incluso
otras listas. Se definen entre corchetes "​[]​" y sus elementos han de ir
separados por una coma.
Ejemplo de una lista en la que todos sus elementos son números enteros:

>>>​ numeros ​=​ [​3​, ​-​9​, ​12​, ​1​, ​-2


​ 7​]
>>>​ números
[​3​, ​-​9​, ​12​, 1
​ ​, ​-​27​]

Ejemplo de lista con todos sus elementos de tipo cadena de caracteres


o ​string:​
>>>​ caracteres ​=​ [​'a'​, '
​ b'​, ​'c'​, ​'d'​, ​'Hola'​]

12
>>>​ caracteres
[​'a'​, ​'b'​, ​'c'​, ​'d'​, ​'Hola'​]

En este ejemplo se muestra una lista con varios tipos de datos diferentes:

>>>​ lista_variada ​=​ [​1, ​ ​'Hola'​, ​-​27​, ​'b'​, ​3.14​]


>>>​ lista_variada
[​1​, ​'Hola'​, ​-​27​, ​'b'​, ​3.14​]

E incluso se puede hacer una lista en la que sus elementos sean también
listas, esto se conoce como listas anidadas:
>>> lista_de_listas ​= [[​3, ​ ​-​9​], [​'a'​, '
​ Hola'​], [​0.25​, ​3.14​], [​3​, ​'b'​, ​15.9​]]
>>>​ lista_de_listas
[[​3​, ​-​9​], [​'a'​, ​'Hola'​], [​0.25​, ​3.14​], [​3​, ​'b'​, ​15.9​]]

Al igual que en el tipo de datos ​string​ o cadenas de carácteres también se


puede acceder a cada elemento de una lista indicando la posición del
elemento mediante un índice. Por ejemplo, para mostrar el primer elemento de
la una lista basta con indicar el índice ​[0]​, véase el ejemplo:

>>>​ letras ​=​ [​'a'​, ​'b'​, ​'c'​, ​'d'​, ​'e'​]


>>>​ letras[​0​]
'a'

Para mostrar toda la lista menos el último elemento:

>>>​ letras[:​-​1​]
[​'a'​, ​'b'​, ​'c'​, ​'d'​]

O bien toda la lista menos los dos últimos elementos:

>>>​ letras[:​-​2​]
[​'a'​, ​'b'​, ​'c'​]

También los elementos que van desde el tercero hasta el final:

>>>​ letras[​2​:]
[​'c'​, ​'d'​, ​'e'​]

O los elementos que van desde el segundo hasta el cuarto (el elemento
indicado en el final del ​slice​ no se muestra):

>>>​ letras[​1​:​4​]
[​'b'​, ​'c'​, ​'d'​]

Como se puede ver, el comportamiento del ​slicing​ es exactamente el mismo


que cuando lo usábamos en las cadenas de carácteres. Anteriormente vimos
cómo acceder a una posición del índice en una cadena de carácteres, y vimos

13
que no era posible modificar un elemento de la cadena de carácteres mediante
la reasignación de valor a su índice. En el caso de las listas si es posible, por
ejemplo:

>>>​ pares ​=​ [​0​, ​2​, ​5​, ​6​, ​8]



>>>​ pares[​2​] ​=​ ​4
>>>​ pares
[​0​, ​2​, ​4​, ​6​, ​8​]

También se pueden modificar varios elementos de la lista mediante un ​slicing​,


en este caso queremos cambiar las 3 primeras letras de una lista de letras:
>>>​ letras ​=​ [​'a'​, ​'b'​, ​'c'​, ​'d'​, ​'e'​, ​'f'​]
>>>​ letras[:​3​]
[​'a'​, ​'b'​, ​'c'​]
>>>​ letras[:​3​] ​=​ [​'A'​, ' ​ B'​, ​'C'​]
>>>​ letras
[​'A'​, ​'B'​, ​'C'​, ​'d'​, ​'e'​, ​'f'​]

Todos los elementos de una lista tienen un orden, al que se puede acceder
mediante índices tal y como ya hemos visto. Para añadir nuevos elementos a
la lista existe un método ​append()​ que nos permite incorporar nuevos
elementos al final de la lista, por ejemplo:

>>>​ frutas ​=​ [​'fresa'​, '​ pera'​, '


​ uva'​]
>>>​ frutas.append(​'manzana'​)
>>>​ frutas
[​'fresa'​, ​'pera'​, ​'uva'​, ​'manzana'​]

Para eliminar varios elementos de una lista se puede hacer asignándole un


valor de lista vacía "​[]​" a un rango de elementos mediante ​slicing,​ por ejemplo,
si queremos eliminar los tres primeros elementos de la lista:

>>>​ letras ​=​ [​'a'​, ​'b'​, ​'c'​, ​'d'​, ​'e'​, ​'f'​]


>>>​ letras[:​3​] ​=​ []
>>>​ letras
[​'d'​, ​'e'​, ​'f'​]

En caso de que queramos borrar todos los elemento de la lista basta con
asignar el valor de la lista con una lista vacía:

>>>​ letras ​=​ [​'a'​, ​'b'​, ​'c'​, ​'d'​, ​'e'​, ​'f'​]


>>>​ letras =​ ​ []
>>>​ letras
[]

Si lo que se quiere es borrar un elemento concreto se puede hacer utilizando la


instrucción ​del​ (​delete​) de la siguiente manera:

14
>>>​ letras ​=​ [​'a'​, ​'b'​, ​'c'​, ​'d'​, ​'e'​, ​'f'​]
>>>​ ​del​ letras[​2​]
>>>​ letras
[​'a'​, ​'b'​, ​'d'​, ​'e'​, ​'f'​]

En este caso se ha eliminado el tercer elemento de la lista, es decir, el


carácter ​'c'​. Esta instrucción ​del​ también permite eliminar varios elementos
mediante ​slicing,​ por ejemplo, para eliminar el tercer y cuarto elemento de la
lista:

>>>​ letras ​=​ [​'a'​, ​'b'​, ​'c'​, ​'d'​, ​'e'​, ​'f'​]


>>>​ ​del​ letras[​2​:​4​]
>>>​ letras
[​'a'​, ​'b'​, ​'e'​, ​'f'​]

Volvamos a las listas anidadas, es decir, aquellas listas que sus elementos son
a su vez otras listas. Veamos cómo se pueden construir y cómo se puede
acceder a sus datos mediante múltiples índices, por ejemplo:

>>>​ lista_1 ​=​ [​0​, ​1​, ​2] ​


>>>​ lista_2 ​=​ [​3​, ​4​, ​5] ​
>>>​ lista_3 ​=​ [​6​, ​7​, ​8] ​
>>>​ lista_anidada ​=​ [lista_1, lista_2, lista_3]
>>>​ lista_anidada
[[​0​, ​1​, ​2​], [​3​, ​4​, ​5​], [​6​, 7
​ ​, ​8​]]

Para acceder al primer elemento de ​lista_anidada​ basta con indicar el


índice ​[0]​.

>>>​ lista_anidada[​0​]
[​0​, ​1​, ​2​]

Pero para acceder a uno de los elementos de este primer elemento de la


lista ​lista_anidada​ se ha de indicar un segundo índice, donde se indicará la
posición del elemento a que se quiere acceder, por ejemplo, para acceder al
primer elemento de la primera lista:

>>>​ lista_anidada[​0​][​0]

0

Y para acceder al primer elemento de la segunda lista:

>>>​ lista_anidada[​1​][​0]

3

A continuación veremos algunos de los métodos que se pueden utilizar con las
listas. Por ejemplo, el método ​len()​ devolverá el tamaño de la lista, es decir, el
número de elementos que tiene, véase el ejemplo:

15
>>>​ lista ​=​ [​'Dennis'​, '
​ Ken'​, ​'Richard'​]
>>>​ ​len​(lista)
3

El método ​count()​ devolverá el número de veces que existe un elemento


dentro de una lista, por ejemplo:

>>>​ mascotas ​=​ [​'perro'​, ​'gato'​, ​'canario'​, ​'gato'​, ​'hurón'​]


>>>​ mascotas.count(​'gato'​)
2

El método ​index()​ devuelve el índice menor en el que se encuentra un


elemento dentro de una lista, por ejemplo:

>>>​ mascotas ​=​ [​'perro'​, ​'gato'​, ​'canario'​, ​'gato'​, ​'hurón'​]


>>>​ mascotas.index(​'gato'​)
1
>>>​ mascotas.index(​'hurón'​)
4

El método ​insert()​ permite añadir un nuevo elemento a una lista, y además


permite hacerlo en la posición del índice que le especifiquemos. Lo que
sucederá con el resto de elementos que se encontraban en esa posición del
índice en adelante es desplazarnos, véase el ejemplo en el que se añade una
nueva mascota en la cuarta posición del índice:

>>>​ mascotas.insert(​3​, ' ​ iguana'​)


>>>​ mascotas
[​'perro'​, ​'gato'​, ​'canario'​, ​'iguana'​, ​'gato'​, ​'hurón'​]

El método ​pop()​ elimina y devuelve un elemento de la lista. Si no se le pasa


ningún argumento tendrá en cuenta el último elemento de la lista, y si se indica
una posición del índice lo hará con el elemento indicado, véase el ejemplo:

>>>​ mascotas.pop()
'hurón'
>>>​ mascotas
[​'perro'​, ​'gato'​, ​'canario'​, ​'iguana'​, ​'gato'​]
>>>​ mascotas.pop(​1​)
'gato'
>>>​ mascotas
[​'perro'​, ​'canario'​, ​'iguana'​, '​ gato'​]

El método ​reverse()​ invertirá el orden de los elementos de una lista según su


índice, por ejemplo:

​ canario'​, ​'iguana'​, ​'gato'​]


>>>​ mascotas [​'perro'​, '

16
>>>​ mascotas.reverse()
>>>​ mascotas
[​'gato'​, ​'iguana'​, ​'canario'​, ​'perro'​]

El método ​sort()​ ordenará los elementos de una lista, de mayor a menor o


alfabéticamente, por ejemplo:

>>>​ mascotas.sort()
>>>​ mascotas
[​'canario'​, ​'gato'​, ​'iguana'​, ' ​ perro'​]
>>>​ numeros ​=​ [​23​, ​48​, 5 ​ ​, ​19​, 7​ 1​, ​9​]
>>>​ numeros.sort()
>>>​ números
[​5​, ​9​, ​19​, ​23​, ​48​, ​71​]

A continuación se explicarán algunas operaciones básicas que se pueden


realizar con las listas. Por ejemplo, se pueden sumar varias listas con el
operador suma +​ ​:

>>>​ lista_1 ​=​ [​'a'​, ​'b'​, ​'c'​]


>>>​ lista_2 ​=​ [​'d'​, ​'e'​, ​'f'​]
>>>​ lista_1 ​+​ lista_2
[​'a'​, ​'b'​, ​'c'​, ​'d'​, ​'e'​, ​'f'​]

También admite el operador multiplicación ​*​ para multiplicar elementos de una


lista:

>>>​ lista ​=​ [​'Hola'​] ​*​ ​3


>>>​ lista
[​'Hola'​, ​'Hola'​, ​'Hola'​]
>>>​ lista ​=​ [​'a'​, ​'b'​, ' ​ c'​] ​*​ 4

>>>​ lista
[​'a'​, ​'b'​, ​'c'​, ​'a'​, ​'b'​, ​'c'​, ' ​ a'​, ​'b'​, ​'c'​, ​'a'​, ​'b'​, ​'c'​]

También permite averiguar si un elemento existe dentro de una lista mediante


el uso de ​in​, véase el ejemplo:

>>>​ lista ​=​ [​'rojo'​, ​'verde'​, ​'morado'​]


>>>​ ​'verde'​ ​in​ lista
True
>>>​ ​'azul'​ ​in​ lista
False

Y cómo no, podemos realizar iteraciones con los elementos de una lista, por
ejemplo:

>>>​ animales ​=​ [​'perro'​, ​'gato'​, ​'pez'​, ​'iguana'​, ​'hurón'​]


>>>​ ​for​ animal ​in​ animales: ​print​(animal)
...

17
perro
gato
pez
iguana
hurón

Operadores y expresiones

Operadores relacionales

Son aquellos operadores que se utilizan para realizar comparaciones. Por


ejemplo, el operador "igual qué" se escribe con el símbolo igual dos veces
seguidas =​ =​, se utiliza de la siguiente manera:

>>>​ ​'a'​ ​==​ ​'a'


True
>>>​ ​7​ ​==​ ​9
False

Por lo contrario, el operador "distinto de" se utiliza para comprobar si dos o


elementos son diferentes, se escribe con el símbolo de exclamación seguido
de un igual !​ =​, véase el ejemplo:
>>>​ ​'Hola'​ ​!=​ ​'hola'
True
>>>​ ​8​ ​!=​ ​27
True
>>>​ ​3​ ​!=​ ​3
False

El operador "mayor que" se escribe con el símbolo ​>​ y sirve para comprobar si
el primer elemento es mayor que el segundo, por ejemplo:

>>>​ ​3​ >


​ ​ 7​
False
>>>​ ​9​ >​ ​ 0 ​
True

Existe otro operador "mayor o igual que", se escribe ​>=​ y sirve para comprobar
si el primer elemento es mayor o igual que el segundo, por ejemplo:

>>>​ ​3​ >


​ =​ 5 ​
False
>>>​ ​3​ >​ =​ 3 ​
True
>>>​ ​3​ > ​ =​ 2 ​
True

18
El operador "menor que" se escribe con el símbolo ​<​ y sirve para comprobar si
el primer elemento es menor que el segundo, por ejemplo:

>>>​ ​21​ ​<​ ​15


False
>>>​ ​-​15​ ​<​ ​-​7
True

Por último hay también un operador "menor o igual que", se escribe ​<=​ y sirve
para comprobar si el primer elemento es menor o igual que el segundo, por
ejemplo:

>>>​ ​-​25​ ​<=​ ​128


True
>>>​ ​17​ ​<=​ ​17
True
>>>​ ​17​ ​<=​ ​16
False

Una vez explicados los operadores relacionales se pueden utilizar para realizar
algunas comprobaciones más complejas, por ejemplo, se puede comparar si
el resultado de una operación aritmética es igual a un número concreto:
>>>​ ​2​ +
​ ​ 3​ ​ =​ =​ 7​
False
>>>​ ​7​ -​ ​ 2​ ​ =​ =​ 5 ​
True

O si dos listas son iguales:

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


>>>​ lista_1 = ​
>>>​ lista_2 =​ ​ [​2​, ​3​, ​4] ​
>>>​ lista_1 = ​ =​ lista_2
False

En este caso devuelve un ​False​ por que evidentemente no son iguales, tienen
elementos diferentes, pero ¿y si comprobamos la igualdad entre el último
elemento de la primera lista y el primero de la segunda lista?

>>>​ lista_1[​-​1​] ​==​ lista_2[​0​]


True

También podemos comprobar la igualdad entre la longitud de la primera lista y


la longitud de la segunda lista:

>>>​ ​len​(lista_1) ​==​ ​len​(lista_2)


True

19
Operadores lógicos

La Lógica es una rama de la matemática que sirve para modelizar un


problema, proporcionar su solución y nos ayuda a saber si esa solución es
verdadera o falsa. En lógica existen conectivas u operadores como en las
matemáticas, pero en este caso se llaman de otra manera y evalúan los
enunciados o proposiciones de ambos lados de forma distinta. En Python, los
dos operadores lógicos principales son la conjunción a​ nd​ (símbolo en lógica ∧ ​ ​)
y la disyunción o​ r​ (símbolo en lógica ∨
​ ​). Se puede consultar la siguiente tabla
de la verdad para saber qué valor se obtiene como resultado al utilizar
cualquiera de los dos operadores a​ nd​ y o​ r​ entre dos predicados como p​ ​ y q​ ​:

Véase algunos ejemplos:

>>>​ p ​=​ ​True


>>>​ q ​=​ ​False
>>>​ p ​and​ q
False
>>>​ p ​or​ q
True

No solo podremos hacer operaciones lógicas con los valores ​True​ y ​False​,
también podremos hacerlo con expresiones algebraicas o comparaciones que
terminarán devolviendo un valor booleano igualmente, por ejemplo:

>>>​ ​3​ +
​ ​ 3​ ​ >​ ​ 2​
True
>>>​ ​3​ +​ ​ 3​ ​ >​ ​ 2 ​ 7
False

También con datos de tipo ​string​ o cadena de carácteres:

>>>​ ​'Hola'​ ​==​ ​'hola'​ ​and​ ​'b'​ ​!=​ ​'f'


False

En este caso el resultado es ​False​ ya que en una conjunción ​and​ para que el
resultado sea ​True​ ambos predicados deben ser verdaderos, y en este ejemplo

20
la cadena ​'Hola'​ no es igual a la cadena ​'hola'​, ya que una comienza por
mayúscula y la otra por minúscula.

También podemos comprobar si una cadena de caracteres tiene una longitud


concreta y si esta comienza por una letra determinada, véase el ejemplo:

>>>​ cadena ​=​ ​'Hola'


>>>​ ​len​(cadena) ​>=​ ​4​ ​and​ cadena[​0​] =
​ =​ ​'H'
True

En el caso de la disyunción ​or​ basta con que uno de los dos predicados sea
verdadero para que el resultado sea ​True​, véase el siguiente ejemplo:

>>>​ ​'Hola'​ ​==​ ​'hola'​ ​or​ ​'b'​ ​!=​ ​'f'


True

En este ejemplo ​'Hola'​ sigue sin ser igual a la cadena ​'hola'​, pero al menos el
segundo predicado es verdadero, pues el carácter ​'b'​ no es igual que el
carácter '​ f'​, por lo tanto se obtiene un resultado ​True​.

Además de los operadores ​and ​y ​or ​también existe el operador ​not​. Es una
negación lógica o un inversor, es decir, a un valor booleano (​True​ o ​False​) lo
convierte en el contrario, véase el siguiente ejemplo:

>>>​ ​not​ T
​ rue
False
>>>​ ​not​ F​ alse
True

Expresiones anidadas

Podemos combinar los diferentes operadores que ya hemos visto, tales como
el operador suma ​+​, resta ​-,​ multiplicación ​*​, división ​/​, módulo ​%​ o
potencias *​ *​ y podemos combinarlos con los demás operadores relacionales
como <​ ​, >​ ​, <​ =​, >​ =​, =​ =​ o !​ =​, los operadores lógicos como las conjunciones ​and​ y
disyunciones o​ r​, e incluso podemos englobar algunas de ellas entre
paréntesis (​ )​, pero para realizar las operaciones correctamente es importante
conocer las normas de precedencia en estos casos de expresiones
combinadas o anidadas. Véase el siguiente ejemplo en el que se mezclan
diferentes expresiones:

​ ​ 3
>>>​ a = ​
>>>​ b =​ ​ 5 ​
>>>​ a * ​ ​ b ​-​ ​2​**​b ​>=​ ​20​ ​and​ ​not​ (a ​%​ b) ​!=​ ​0
False

21
En todo caso, las normas de precedencia a la hora de realizar operaciones
combinadas o anidadas son las siguientes.

1. Las operaciones que se encuentren entre paréntesis, sean del tipo que
sean, aritméticas, relacionales o lógicas.
2. Después los exponentes y raíces, siempre de izquierda a derecha.
3. A continuación las multiplicaciones, divisiones y módulo, siempre de
izquierda a derecha.
4. Luego las sumas y restas, siempre de izquierda a derecha.
5. Después se han de realizar las operaciones relacionales, siempre de
izquierda a derecha.
6. Finalmente las operaciones lógicas, donde el operador n​ ot​ o inversor
tiene preferencia ante la conjunción a​ nd​ y la disyunción o​ r​, el resto
siempre se ha de calcular de izquierda a derecha.

Sabiendo el orden de preferencia podemos comenzar a resolver paso a paso


esta expresión anidada con varias operaciones de varios tipos diferentes. El
primer lugar tenemos la expresión entre paréntesis ​(a % b)​, donde ​a = 3​ y ​b =
5​, por lo tanto:

>>>​ (a ​%​ b)
3

El resultado es ​3​ porque es el resto de dividir ​3​ entre ​5​. Vamos a ir actualizando
la expresión con los cálculos según los vayamos resolviendo, de momento se
queda así:

a ​*​ b ​-​ ​2​**​b ​>=​ ​20​ ​and​ ​not​ ​3​ ​!=​ ​0

Continuamos, ahora vendrían los exponentes y las raíces. En este caso no hay
raíces, pero si un exponente 2**b, que es lo mismo que ​2​ elevado a ​5​:

>>>​ ​2​**​5
32

Actualizamos la expresión:

a ​*​ b ​-​ ​32​ ​>=​ ​20​ ​and​ ​not​ ​3​ ​!=​ 0


Ahora vendrían las multiplicaciones y divisiones. En este caso no hay


divisiones, pero si la multiplicación ​a * b​, que es lo mismo que ​3 * 5​:

>>>​ a ​*​ b

22
15

Actualizamos la expresión:

15​ ​-​ ​32​ ​>=​ ​20​ ​and​ ​not​ 3


​ ​ ​!=​ ​0

Sigamos con las sumas y restas. En este ejemplo no hay sumas, pero si la
resta ​15 - 32​:

>>>​ ​15​ ​–​ ​32


-​17

Actualizamos la expresión:

-​17​ ​>=​ ​20​ ​and​ ​not​ ​3​ ​!=​ ​0

En este momento ya no quedan operaciones aritméticas en la expresión, solo


operaciones relacionales y lógicas. Siguiendo el orden de precedencia
debemos resolver primero las operaciones relacionales, siempre de izquierda a
derecha, y lo primero que tenemos por la izquierda es la operación
relacional -​ 17 >= 20​.

>>>​ ​-​17​ ​>=​ ​20


False

El resultado de esta operación es ​False​, puesto que el número ​-17​ no es mayor


o igual que ​20​. Sigamos con la siguiente operación relacional ​3 != 0​:

>>>​ ​3​ ​!=​ ​0


True

El resultado es ​True​ puesto que el número ​3​ es diferente del número ​0​.
Actualizamos la expresión:

False​ ​and​ ​not​ ​True

Ahora nos queda una expresión únicamente lógica. Si seguimos las normas de
precedencia debemos tener en cuenta primero el operador ​not​, en este caso la
operación es n​ ot True​:

>>>​ ​not​ ​True


False

Actualizamos la expresión:

False​ ​and​ ​False

23
Finalmente calculamos esta operación lógica en la que hay una conjunción ​and​:

>>>​ ​False​ ​and​ ​False


False

Operadores de asignación

Se trata de aquellos operadores que se utilizan para asignar o modificar el


valor de una variable, constante o cualquier otro objeto en Python.

El primer operador de asignación es un simple símbolo de igual ​=​ y se utiliza


para asignar un valor sin más, por ejemplo:

>>>​ a ​=​ ​0

De esta manera la variable ​a​ tendría asignado desde este momento el valor
entero ​3​.

También existe el operador de suma en asignación, se escribe ​+=​ y lo que hace


es incrementar el valor inicial de una variable, y lo incrementa exactamente la
cantidad que se coloque a la derecha del operador, véase el ejemplo:

>>>​ a ​+=​ ​1
>>>​ ​print​(a)
1
>>>​ a ​+=​ ​1
>>>​ ​print​(a)
2
>>>​ a ​+=​ ​1
>>>​ ​print​(a)
3
También podemos hacer una resta en asignación, se escribe ​-=​ y funciona
exactamente igual que el anterior solo que cada vez se resta la cantidad que
tiene a su derecha, véase el ejemplo:

>>>​ b ​=​ ​15


>>>​ b ​-=​ ​5
>>>​ ​print​(b)
10
>>>​ b ​-=​ ​5
>>>​ ​print​(b)
5
>>>​ b ​-=​ ​5
>>>​ ​print​(b)
0

24
En este caso, en vez de aumentar o disminuir el valor de uno en uno lo hemos
hecho de cinco en cinco.

Otro operador de asignación es el producto o la multiplicación en asignación,


se escribe ​*=​ y funciona como los anteriores, solo que en ese caso
multiplicará, veamos el ejemplo:

>>>​ c ​=​ ​3
>>>​ c ​*=​ ​2
>>>​ ​print​(c)
6
>>>​ c ​*=​ ​2
>>>​ ​print​(c)
12
>>>​ c ​*=​ ​2
>>>​ ​print​(c)
24

De la misma forma podemos usar otro operador de asignación llamado


división en asignación, se escribe ​/=​, por ejemplo:

>>>​ d ​=​ ​1024


>>>​ d ​/=​ ​2
>>>​ ​print​(d)
512.0
>>>​ d ​/=​ ​2
>>>​ ​print​(d)
256.0
>>>​ d ​/=​ ​2
>>>​ ​print​(d)
128.0

Nótese que en el caso de la división el resultado es devuelto con coma flotante


o decimal.

También hay un operador de asignación llamado módulo en asignación, se


escribe ​%=​ y se usa como los demás, solo que en este caso devuelve el resto
de la división del valor original entre el que se ponga a la derecha del
operador, véase el ejemplo:

>>>​ e ​=​ ​29


>>>​ e ​%=​ ​2
>>>​ ​print​(e)
1
>>>​ e ​%=​ ​2
>>>​ ​print​(e)
1

25
El primer resultado es ​1​, ya que es el resto de dividir ​29​ entre ​2,​ así pues, el
nuevo valor de la variable ​e​ pasa a ser ​1​. En el segundo cálculo el resultado
vuelve a ser ​1​, ya que es el resto de dividir ​1​ entre ​2​.

Por último tenemos el operador de asignación llamado potencia en asignación,


se escribe ​**=​ y funciona de la siguiente manera:

>>>​ f ​=​ ​2
>>>​ f ​**=​ ​2
>>>​ ​print​(f)
4
>>>​ f ​**=​ ​2
>>>​ ​print​(f)
16
>>>​ f ​**=​ ​2
>>>​ ​print​(f)
256

Control de flujo
El flujo de un programa es la sucesión de acciones que se irán ejecutando en
un programa. Veremos los diferentes controles de flujo que tenemos
disponibles en Python como el control de flujo ​condicional​ en el que se podrá
elegir la ejecución de una acción u otra, o el control de flujo ​iterativo​, que
repetirá un bloque de instrucciones hasta que se cumpla una condición
específica.

Sentencia if

Se trata de una sentencia disponible en la mayoría de los lenguajes de


programación, y cómo no también en Python. Permite dividir el flujo en
diferentes caminos. La manera de escribir un bloque de código que contenga
una sentencia i​ f​ es la siguiente:

if​ ​True​:
print​(​'Hello world!'​)

Comienza con la palabra reservada ​if​, seguido de una expresión. Python


evaluará si esa expresión devuelve ​True​ o ​False​, y solo ejecutará el bloque de
código que viene a continuación si la evaluación de la expresión resulta ​True​. A
continuación de la expresión se ha de escribir el símbolo de los dos
puntos :​ ​ para indicar que el fin de la expresión a evaluar. En Python es
imprescindible que el código que venga a continuación de un
condicional i​ f​ esté indentado, es decir, que tenga una tabulación por delante,
en este caso se imprimirá el mensaje "​Hello world!​" única y exclusivamente si

26
al evaluar la expresión condicional ​if​ resulta ​True​, pero como hemos puesto
una expresión lógica ​True​ sin más siempre será verdadero, por lo tanto se
imprimirá siempre el mensaje.
Veamos otro ejemplo de condicional ​if​ con otra expresión diferente:

>>>​ ​if​ ​3​ ​>​ ​1​:


...​ ​print​(​'Esta expresión es verdadera'​)
...
Esta expresión es verdadera.

En esta ocasión la expresión a evaluar es si se cumple que el número ​3​ es


mayor que el número ​1​, y como sí lo es se imprimirá el mensaje "​Esta expresión
es verdadera​".
Veamos otro tipo de expresiones que también se pueden evaluar, por ejemplo
expresiones con operadores relacionales como =​ =​, !​ =​, <​ ​, <​ =​, >​ ​ o >​ =​:

>>>​ ​if​ ​3​ ​+​ ​2​ ​==​ ​5​:


...​ ​print​(​'Es verdadero.'​)
...
Es verdadero.
>>>​ saludo ​=​ ​'Hola'
>>>​ ​if​ ​len​(saludo) ​>=​ 7 ​ ​:
...​ ​print​(​'saludo tiene una longitud mayor o igual que 7.'​)
...

En este caso la variable ​saludo​, que tiene una longitud de ​4​, no cumple la
condición de que su longitud sea mayor o igual a ​7​, por lo tanto no se
imprimirá ningún mensaje.

Veamos una condición en la que se evalúan expresiones anidadas mediante


una conjunción ​and​:

>>>​ a ​=​ ​5
>>>​ b ​=​ ​17
>>>​ ​if​ a ​<​ b ​and​ a ​*​ ​4​ ​>​ b:
...​ ​print​(​'Esta expresión es verdadera.'​)
...
Esta sentencias es verdadera.

En este caso si se imprime el mensaje porque tanto la expresión ​a < b​ como la


expresión ​a * 4 > b​ son verdaderas. Veamos otro ejemplo con una
disyunción o​ r​:

>>>​ a ​=​ ​0
>>>​ b = ​ ​ ​5
>>>​ ​if​ a ​>​ b ​or​ ​25​ ​%​ b ​==​ 0
​ ​:
...​ ​print​(​'Esta expresión es verdadera.'​)
...

27
Esta expresión es verdadera.

En este caso también se imprime el mensaje, a pesar de que la expresión ​a >


b​ sea ​False​, la segunda expresión ​25 % b == 0​ es ​True​, y en una disyunción
basta con que al menos una de las expresiones sea verdadera.
En todos estos ejemplos estamos viendo que el código que se ha de ejecutar
en caso de que la expresión sea T​ rue​ no es más que una simple llamada a la
función p​ rint()​ con un mensaje, pero podemos poner cualquier otro bloque de
código, como por ejemplo otro condicional i​ f​ anidado uno dentro de otro,
véase el ejemplo:

>>>​ a ​=​ ​5
>>>​ b ​=​ ​10
>>>​ ​if​ a ​==​ ​5​:
...​ ​print​(​'a vale'​, a)
...​ ​if​ b ​==​ ​10​:
...​ ​print​('
​ b vale'​, b)
...
a vale ​5
b vale ​10

Es muy importante respetar el indentado en todo momento.

Hasta ahora hemos visto cómo hacer que se ejecute un bloque de código
cuando se cumple una condición, pero también podemos ejecutar código
cuando la condición no se cumple. Para ello está la palabra reservada ​else​ y
se utiliza de la siguiente manera:

>>>​ n ​=​ ​10


>>>​ ​if​ n ​>​ ​20​:
...​ ​print​(​'n es mayor que 20.'​)
...​ ​else​:
...​ ​print​(​'n no es mayor que 20.'​)
...
n no es mayor que ​20​.

Además de la palabra reservada ​else​ también existe la palabra reservada ​elif​.


Funciona de forma similar a ​else​ solo que evalúa una expresión adicional. Se
suele usar para la creación de menús interactivos, por ejemplo:

>>>​ comando ​=​ ​'SALIR'


>>>​ ​if​ comando ​==​ ​'ENTRAR'​:
...​ ​print​(​'Bienvenido al sistema!'​)
...​ ​elif​ comando ​==​ ​'SALUDAR'​:
...​ ​print​(​'Hola!'​)
...​ ​elif​ comando ​==​ ​'SALIR'​:
...​ ​print​(​'Saliendo del sistema ...'​)
...​ ​else​:
...​ ​print​(​'Este comando no se reconoce.'​)

28
...
Saliendo ​del​ sistema ​...

Como podemos comprobar el código de nuestra sentencia if cada vez se va


haciendo más grande, en realidad ya empieza a coger forma y resulta que
estamos haciendo un pequeño programa, esto empieza a ser un poco más
complicado de manejar desde una consola de Python, así que lo mejor es que
comencemos a escribir nuestro código dentro de un archivo al que
llamaremos m​ i_primer_programa.py​ de modo que quede de la siguiente manera:

comando ​=​ ​'SALIR'

if​ comando ​==​ ​'ENTRAR'​:


print​(​'Bienvenido al sistema!'​)
elif​ comando ​==​ ​'SALUDAR'​:
print​(​'Hola!'​)
elif​ comando ​==​ ​'SALIR'​:
print​(​'Saliendo del sistema ...'​)
else​:
print​(​'Este comando no se reconoce.'​)

En realidad el archivo puede tener cualquier otro nombre, este solo es uno de
ejemplo. Para ejecutar un programa Python contenido en un archivo se ha de
hacer de la siguiente forma desde la consola:

python3 mi_primer_programa.py
Saliendo del sistema ...

Como se puede comprobar funciona perfectamente, muestra el mensaje que


tiene que mostrar en función del valor que tiene su variable, definido en el
propio código. A partir de este momento vamos a trabajar siempre con
archivos en los que escribiremos el código fuente de nuestros programas.

Vamos a desarrollar un pequeño programa en el que podamos utilizar de


nuevo una sentencia ​if​ con ​elif​ y ​else​, solo que esta vez incluiremos una
nueva función ​input()​ para pedirle al usuario que introduzca un dato al
ejecutar el programa. Para ello escribiremos el siguiente fragmento de código
en un archivo al que llamaremos c​ alcular_nota.py​, será un pequeño programa
que le pedirá al usuario introducir una calificación, es decir, un número
cualquiera.

nota ​=​ ​float​(​input​(​'Introduce la nota: '​))

if​ nota ​>=​ ​9​:


print​(​'Sobresaliente'​)
elif​ nota ​>=​ ​7​:
print​(​'Notable'​)
elif​ nota ​>=​ ​6​:

29
print​(​'Bien'​)
elif​ nota ​>=​ ​5​:
print​(​'Suficiente'​)
else​:
print​(​'Insuficiente'​)

Si ejecutamos el programa podremos introducir cualquier número,


automáticamente lo convertirá en un entero de coma flotante ya que lo
estamos forzando así con la función ​float()​. Es decir, si el usuario introduce
un número 5​ ​ el programa lo convertirá automáticamente en el número ​5.0​, y si
introduce un 1​ 0​ lo convertirá en un 1​ 0.0​. Aquí tenemos algunas ejecuciones de
nuestro programa con diferentes ​inputs​ de entrada, se puede ver cómo
imprime un mensaje diferente en cada caso:

python3 calcular_nota.py
Introduce la nota: 10
Sobresaliente

python3 calcular_nota.py
Introduce la nota: 7.5
Notable

python3 calcular_nota.py
Introduce la nota: 6.9
Bien

python3 calcular_nota.py
Introduce la nota: 5
Suficiente

python3 calcular_nota.py
Introduce la nota: 4.9
Insuficiente

Sentencia while

En esta sección trataremos la sentencia ​while​, nos servirá para realizar


iteraciones. Un iteración no es más que la ejecución de una acción una o más
veces, en este caso siempre y cuando cumpla como verdadera una expresión
relacional o lógica. Solo finalizará cuando esa
condición devuelva un valor F​ alse​. Veamos
algunos ejemplos, para ello crearemos un
nuevo archivo llamado b​ ucle_while.py​ y
escribiremos en él nuestro código:

c ​=​ ​0

while​ c ​<=​ ​5​:

30
c ​+=​ ​1
print​(​'c vale'​, c)

En este simple programa comenzamos por iniciar una variable ​c​ con valor ​0​. A
continuación iniciamos un bucle ​while​ que ejecutará el código que contiene en
su cuerpo solo si se cumple la condición en la que se evaluará si la
variable c​ ​ en ese momento tiene un valor menor o igual que ​5​. La primera vez
la variable c​ ​ vale 0​ ​, por lo que esta expresión devolverá ​True​, así que el
bucle w​ hile​ ejecutará el código con contiene. Lo primero que hace es
incrementar en 1​ ​ el valor actual de c​ ,​ por lo que en este momento, tras ejecutar
esta línea c​ ​ pasaría a valer 1​ .​ A continuación imprime un mensaje que dice "​c
vale 1​" y entonces es cuando vuelve a evaluar de nuevo la expresión inicial,
solo que ahora c​ ​ ya no vale 0​ ,​ ha pasado a valer 1​ ​.

En esta segunda iteración se vuelve a cumplir que ​c sea menor o igual que ​5​,
por lo que repetirá el código que hay dentro del bucle ​while​. Una vez más
incrementará en ​1 el valor de ​c,​ por lo que a partir de ese momento la
variable c​ ​ pasa a valer ​2.​ Después imprime el mensaje "​c vale 2​" y vuelve de
nuevo a evaluar la expresión inicial.

Este proceso se repetirá varias veces, hasta que en una de las iteraciones el
valor de ​c​ se incremente hasta valer ​6​, en cuyo caso, al volver a evaluar la
expresión inicial devolverá un ​False​, pues ​6 ​no es menor o igual que ​5​. En ese
momento el bucle ya no ejecutará más veces el código que contiene. A
continuación un ejemplo de cómo sería la ejecución de nuestro programa con
este bucle w​ hile​ que hemos escrito.

python3 bucle_while.py
c vale ​1
c vale ​2
c vale ​3
c vale ​4
c vale ​5
c vale ​6

Una particularidad que tiene el bucle ​while​ en Python es que puede usar la
palabra reservada ​else​ para ejecutar una acción tras finalizar el bucle, véase el
siguiente ejemplo:

c ​=​ ​0

while​ c ​<=​ ​5​:


c ​+=​ ​1
print​(​'c vale'​, c)
else​:
print​(​'Saliendo del bucle'​)

31
Si ejecutamos de nuevo nuestro programa con esta inclusión de la palabra
reservada ​else​ veremos que el resultado es el mismo y además añade el
mensaje "​Saliendo del bucle​" al finalizar este:

python3 bucle_while.py
c vale 1
c vale 2
c vale 3
c vale 4
c vale 5
c vale 6
Saliendo del bucle

Al igual que en otros lenguajes de programación también podemos interrumpir


la ejecución del bucle ​while​ con una palabra reservada llamada ​break​. Por
ejemplo, en nuestro programa queremos que el bucle se corte cuando la
variable c​ ​ tenga por valor ​3,​ para ello debemos modificar el código de nuestro
programa añadiendo un condicional i​ f​ y utilizaremos la sentencia b​ reak​ de la
siguiente manera:

c ​=​ ​0

while​ c ​<=​ ​5​:


c ​+=​ ​1

if​ c ​==​ ​3​:


​ Se corta la iteración cuando c vale'​, c)
print​('
break
print​(​'c vale'​, c)
else​:
print​(​'Saliendo del bucle'​)

Se ha añadido el condicional justo debajo de el incremento de la variable ​c​, y


dentro del condicional se imprime un mensaje y se hace una llamada a la
sentencia b​ reak​, que finalizará las iteraciones del bucle. Veamos el resultado de
la ejecución:

python3 bucle_while.py
c vale 1
c vale 2
Se corta la iteración cuando c vale 3

Ni siquiera llega a imprimir el mensaje que tenemos en la sentencia ​else​,


interrumpe el bucle ​while​ del todo.

Otra sentencia que podemos utilizar en un bucle ​while​ es ​continue​, que a


diferencia de ​break​ en vez de romper la ejecución simplemente se salta la

32
iteración en la por ejemplo que se cumpla una condición. Vamos a modificar el
código de nuestro programa de la siguiente manera:

c ​=​ ​0

while​ c ​<=​ ​5​:


c ​+=​ ​1
if​ c ​==​ ​3​:
continue
print​(​'c vale'​, c)
else​:
print​(​'Saliendo del bucle'​)

Solo hemos borrado el mensaje que se imprimiría en el


condicional ​if​ cuando ​c​ valga ​3​, y hemos sustituido la
sentencia b​ reak​ por c​ ontinue​. Este sería el resultado de la ejecución de nuestro
programa:

python3 bucle_while.py
c vale 1
c vale 2
c vale 4
c vale 5
c vale 6
Saliendo del bucle

Si nos fijamos bien, ha impreso el mensaje en el que va indicando el valor


actual de la variable ​c​ en todo momento menos en la iteración en la
que c​ ​ vale ​3​, y finalmente ha impreso el mensaje indicado en la sentencia ​else​.
Antes de terminar este apartado vamos a crear un nuevo programa en el que
usaremos la sentencia w​ hile​ para crear un menú interactivo desde la consola,
el programa pedirá al usuario que introduzca un valor y en función de lo que el
usuario introduzca ejecutará una acción u otra. También tendrá una opción
para finalizar el programa. Para ello crearemos un nuevo archivo
llamado m​ enu.py​, el código es el siguiente:

print​(​'\n MENU \n======\n'​)

while​ ​True​:
print​(​'Selecciona una opción:\n'​)
print​(​'1: Saludar'​)
print​(​'2: Sumar dos números'​)
print​(​'3: Salir\n'​)

user_choice ​=​ i
​ nput​()

if​ user_choice ​==​ '


​ 1'​:
​ Hola, qué tal?'​)
print​('
elif​ user_choice ​==​ ​'2'​:

33
n1 ​=​ ​float​(​input​(​'Introduce el primer número: '​))
n2 ​=​ ​float​(​input​(​'Introduce el segundo número: '​))
print​(' ​ El resultado de la suma es:'​, n1 ​+​ n2)
elif​ user_choice ​==​ ​'3'​:
print​(' ​ Saliendo del programa!'​)
break
else​:
print​(' ​ Opción no válida, vuelve a intentarlo.\n'​)

Sentencia for

La sentencia ​for​ nos permite realizar iteraciones de una manera muy similar a
la sentencia ​while​, solo que reduce considerablemente la complejidad, puesto
que no es necesario crear variables previamente con índices ni necesitaremos
hacer incrementos con operadores de incremento.
Normalmente se utiliza para iterar sobre listas o elementos compuestos en los
que puede ser necesario recorrer cada uno de esos elementos. Veamos un
ejemplo muy simple, crearemos un archivo nuevo llamado b​ ucle_for.py​ y en él
escribiremos el siguiente código:

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


​ ​]

for​ numero ​in​ numeros:


print​(numero)

En este simple programa creamos primero una variable llamada ​numeros​ de tipo
lista, y como valor le asignamos una lista de números enteros del ​1​ al ​5​. A
continuación usamos la palabra reservada f​ or​, luego escribimos el nombre de
la variable temporal que usaremos dentro del bucle para referirnos a cada
elemento por el que iteraremos, en este caso n​ umero​, luego la palabra
reservada i​ n​ y finalmente el nombre de la variable que contiene la información
por la que realizaremos las iteraciones, en este caso nuestra lista n​ umeros​. A
continuación vendría el cuerpo del bucle, siempre identado con una
tabulación, en este caso imprimir el valor de la variable temporal n​ umero​ que en
cada iteración tendrá por valor cada uno de los elementos de la lista n​ umeros​.
Veamos un ejemplo de ejecución de este pequeño programa:

python3 bucle_for.py
1
2
3
4
5

Podemos comprobar que en cada iteración imprime el valor de la


variable ​numero​. Tal y como hemos escrito el código de nuestro programa no ha
hecho falta acceder a cada elemento de la variable n​ umeros​ mediante un índice,

34
como por ejemplo ​numeros[0]​ o ​numeros[1]​, ni tampoco ha hecho falta crear una
variable a modo de contador para usarla como índice, simplemente itera de
uno en uno a través de todos los elementos, desde el primero hasta el último.
Si quisiéramos tener un control de cada elemento mediante un índice se puede
realizar de varias maneras, pero Python dispone de un método
llamado e​ numerate()​ que podremos utilizar para facilitar el proceso. Vamos a
reemplazar el código de nuestro archivo b​ ucle_for.py​ con el siguiente código
de ejemplo:

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


​ ​]

for​ indice, numero ​in​ e


​ numerate​(numeros):
print​(numeros[indice], numero)

Después de la sentencia ​for​ hemos iniciado dos variables temporales, es


decir, variables que solo estarán disponibles dentro del cuerpo del
bucle f​ or​ hasta que este termine, estas variables son ​indice​ y ​numero​. Dentro
del cuerpo del bucle imprimimos cada elemento de la lista de las dos maneras
disponibles que tenemos, una es mediante la propia lista n​ umeros​ a la que
accedemos a su índice mediante la variable í​ ndice ​y la otra es mediante la
variable n​ umero​. A continuación un ejemplo de la ejecución del programa:

python3 bucle_for.py
1 1
2 2
3 3
4 4
5 5

Acceder a los elementos de una lista mediante un índice nos va a permitir


tener un mayor control de cada elemento por el que iteramos, por ejemplo
para poder editar el valor de ese elemento y que permanezca ese nuevo valor
en la lista una vez termine el bucle ​for​, por ejemplo, vamos a modificar el
código de nuestro programa para que el tercer elemento adquiera un valor
nuevo, por ejemplo su propio valor multiplicado por 3:

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


​ ​]

for​ indice, numero ​in​ e ​ numerate​(numeros):


if​ indice ​==​ ​2:​
numeros[indice] ​*=​ ​3

print​(numeros[indice])

print​(numeros)

Hemos incluido al final del programa una línea que imprima todos los
elementos de la lista, fuera del bucle ​for​, pero comprobar que el cambio de

35
valor en el tercer elemento prevalece en el valor de la lista. Si ejecutamos el
programa de nuevo veremos el siguiente resultado:

python3 bucle_for.py
1
2
9
4
5
[1, 2, 9, 4, 5]

El tercer elemento que antes era ​3​ ahora es su propio valor multiplicado por 3,
es decir, ​9​.

La sentencia ​for​ no solo sirve para recorrer los elementos de una lista, también
tiene otras utilidades interesantes como por ejemplo la de recorrer todos los
carácteres de una variable de tipo ​string​ o cadena de texto, por ejemplo,
modifiquemos el código de nuestro programa en el archivo b​ ucle_for.py​ con el
siguiente código:

cadena ​=​ ​'Hola qué tal'

for​ caracter ​in​ cadena:


print​(caracter)

Primero hemos creado una variable de tipo ​string​ con el mensaje "​Hola qué
tal​" y luego hemos creado un bucle ​for​ en el que hemos creado la
variable ​caracter​ para iterar por cada carácter de la variable ​cadena​. Si
ejecutamos el programa se imprimirán todos los caracteres uno a uno, incluido
los espacios, que también son un carácter:

python3 bucle_for.py
H
o
l
a

q
u
é

t
a
l

También podemos utilizar la función ​enumerate()​ como en el ejemplo anterior


para acceder a cada carácter de la cadena de carácteres. Veamos cómo
podemos usar la función ​enumerate​ para acceder a cada carácter:

36
cadena ​=​ ​'Hola qué tal'

​ n​ ​enumerate​(cadena):
for​ i, c i
print​(cadena[i], c)

Si ejecutamos el programa obtendremos el siguiente resultado:

python3 bucle_for.py
H H
o o
l l
a a

q q
u u
é é

t t
a a
l l

Como podemos ver, funciona igual que cuando usábamos el bucle ​for​ en el
ejemplo anterior. Probemos a modificar el primer carácter "​H​" por "​X​" y veamos
qué sucede en este caso:

cadena ​=​ ​'Hola qué tal'

​ n​ ​enumerate​(cadena):
for​ i, c i
if​ i ​==​ ​0​:
cadena[i] ​=​ ​'X'

print​(cadena[i], c)

print​(cadena)

Si ejecutamos el programa nos devolverá el siguiente error:

python3 bucle_for.py
Traceback (most recent call last):
File ​"bucle_for.py"​, line 5, i
​ n​ <
​ ​module​>
cadena[i] = ​'X'
TypeError: ​'str'​ object does not support item assignment

Esto sucede porque las variables de tipo ​string​ son inmutables, así que en
esta ocasión no podremos modificar el valor de ningún caracter.

En un bucle ​for​ también podemos utilizar la función ​range()​, que generará una
lista entre un rango numérico, por ejemplo, si queremos imprimir los 10

37
primeros números utilizando la función ​range()​ tendríamos que editar el
archivo ​bucle_for.py​ con el siguiente código:

for​ numero ​in​ ​range​(​10​):


print​(numero)

Si ejecutamos el programa obtendremos el siguiente resultado:

python3 bucle_for.py
0
1
2
3
4
5
6
7
8
9

La función ​range()​ permite introducir dos valores separados por coma entre
sus paréntesis, por ejemplo si queremos obtener un rango numérico entre el
número 2​ 7​ y el número 3​ 1​ tendríamos que escribir ​range(27, 31)​, modifiquemos
nuestro código para verlo:

for​ numero ​in​ ​range​(​27​, ​31​):


print​(numero)

Si ejecutamos de nuevo el programa obtendremos este otro resultado:

python3 bucle_for.py
27
28
29
30

Colecciones
En Python, además de las listas también tenemos un tipo especial de dato
llamado colecciones, y a que a su vez se divide en varias estructuras de datos
que veremos en detalle en esta sección:

● Tuplas
● Conjuntos
● Diccionarios

38
Además, podemos simular dos colecciones tradicionales de otros lenguajes
como las ​Pilas​ y las ​Colas​ utilizando datos de tipo ​lista​ o librerías y módulos
de Python.

Tuplas

Las tuplas son unas colecciones parecidas a las listas, con la diferencia de que
las tuplas son inmutables. Se suelen utilizar para asegurarnos de que
determinados datos no se puedan modificar. Python utiliza tuplas en alguna de
sus funciones para devolver resultados inmutables. La manera de definir un
dato de tipo tupla es parecido a las listas, solo que en vez de usar
corchetes [​ ]​ se utilizan paréntesis (​ )​. Dentro de los paréntesis se incluyen los
elementos como si fueran una lista. Para poder ver un ejemplo práctico vamos
a crear un archivo llamado t​ upla.py​ y dentro vamos a incluir el siguiente
código:

tupla ​=​ (​100​, ​'Hola'​, [​1​, ​2​, ​3]


​ , ​3.14​)

print​(tupla)

Si ejecutamos el programa obtendremos el siguiente resultado:


python3 tupla.py
(100, ​'Hola'​, [1, 2, 3], 3.14)

Las tuplas, al igual que las listas, aceptan indexación y ​slicing​. Por ejemplo,
podremos consultar el primer elemento con el índice ​[0]​ o el último elemento
de la tupla con el índice ​[-1]​:

tupla ​=​ (​100​, ​'Hola'​, [​1​, ​2​, ​3]


​ , ​3.14​)

print​(tupla[​0​])
print​(tupla[​-​1​])

Al ejecutarlo obtendremos el siguiente resultado:

python3 tupla.py
100
3.14

Y también podremos imprimir todos los elementos desde el tercero hasta el


final con el ​slicing​ ​[2:]​:

tupla ​=​ (​100​, ​'Hola'​, [​1​, ​2​, ​3]


​ , ​3.14​)
print​(tupla[​2​:])

python3 tupla.py

39
([1, 2, 3], 3.14)

En este caso, uno de los elementos de la tupla es una lista, también podremos
acceder a uno de los elementos internos de esa lista tal y como lo hacíamos
cuando teníamos listas dentro de listas, con dobles índices, por ejemplo:

tupla ​=​ (​100​, ​'Hola'​, [​1​, ​2​, ​3]


​ , ​3.14​)
print​(tupla[​2​][​1​])

python3 tupla.py
2

Antes se ha mencionado que las tuplas es un tipo de dato parecido a las listas
pero que su valor es inmutable. Vamos a modificar el código de nuestro
programa para intentar modificar el primer valor de nuestra tupla y ver el error
que devuelve:

tupla ​=​ (​100​, ​'Hola'​, [​1​, ​2​, ​3]


​ , ​3.14​)

tupla[​0​] ​=​ ​37

print​(tupla)

Si ejecutamos el programa veremos que devuelve el siguiente error en el que


se indica que las tuplas son objetos que no soportan la asignación de valores
a cualquiera de su elementos:

python3 tupla.py
Traceback (most recent call last):
File ​"tupla.py"​, line 3, ​in​ ​<​module​>
tupla[0] = 37
TypeError: ​'tuple'​ object does not support item assignment

Al igual que la listas también tienen una longitud, que representaría el número
de elementos que tiene, y para ello se utiliza de nuevo la función ​len()​, por
ejemplo:

tupla ​=​ (​100​, ​'Hola'​, [​1​, ​2​, ​3]


​ , ​3.14​)

print​(​len​(tupla))

python3 tupla.py
4

Tanto en las listas como en las tuplas se puede buscar la posición de un


elemento si conocemos el valor de dicho elemento, esto se consigue
invocando al método ​.index()​ de la siguiente manera, por ejemplo,

40
modifiquemos el código de nuestro programa para que nos devuelva la
posición del elemento ​[1, 2, 3]​:

tupla ​=​ (​100​, ​'Hola'​, [​1​, ​2​, ​3]


​ , ​3.14​)

print​(tupla.index([​1​, ​2​, ​3]


​ ))

El ejecutarlo nos devuelve la posición ​2​, es decir, el elemento que está en


tercer lugar, pues en los índices siempre se empieza a contar desde ​0​:

python3 tupla.py
2

Otro método interesante que podemos usar tanto en listas como en tuplas en
el método ​.count()​, este nos devolverá el número de veces que aparece
repetido un elemento conocido en la lista o en la tupla. Editemos el código de
nuestro programa para ver unos ejemplos:

tupla ​=​ (​100​, ​'Hola'​, ​100​, 1


​ 00​, [​1,
​ ​2​, ​3​], ​3.14​)
print​(tupla.count(​100​))

python3 tupla.py
3

Aunque las tuplas y las listas tienen muchos métodos en común hay algunos
métodos que no están disponibles en las tuplas como por ejemplo el
método .​ append()​ para añadir nuevos elementos, puesto que las tuplas son
inmutables y no se pueden ni editar, si siquiera añadir o eliminar elementos.

Conjuntos

Los conjuntos son colecciones desordenadas de elementos únicos. Se suelen


utilizar para comprobar pertenencias a grupos y eliminación de elementos
duplicados.

Para empezar a trabajar con los conjuntos vamos a crear un archivo nuevo
llamado ​conjuntos.py​ y vamos a ir escribiendo código en él. Para crear un
conjunto, si este va a ser un conjunto vacío se hace de la siguiente manera:

conjunto ​=​ ​set​()

Pero si queremos un conjunto que contenga elementos solo hay que escribir
los elementos que se quieren añadir a dicho conjunto entre llaves ​{}​ y
separados por comas, por ejemplo:

conjunto ​=​ {​1​, ​2​, ​3​}

41
print​(conjunto)

python3 conjunto.py
{1, 2, 3}

Ahora vamos a utilizar el método ​.add()​ para añadir un nuevo elemento a este
conjunto, por ejemplo el número ​4​:

conjunto.add(​4​)

python3 conjunto.py
{1, 2, 3, 4}

Se puede ver cómo se ha añadido el número ​4​ al conjunto, justo al final. Ahora
vamos a añadir otro elemento nuevo, por ejemplo el número ​0​ y mostremos
cómo imprime el valor del conjunto:

conjunto.add(​0​)

python3 conjunto.py
{0, 1, 2, 3, 4}

Se ve cómo lo ha añadido, pero no lo ha incluido al final de los elementos, si


no al principio, aparentemente está ordenado numéricamente. Probamos
ahora a añadir un nuevo elemento de tipo ​string​:

conjunto.add(​'H'​)

python3 conjunto.py
{0, 1, 2, 3, 4, ​'H'​}

La letra "​H​" la ha añadido al final. Ahora vamos a añadir dos letras más, la letra
"​A​" y la letra "​Z​" y veamos qué pasa:

conjunto.add(​'A'​)
conjunto.add(​'Z'​)

python3 conjunto.py
{0, 1, 2, 3, 4, ​'A'​, ​'Z'​, ​'H'​}

Los números los sigue mostrando en orden numérico de menor a mayor, pero
los carácteres los muestra en un orden aleatorio, de hecho, si ejecutamos el
programa varias veces seguidas podremos comprobar que el orden de las
letras "​A​", "​H​" y "​Z​" va cambiando, por eso decimos que los conjuntos son un
tipo de colecciones desordenadas.

42
Los conjuntos son muy útiles para comprobar la pertenencia a un grupo, por
ejemplo, hagamos un grupo de personas llamado ​conjuntoA​:

conjuntoA ​=​ {​'Javier'​, '


​ Bob'​, ​'Alice'​}

Para saber si una persona en concreto pertenece al


conjunto ​conjuntoA​ debemos usar la palabra reservada ​in​ de la siguiente
manera:

'Javier'​ ​in​ conjuntoA

Esta comprobación devolverá un valor booleano, es decir ​True​ o ​False​, usemos


la función ​print()​ en nuestro código para poder visualizarlo:

conjuntoA ​=​ {​'Javier'​, '


​ Bob'​, ​'Alice'​}

print​(​'Javier'​ ​in​ conjuntoA)

python3 conjunto.py
True

Si lo que queremos comprobar es si una persona en concreto no está en el


conjunto ​conjuntoA​ podemos usar la negación ​not​ de la siguiente manera:

conjuntoA ​=​ {​'Javier'​, '


​ Bob'​, ​'Alice'​}

print​(​'Javier'​ ​not​ ​in​ conjuntoA)

python3 conjunto.py False

Algo muy interesante de los conjuntos es que no pueden contener elementos


repetidos, si se añade varias veces el mismo elemento no dará ningún error,
pero solo lo mostrará una vez, hagamos una prueba en nuestro código:

conjuntoA ​=​ {​'Javier'​, '


​ Bob'​, ​'Javier'​, ​'Javier'​, ​'Alice'​}

print​(conjuntoA)

python3 conjunto.py
{​'Alice'​, ​'Javier'​, ​'Bob'​}

Podemos explotar esta funcionalidad de los conjuntos por ejemplo para


eliminar elementos repetidos en una lista, pero para ello hay que emplear una
técnica llamada ​cast​ en la que vamos a cambiar un tipo de dato a otro tipo, en
este caso vamos a convertir una variable de tipo lista que contendrá elementos
repetidos a una variable de tipo conjunto, por lo que se eliminarán, finalmente
volveremos a hacer ​cast​ para volver a convertir nuestra variable a tipo lista,

43
hagámoslo por pasos, primero creamos la variable ​lista​ de tipo lista con
elementos repetidos:

lista ​=​ [​1​, ​2​, ​3​, ​3​, ​2,


​ ​1​]

A continuación creamos una nueva variable ​conjunto​ de tipo conjunto a la que


le vamos a añadir como valor el contenido de la variable ​lista​ utilizando el
método s​ et()​ de la siguiente manera:

lista ​=​ [​1​, ​2​, ​3​, ​3​, ​2,


​ ​1​]
conjunto ​=​ ​set​(lista)

print​(conjunto)

Si ejecutamos ahora nuestro programa nos devolverá un conjunto con los


elementos de ​lista​ sin repetir:

python3 conjunto.py
{1, 2, 3}

Finalmente vamos a convertir de nuevo la variable ​conjunto​ sin elementos


repetidos a una lista, para ello volvemos a hacer ​cast​ utilizando en este caso el
método l​ ist()​ de la siguiente manera:

lista ​=​ [​1​, ​2​, ​3​, ​3​, ​2,


​ ​1​]
conjunto ​=​ ​set​(lista)
lista ​=​ ​list​(conjunto)

print​(lista)

python3 conjunto.py
[1, 2, 3]

En vez de hacer este ​cast​ doble en varias líneas podemos hacerlo más
sencillo en una sola línea de la siguiente manera:

​ ​ [​1​, ​2​, ​3​, ​3​, ​2,


lista = ​ ​1​]
lista =​ ​ ​list​(​set​(lista))

print​(lista)

python3 conjunto.py
[1, 2, 3]

Este concepto también funciona con cadenas de carácteres, por ejemplo:

cadena ​=​ ​"El perro de San Roque no tiene rabo"

print​(​set​(cadena))

44
python3 conjunto.py {​'S'​, ​'u'​, ​'b'​, ​'E'​, ​'r'​, ​'d'​, ​'n'​, ​'o'​, ​'q'​, ​'i'​, ​'e'​, ​'
'​, ​'t'​, ​'a'​, ​'p'​, ​'R'​, '
​ l'​}

Como resultado se crea un conjunto con todas las letras que aparecen en la
variable de tipo ​string​ ​cadena​, pero sin repetir y sin guardar un orden
específico.

Diccionarios

El último tipo de colección que vamos a ver son los diccionarios. Junto a las
listas son las colecciones más utilizadas en Python. Se basa en una estructura
mapeada, del inglés ​mapping​ donde cada elemento de la colección se
encuentra identificado mediante una clave única, por lo tanto no puede haber
dos claves iguales en el mismo diccionario. Así pues, para crear un diccionario
deberemos indicar siempre una clave, que generalmente será una cadena de
caracteres, y un valor para cada elemento. Crearemos un nuevo archivo
llamado d​ iccionario.py​ para ir añadiendo el código de esta sección. Podemos
crear un diccionario vacío escribiendo unas simples llaves {​ }​ sin nada dentro:

diccionario_vacio ​=​ {}

print​(diccionario_vacio)

python3 diccionario.py
{}

Podemos asegurarnos de que es un diccionario utilizando la función ​type()​,


que nos devolverá el tipo al que pertenece una variable:

diccionario_vacio ​=​ {}

print​(​type​(diccionario_vacio))

python3 diccionario.py
<​class ​'dict'​>

Veamos un ejemplo en el que crearemos un diccionario de colores y su


traducción al inglés:

diccionario ​=​ {​'amarillo'​: '


​ yellow'​, ​'azul'​: ​'blue'​, ​'verde'​: ​'green'​}
print​(diccionario)

python3 diccionario.py
{​'amarillo'​: ​'yellow'​, '
​ azul'​: '
​ blue'​, ​'verde'​: ​'green'​}

45
Con esto ya tendríamos un diccionario definido con tres elementos con la
estructura "​clave:valor​". Si quisiéramos saber el valor que tiene el
color ​amarillo​ en esta estructura de datos tendríamos que hacerlo de la
siguiente manera:

diccionario ​=​ {​'amarillo'​: '


​ yellow'​, ​'azul'​: ​'blue'​, ​'verde'​: ​'green'​}

print​(diccionario[​'amarillo'​])

python3 diccionario.py
yellow

Para mostrar el ​valor​ de uno de los elementos del diccionario es necesario


especificar la ​clave​, en este ejemplo la clave es ​'amarillo'​ y el valor
es '​ yellow'​.

También podemos utilizar números como claves o índices, por ejemplo, vamos
a crear un diccionario nuevo llamado números en el que las claves serán
números enteros y los valores un ​string​ con su nombre:

numeros ​=​ {​10​: ​'diez'​, 2


​ 3​: '
​ veintitrés'​, ​57​: ​'cincuenta y siete'​}

Para acceder al valor de un índice del diccionario bastará con especificar la


clave, por ejemplo:

print​(numeros[​57​])

python3 diccionario.py
cincuenta y siete

Los diccionarios también nos permiten crear índices con la clave de


tipo ​string​ y su valor de tipo numérico o entero, por ejemplo, creamos un
diccionario que se llama ​edades​ de la siguiente manera:

edades ​=​ {​'Javier'​: ​18​, ​'Alice'​: ​21​, ​'Bob'​: ​33​}

print​(edades)

python3 diccionario.py
{​'Javier'​: 18, ​'Alice'​: 21, ​'Bob'​: 33}

Del mismo modo que podíamos modificar los registros de una lista también
podremos modificar los registros de un diccionario. Volvamos al ejemplo de
los colores para ver un ejemplo. Vamos a crear un diccionario llamado ​colores​,
este va a contener los nombres de algunos colores como clave y su traducción
al inglés como valor:

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

Ahora vamos a imprimir el diccionario antes de realizar alguna modificación y


después cambiamos el índice o clave ​azul​, que actualmente tiene como
valor b​ lue​, le vamos a poner como nuevo valor ​purple​ y finalmente volvemos a
imprimir el contenido del diccionario para ver que se ha modificado con éxito:

colores ​=​ {​'amarillo'​: '


​ yellow'​, ​'azul'​: ​'blue'​, ​'verde'​: ​'green'​}

print​(colores)

colores[​'azul'​] ​=​ ​'purple'

print​(colores)

python3 diccionario.py
{​'amarillo'​: ​'yellow'​, '
​ azul'​: '​ blue'​, ​'verde'​: ​'green'​}
{​'amarillo'​: ​'yellow'​, '​ azul'​: '​ purple'​, ​'verde'​: ​'green'​}

También podemos borrar una entrada del diccionario, es decir una clave y su
valor, para ello es necesario usar el método ​.del()​ y especificar la clave a
borrar, véase el ejemplo:

del​(colores[​'azul'​])

print​(colores)

python3 diccionario.py
{​'amarillo'​: ​'yellow'​, '
​ verde'​: ​'green'​}

Otra cosa bastante útil que podemos hacer con un diccionario es recorrer
todos sus ítems con un bucle ​for​, veamos un primer ejemplo:

edades ​=​ {​'Javier'​: ​18​, ​'Alice'​: ​21​, ​'Bob'​: ​33​}

for​ clave ​in​ edades:


print​(clave)

python3 diccionario.py
Javier
Alice
Bob

De este modo podremos listar la clave de cada uno de los índices del
diccionario, pero quizás podría interesarnos acceder a los valores. Esto se
puede realizar de la siguiente manera:

for​ clave ​in​ edades:


print​(edades[clave])

47
python3 diccionario.py
18
21
33

Si lo que quisiéramos es mostrar tanto la clave como el valor podríamos


hacerlo de la siguiente manera:

for​ clave ​in​ edades:


print​(clave, edades[clave])

python3 diccionario.py
Javier 18
Alice 21
Bob 33

Sin embargo esta forma es un poco rudimentaria, existen formas más óptimas
de acceder a ambos datos, por ejemplo con el método ​.items()​ y seteando en
el bucle las dos variables de la siguiente manera:

for​ clave, valor ​in​ edades.items():


print​(clave, valor)

python3 diccionario.py
Javier 18
Alice 21
Bob 33

Para terminar esta sección veremos cómo se puede crear una estructura de
datos un poco más avanzada, por ejemplo creando una lista con varios
diccionarios como elementos y cómo podemos luego acceder a los datos de
cada diccionario con un bucle ​for​, bien para mostrarlos o para editarlo,
veamos un ejemplo. Primero crearemos una lista vacía llamada ​alumnos​:

alumnos ​=​ []

A continuación crearemos un diccionario llamado ​a​ y registrará tres datos de


una alumno, por ejemplo ​'Nombre'​, ​'Curso'​ y ​'Clase'​.

a ​=​ {​'Nombre'​: ​'Javier'​, ​'Curso'​: ​1​, ​'Clase'​: ​'A'​}

Añadimos este primer diccionario como un elemento a la lista ​alumnos​:

alumnos.append(a)

48
Repetimos los dos pasos anteriores un par de veces con otros dos alumnos
más:

a ​=​ {​'Nombre'​: ​'Alice'​, ​'Curso'​: ​2,


​ ​'Clase'​: ​'C'​}
alumnos.append(a)

a ​=​ {​'Nombre'​: ​'Bob'​, ​'Curso'​: 3


​ ​, ​'Clase'​: ​'B'​}
alumnos.append(a)

Y finalmente recorremos la lista ​alumnos​ con un bucle for, seteando como


variable ​a​ cada elemento, que en este caso es un diccionario. Luego dentro del
bucle haremos un p​ rint()​ de los tres datos de cada diccionario a​ ​, es
decir '​ Nombre'​, '​ Curso'​ y '​ Clase'​:

for​ a ​in​ alumnos:


print​(a[​'Nombre'​], a[​'Curso'​], a[​'Clase'​])

python3 diccionario.py
Javier 1 A
Alice 2 C
Bob 3 B

Como se puede ver, es relativamente sencillo crear una lista que haga la
función de una simple base de datos de alumnos, y ahora ya sabemos cómo
podemos acceder a los datos de cada elemento de la lista, que son los datos
de los alumnos.

Pilas
El lenguaje Python no implementa una colección del tipo ​pila​ como en otros
lenguajes, sin embargo podemos simularlas fácilmente con listas. Una ​pila​ es
una colección de elementos ordenados y únicamente permite dos acciones,
añadir elementos a la pila y sacar elementos de la pila. Lo interesante de las
pilas es que el último elemento en entrar en la pila es el primero en salir, en
inglés se denomina L​ IFO​ (​Last In, Fist Out​), como si se tratara de una pila de
platos sucios que hay que lavar a mano, se van dejando encima de una mesa,
y luego se van cogiendo uno a uno para lavarlos, en cuyo caso se coge
primero el último plato sucio que se dejó en la pila.
Para simular el comportamiento de una p​ ila​ en Python comenzaremos
creando un nuevo fichero llamado p​ ila.py​ y crearemos una lista
llamada p​ ila​ con unos cuantos valores:

pila ​=​ [​0​, ​1​, ​2​, ​3​, ​4​]

49
A continuación añadimos un nuevo elemento a ​pila​ mediante el
método ​.append()​, que es el que usábamos para añadir elementos a una lista,
ya que es exactamente lo que queremos hacer, puesto que estamos usando
listas para simular una pila. Añadamos también una línea para imprimir el
resultado tras añadir un elemento a la pila:

pila.append(​5​)

print​(pila)

python3 pila.py [0, 1, 2, 3, 4, 5]

Como se puede comprobar, el último elemento que hemos añadido a ​pila​ es


el número ​5​. Ahora para eliminar el último elemento de la pila usaremos un
nuevo método que tienen disponible las listas llamado ​.pop()​:

pila.pop()

print​(pila)

python3 pila.py
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4]

Ahora al ejecutar el programa, se puede ver que tras ejecutar el


método ​.pop()​ si se imprime el contenido de la pila esta ya no tiene el último
elemento. Probemos a añadir después de nuestro código varias llamadas al
método .​ pop()​ seguidas e imprimamos el nuevo estado de la pila:

pila.pop()
pila.pop()
pila.pop()
print​(pila)

python3 pila.py
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4]
[0, 1]

Cada llamada al método ​.pop()​ se elimina el último elemento que entró en


la ​pila​, en este caso se han eliminado 3 elementos más.

Colas
Una ​cola​ es una estructura de datos parecida a las ​pilas​, a diferencia de que el
primer elemento en entrar en la ​cola​ es el primero en salir, en inglés se

50
denomina ​FIFO​ (​First In, First Out​). Podríamos compararlo con una cola para
entrar en la cita con el médico, el que primero llega es atendido y cuando
termina sale.

Para poder ver cómo implementar una ​cola​ en Python primero vamos a crear
un nuevo archivo llamado ​cola.py​ y tendremos que importar el
módulo ​deque​ de la librería estándar de Python ​collections​ de la siguiente
manera:

from​ collections ​import​ deque

Más adelante se explicará en detalle la importación de librerías y módulos de


librerías en Python.

A continuación crearemos una cola vacía mediante el siguiente código:

cola ​=​ deque()

print​(cola)

Si imprimimos el contenido de la cola veremos que aparece como una lista


vacía:

python3 cola.py
deque([])

Ahora vamos a añadir un primer elemento a esta cola que inicialmente está
vacía, como lo que contiene dentro es una ​lista​ podemos usar el
método ​.append()​ que ya habíamos usado anteriormente para añadir
elementos a una lista, por ejemplo una cadena de carácteres, además
imprimimos el contenido de la cola de nuevo para comprobar que la cola ya
tiene al menos un elemento:

cola.append(​'Javier'​)

print​(cola)

python3 cola.py
deque([])
deque([​'Javier'​])

Vamos a añadir un par de elementos más a la ​cola​:

cola.append(​'Bob'​)
cola.append(​'Alice'​)

print​(cola)

51
python3 cola.py
deque([])
deque([​'Javier'​])
deque([​'Javier'​, ​'Bob'​, ​'Alice'​])

Ahora que ya tenemos una ​cola​ con varios elementos, que han sido
introducidos secuencialmente, vamos a usar un método propio del
módulo ​deque​ que hemos importado llamado ​.popleft()​ para eliminar el
elemento de la c​ ola​ que entró primero, en este caso es la cadena ​'Javier'​, e
imprimamos el nuevo estado de la cola:

cola.popleft()
print​(cola)

python3 cola.py
deque([])
deque([​'Javier'​])
deque([​'Javier'​, ​'Bob'​, ​'Alice'​])
deque([​'Bob'​, ​'Alice'​])

El método ​.popleft()​ funciona como el método ​.pop()​ que hemos usado en


las ​pilas​ solo que elimina los elementos de una lista por la izquierda en vez de
por la derecha.

Entradas y salidas de datos


En esta sección vamos a aprender acerca de la entrada y salida de datos en
un programa Python, entendiendo por entrada la forma de capturar
información desde fuera del programa y por salida la forma de exponer los
datos o la información.

La primera forma de capturar información en nuestro programa Python ya la


conocemos, es mediante la función ​input()​, que toma los datos que el usuario
introduce a través del teclado y lo hace como cadenas de texto o ​strings,​ pero
también podemos transformar los datos introducidos para poder trabajar con
ellos o manipularlos.

Entrada de datos por teclado

Para trabajar la entrada de datos por teclado vamos a crear un nuevo archivo
llamado ​entrada_por_teclado.py​ y vamos a ir añadiendo código para su estudio.
Por ejemplo, vamos a hacer un pequeño programa que pida al usuario
introducir un número decimal (​float​), y para ello vamos a usar en primera
instancia la función i​ nput()​. Una vez que el usuario introduzca un número
decimal éste se imprimirá por pantalla:

52
decimal ​=​ ​input​(​'Introduce un número decimal: '​)
print​(decimal)

python3 entrada_por_teclado.py
Introduce un número decimal: 3.14
3.14

Aparentemente lo ha hecho todo correctamente, y así es, ha impreso por


pantalla el valor que hemos introducido. Pero es posible que no sea el tipo de
dato que queremos para luego trabajar con él, en este caso el tipo de dato que
se muestra es de tipo cadena de carácteres o ​string​. Podemos ver el tipo de
dato que tenemos almacenado en la variable d​ ecimal​ de la siguiente manera:

print​(​type​(decimal))

python3 entrada_por_teclado.py
Introduce un número decimal: 3.14
3.14 ​<​class ​'str'​>

Quizás nos interese que cuando el usuario introduzca el número decimal este
se convierta en un tipo ​float​, para ello tendremos que indicarlo usando la
función f​ loat()​ y englobando el ​input()​ dentro:

decimal ​=​ ​float​(​input​('


​ Introduce un número decimal: '​))
print​(decimal)
print​(​type​(decimal))

python3 entrada_por_teclado.py
Introduce un número decimal: 3.14
3.14 ​<​class ​'float'​>

Otra manera de pedir datos por teclado varias veces seguidas puede ser
usando un bucle ​for que realice 3 iteraciones. Por ejemplo, creamos una
variable v​ alores​ de tipo lista vacía:

valores ​=​ []

A continuación creamos un bucle ​for​ que itere 3 veces y en el cuerpo del


bucle invocamos al método ​.append()​ sobre la lista ​valores​ y le añadiremos lo
que el usuario introduzca por teclado. Al finalizar el bucle de 3 iteraciones se
imprimirá el valor de la lista v​ alores​:

for​ x ​in​ r
​ ange​(​3​):
valores.append(​input​(​'Introduce un valor cualquiera: '​))

print​(valores)

53
python3 entrada_por_teclado.py
Introduce un valor cualquiera: abcd
Introduce un valor cualquiera: Hola
Introduce un valor cualquiera: 1234
[​'abcd'​, ​'Hola'​, ​'1234'​]

Como se puede ver, hemos introducido varios valores dentro de una lista,
hemos completado una colección introduciendo datos por teclado varias
veces.

Esta manera de introducir datos no es la más común, solo cuando se están


ejecutando programas o scripts Python en una terminal, normalmente los
datos se suelen obtener mediante la lectura de datos en ficheros o bases de
datos o mediante interfaces gráficas en los que los usuarios completan
formularios de datos.

Entrada de datos por argumentos

Otra posibilidad de pasar datos externos a un programa en Python es a través


de argumentos. Se trata de una serie de datos que se han de escribir a la hora
de ejecutar el programa, separados por espacios y siempre a continuación del
nombre del archivo que contiene el código Python, y que podremos utilizar
dentro del programa:

python3 nombre_del_programa.py argumento1 argumento2 argumentoN

Es importante aclarar que si se quiere pasar como argumento una cadena de


carácteres que contiene espacio, por ejemplo una frase corriente, esta debe ir
siempre englobada entre comillas simples o dobles, de lo contrario entenderá
cada palabra de la frase como un argumento diferente. Para poder ver un
ejemplo crearemos el archivo e​ ntrada_por_argumentos.py​ y añadiremos el
siguiente código de ejemplo:

import​ sys

print​(sys.argv)

Para poder pasarle al programa argumentos será necesario importar al inicio


del código la librería interna de Python llamada ​sys​. A continuación imprimimos
todos los argumentos que se le pasan al programa Python mediante la
instrucción s​ ys.argv​, que imprimirá todos los argumentos como elementos de
una lista:

python3 entrada_por_argumentos.py ​'Todo esto es un argumento'​ 3.14 -27


[​'entrada_por_argumentos.py'​, ​'Todo esto es un argumento'​, ​'3.14'​, ​'-27'​]

54
Nótese que el propio nombre del programa es en sí un argumento, el primer
argumento de la lista, el que está en la posición ​0​. Todos los demás
argumentos se han convertido a formato ​string​ ​automáticamente y cada uno
está en una posición del índice de la lista, respetando el mismo orden que se
empleó a la hora de escribirlos al ejecutar el programa.

Ahora vamos a cambiar el código de nuestro programa para que imprima una
frase un número de veces. Haremos que la frase a imprimir sea el primer
argumento (entre comillas simples o dobles) y el número de veces que se
imprimirá será el segundo argumento:

import​ sys

texto ​=​ sys.argv[​1​]


repeticiones ​=​ ​int​(sys.argv[​2​])

for​ r ​in​ r
​ ange​(repeticiones):
print​(texto)

python3 entrada_por_argumentos.py ​'Hola mundo!'​ 3


Hola mundo​!
Hola mundo​!
Hola mundo​!

Como se puede ver, el programa ha impreso el primer argumento ​'Hola


mundo!'​ tantas veces como indica el número del segundo argumento.
Tal y como está hecho este simple programa podría fallar si no se le pasan los
argumentos esperados. Para ello es recomendable añadir un pequeño control
sobre los argumentos al inicio, por ejemplo:

import​ sys

if​ ​len​(sys.argv) ​==​ ​3​:


texto ​=​ sys.argv[​1]​
repeticiones ​=​ ​int​(sys.argv[​2​])

for​ r ​in​ r
​ ange​(repeticiones):
print​(texto)
else​:
print​(​'Error, introduce los argumentos correctamente.'​)
print​(​'Ejemplo: '​ +
​ ​ sys.argv[​0​] ​+​ ​' \'Texto cualquiera\' 3'​)

python3 entrada_por_argumentos.py ​'Hola mundo!'


Error, introduce los argumentos correctamente.
Ejemplo: entrada_por_argumentos.py ​'Texto cualquiera'​ 3

En esta ocasión el programa devuelve un error porque no se cumple la


condición que hemos establecido, es decir, que el programa tiene 3

55
argumentos, contando como el nombre del archivo del propio programa como
primer argumento.

Salida de datos

Para manejarse ágilmente en Python es necesario conocer bien cómo manejar


las salidas de datos. Hasta ahora hemos visto cómo mostrar por pantalla
cadenas de texto o variables de diferentes tipos. Veamos un ejemplo creando
un nuevo archivo ​salida_por_pantalla.py​:

​ ​ '
a = ​ me llamo Javier'
b =​ ​ 3​

print​(​'Hola'​, a, ​', mi número favorito es el'​, b)

python salida_por_pantalla.py
Hola me llamo Javier, mi número favorito es el 3

Esta es una manera muy simple de imprimir cadenas de texto mezcladas con
variables de diferentes tipos, pero si queremos tener un mayor control sobre
las variables que queremos imprimir debemos comenzar a usar un formato de
escritura de las cadenas de carácteres, en Python se hace con el
método .​ format()​, por ejemplo:

​ ​ '
a = ​ me llamo Javier'
b =​ ​ 3​

print​(​'Hola ​{}​, mi número favorito es ​{}​'​.format(a, b))

python salida_por_pantalla.py
Hola me llamo Javier, mi número favorito es 3

Como se puede ver, hemos creado todo el contenido a imprimir dentro de la


función ​print()​ y hemos añadido unas referencias mediante llaves ​{}​ donde irá
el valor de las variables que se declaran dentro del método ​.format()​,
respetando el mismo orden y separadas por coma. Al ejecutar el programa el
resultado es el mismo.
Existe una manera más precisa de indicar que qué referencia va qué variable, y
esto se consigue mediante el número del índice en el que aparece una variable
ubicada dentro del método .​ format()​, solo habría que poner ese índice dentro
de l referencia, por ejemplo:

​ ​ '
a = ​ me llamo Javier'
b =​ ​ 3​ ​ ​print​(​'Hola ​{0}​, mi número favorito es ​{1}​'​.format(a, b))

print​(​'Hola ​{1}​, mi número favorito es ​{0}​'​.format(a, b))

56
python salida_por_pantalla.py
Hola me llamo Javier, mi número favorito es 3
Hola 3, mi número favorito es me llamo Javier

En este último ejemplo se ha ejecutado el mismo ​print()​ con las mismas


variables dos veces, en el primero se ha indicado primero el valor de la
variable t​ exto​ mediante la referencia ​'{0}'​, y a continuación la
variable n​ umero​ mediante la referencia ​'{1}'​. Pero en la segunda ejecución
del p​ rint()​ hemos especificado primero la referencia ​'{1}'​, que imprimirá el
valor de la segunda variable (​b​) y a continuación la referencia '​ {0}'​, que
imprimirá el valor de la primera variable (​a​). E incluso se puede usar varias
veces una variable indicando tantas veces como se desee la referencia a la
misma, por ejemplo:

​ ​ '
a = ​ me llamo Javier'
b =​ ​ 3​

print​(​'Hola ​{0}​, mi número favorito es ​{1} y tengo ​{1} mascotas.'​.format(a,


b))

python salida_por_pantalla.py
Hola me llamo Javier, mi número favorito es 3 y tengo 3 mascotas.

De esta forma ya no tenemos que preocuparnos del orden de las referencias,


solo importa el índice que tengan dentro, y además se pueden usar repetidas
veces. Pero todavía dependemos del orden en el que están declaradas las
variables dentro del método ​.format()​. Existe una manera para poder asociar
las variables a una clave, por ejemplo asociar la variable ​a​ con la clave ​texto​ y
la variable b​ ​ con la clave n​ umero​, de modo que se pueda hacer referencia a la
clave en vez de al índice o posición de las variables, por ejemplo:

​ ​ '
a = ​ me llamo Javier'
b =​ ​ 3​

print​(​'Hola ​{texto}​, mi número favorito es ​{numero}​.'​.format(​numero​=​b,


texto​=​a))

python salida_por_pantalla.py
Hola me llamo Javier, mi número favorito es 3.

El método ​.format()​ permite también alinear a la izquierda, derecha o centrar


un texto, pero lo hará dentro de un espacio con un tamaño definido por el
usuario. Por ejemplo, vamos a mostrar una palabra en un espacio con un
tamaño de 20 carácteres, y además queremos alinear a la derecha:

palabra ​=​ ​'Hola'​ ​print​(​'​{0​:>20​}​'​.format(palabra))

python3 salida_por_pantalla.py

57
Hola

Analicemos la referencia ​{0:>20}​. El ​0​ hace referencia a la posición del índice


de la variable que queremos usar, como en este caso solo tenemos una
variable dentro del método ​.format()​ pondremos el ​0​. También se puede dejar
vacío o usar claves, tal y como hemos visto antes. Los dos puntos son solo un
separador. Para alinear el valor de la variable p​ alabra​ a la derecha se ha
utilizado el símbolo >​ ​ y finalmente se indica el tamaño que tendrá el espacio de
carácteres, en este caso 20 carácteres.

Si se quiere alinear a la izquierda bastaría con eliminar el símbolo ​>​ de la


referencia:

palabra ​=​ ​'Hola'


print​(​'​{0​:20​}​'​.format(palabra))

python3 salida_por_pantalla.py
Hola

En este caso no se aprecia directamente los 16 espacios que hay a la derecha


de la palabra ​Hola​, pero están ahí. Veamos de qué manera se puede centrar un
texto.

palabra ​=​ ​'Hola'


print​(​'​{0​:^20​}​'​.format(palabra))

python3 salida_por_pantalla.py
Hola

Aquí se pueden apreciar los 8 espacios que hay por la izquierda, luego los 4
carácteres que tiene la palabra ​Hola​ y después los otros 8 espacios restantes,
en total suman 20 carácteres.

Otra posibilidad que nos ofrece el método ​.format()​ es la de truncar una


cadena de caracteres, por ejemplo, vamos a truncar la palabra ​Hola​ mostrando
solo los tres primeros carácteres:

palabra ​=​ ​'Hola'


print​(​'​{0​:.3​}​'​.format(palabra))

python3 salida_por_pantalla.py
Hol

También se puede combinar un truncamiento con una alineación, por ejemplo:

palabra ​=​ ​'Hola'


print​(​'​{0​:>10.3​}​'​.format(palabra))

58
python3 salida_por_pantalla.py
Hol

El método ​.format()​ no solo da formato a cadenas de texto, también en


números, y esto puede resultar muy útil, ya que podríamos necesitar alinear
números de diferentes longitudes en columnas, redondear números decimales
o rellenarlos con ceros o espacios por la izquierda o derecha. Por ejemplo,
vamos a formatear los números enteros 1​ 000​, 1​ 00​ y 1​ 0​ para que queden
alineados a la derecha rellenandolos con espacios:

print​(​'​{​:4d​}​'​.format(​10​))
print​(​'​{​:4d​}​'​.format(​100​))
print​(​'​{​:4d​}​'​.format(​1000​))

python3 salida_por_pantalla.py
10
100
1000

En la referencia ​{:4d}​ el ​4​ es el espacio de caracteres que vamos a usar para


completar la cadena de carácteres que se quiere imprimir, y la letra ​d​ es para
indicar que es un número (​digit)​ , por defecto se añadirán tantos espacios por
la izquierda como sean necesarios para que el total de caracteres sea ​4​.
Si en lugar de rellenar con espacios por la izquierda queremos que se rellenan
con ceros solo tendremos que añadir un 0​ ​ por delante de 4​ d​, quedando de la
siguiente manera:

print​(​'​{​:04d​}​'​.format(​10​))
print​(​'​{​:04d​}​'​.format(​100​))
print​(​'​{​:04d​}​'​.format(​1000​))

python3 salida_por_pantalla.py
0010
0100
1000

Veamos un ejemplo con números decimales o de coma flotante. Por ejemplo,


si tenemos el número ​pi​ con nueve decimales, es decir ​3.141592653​, y
queremos mostrar solo los dos primeros decimales:

print​(​'​{​:.2f​}​'​.format(​3.141592653​))

python3 salida_por_pantalla.py
3.14

En este caso en la referencia ​{:.2f}​ el ​.2​ indica el número de decimales a


mostrar, y la ​f​ que el dato es de coma flotante (​float)​ .

59
Ahora vamos a añadir otro número decimal, por ejemplo ​153.21​, y vamos a
alinear tanto la parte entera, como la coma y tres decimales:

print​(​'​{​:7.3f​}​'​.format(​3.141592653​))
print​(​'​{​:7.3f​}​'​.format(​153.21​))

python3 salida_por_pantalla.py
3.142
153.210

Al querer imprimir tres decimales debemos contar esos tres carácteres más el
carácter del punto (​.​) y los tres carácteres que representan la parte entera más
grande de los dos número decimales, en total hacen 7 carácteres. Así pues, en
la referencia {​ :7.3f}​ el 7 es la cantidad de carácteres que se van a mostrar,
el .​ 3​ la cantidad de decimales, y si el número no tiene esa cantidad de
decimales por defecto rellenará con ceros por la derecha, y finalmente la
letra f​ ​ para indicar que se trata de números de coma flotante (​float)​ .

Si se quisiera rellenar con ceros por la izquierda en vez de espacios hay que
hacer lo mismo que en el ejemplo anterior, añadir en la referencia un ​0​ delante
del número de caracteres que ocupará la cadena:

print​(​'​{​:07.3f​}​'​.format(​3.141592653​))
print​(​'​{​:07.3f​}​'​.format(​153.21​))

python3 salida_por_pantalla.py
003.142
153.210

Funciones
Las funciones son fragmentos de código que se pueden invocar o ejecutar
varias veces gracias a un nombre único que las identifica. Estas pueden recibir
y devolver información para comunicarse con el programa principal.
Crearemos un archivo llamado f​ unciones.py​ en el que iremos añadiendo
código a medida que vayamos aprendiendo los conceptos y el funcionamiento
de las funciones en Python.

Definición de funciones

Para definir una función primero debemos utilizar la palabra reservada ​def​, a
continuación el nombre que le queramos poner a la función, después apertura
y cierre de paréntesis sin nada en su interior ​()​ y finalmente dos puntos ​:​. En la

60
línea siguiente debemos dejar una identación o tabulación a la izquierda y a
continuación el código que queremos que se ejecute al invocar a esta función,
por ejemplo:

def​ ​saludo​():
print​(​'Hola mundo!'​)

Hasta aquí ya tenemos el código de una función. Pero ahora debemos añadir
la línea de código para invocar, bastaría con escribir simplemente el nombre
de la función y los paréntesis, por ejemplo:

def​ ​saludo​():
print​(​'Hola mundo!'​)

saludo()

Si ejecutamos nuestro programa ​funciones.py​ se obtendrá la siguiente salida


por pantalla:

python3 funciones.py
Hola mundo​!

Al igual que con las variables, es recomendable utilizar nombres en


minúsculas, sin acentos ni carácteres que no sean alfanuméricos y el guión
bajo _​ ​. También se puede usar ​camelcase,​ es decir, poner nombres
compuestos por varias palabras sin guiones bajos ni espacios y poniendo en
mayúscula solo la primera letra de cada palabra menos la primera, por
ejemplo m​ iFuncionParaSaludar​ o c​ errarPrograma​.

Vamos a crear un ejemplo un poco más complicado que un simple saludo, por
ejemplo una función que muestre por pantalla la tabla de multiplicar del
número 5:

def​ ​tabla_de_multiplicar_del_5​():
for​ i ​in​ ​range​(​10​):
print​('​ 5 * ​{}​ = ​{}​'​.format(i, i​*​5​))

tabla_de_multiplicar_del_5()

python3 funciones.py
5 ​*​ 0 = 0
5 ​*​ 1 = 5
5 ​*​ 2 = 10
5 ​*​ 3 = 15
5 ​*​ 4 = 20
5 ​*​ 5 = 25
5 ​*​ 6 = 30
5 ​*​ 7 = 35

61
​ ​ 8 = 40
5 *
5 *​ ​ 9 = 45

En este caso hemos utilizado un bucle ​for()​ para iterar a través de todos los
valores comprendidos entre ​0​ y ​9,​ esto lo conseguimos mediante ​range(10)​, y
cada valor del rango en la iteración lo almacenaremos en la variable
temporal i​ ​. Después se añade dentro del cuerpo del bucle ​for()​ un ​print()​ de
un mensaje que contiene dos referencias, la primera mostrará el valor de i​ ​ en
ese momento y la segunda mostrará el resultado de multiplicar i​ ​ por 5​ ​.

Dentro del cuerpo de una función podremos crear nuevas variables y utilizar
cualquier sentencia de las ya vistas, e incluso llamar a otras funciones. Es muy
importante tener en cuenta que cuando se define una variable nueva dentro
del cuerpo de una función, esta variable solo estará disponible y visible dentro
del cuerpo de la función, no fuera. Veamos un ejemplo en el que
provocaremos un error al tratar de utilizar fuera de la función una variable
definida dentro de la función:

def​ ​mi_funcion​():
nombre ​=​ ​'Frank'
print​(nombre)

python3 funciones.py
Traceback (most recent call last):
File ​"funciones.py"​, line 4, i
​ n​ <
​ m
​ odule​>
print(nombre)
NameError: name ​'nombre'​ is not defined

El error dice que la variable ​nombre​ que quiero utilizar en la línea 4 de mi


programa no existe. Sin embargo dentro de la función se ha creado, lo que
sucede es que esta función la hemos creado en un ámbito local, lo que quiere
decir que no estará disponible fuera de la función.
Veamos otro ejemplo en el que definimos una variable ​nombre​ fuera de la
función, al inicio de nuestro código, y después en la función vamos a hacer
uso de esa variable. Finalmente invocamos la función:

nombre ​=​ ​'Frank'

def​ ​mi_funcion​():
print​(nombre)

mi_funcion()

python3 funciones.py
Frank

62
En este caso si tenemos la variable ​nombre​ disponible dentro de la función
porque es una variable definida en un ámbito global. El único requisito que
necesita una variable global para ser utilizada en funciones es que esta
variable debe estar definida antes de la llamada a la función, si no daría un
error indicando que se está utilizando una variable que no existe.

Retorno de valores

Hemos visto que una variable dentro de una función no tiene alcance fuera de
ella. Para conectar la función con el exterior podemos devolver valores al
programa principal. Esto se realiza con la palabra reservada ​return​ en el
cuerpo de la función. Veamos un ejemplo en el que la
función m​ i_funcion​ devuelve una cadena de texto al ser ejecutada, en este
caso se ejecutará dentro de un p​ rint()​:

def​ ​mi_funcion​():
return​ ​'Una cadena de texto'

print​(mi_funcion())

python3 funciones.py
Una cadena de texto

También podemos asignar a una variable el valor devuelto por la función para
poder utilizarlo varias veces en el programa principal sin necesidad de invocar
a la función cada vez que se quiera utilizar es valor, por ejemplo:

def​ ​mi_funcion​():
return​ ​'Hola mundo!'

saludo ​=​ mi_funcion()

print​(saludo)
print​(saludo)
print​(saludo)

python3 funciones.py
Hola mundo​!
Hola mundo​!
Hola mundo​!

La función se ha invocado una sola vez, se ha almacenado su retorno en la


variable ​saludo​ y luego hemos utilizado varias veces la variable. Hay que tener
en cuenta que cuando en una función se ejecuta una línea que contiene
un r​ eturn​ esta devuelve el dato que tenga que devolver y finaliza la ejecución
de la función, no ejecutaría las líneas que pudiera tener por debajo.

63
Las funciones pueden devolver cualquier tipo de dato de los disponibles en
Python y que hemos visto al principio. Veamos un ejemplo en el que la función
devuelve un dato de tipo ​lista​, y veamos también cómo podemos utilizar
índices y ​slicing​ con el valor devuelto.

def​ ​mi_funcion​():
return​ [​1​, ​2​, ​3​, ​4,
​ ​5​]

print​(mi_funcion()) ​# La lista entera


print​(mi_funcion()[​0​]) ​ # Solo el primer elemento
print​(mi_funcion()[​-​1​]) # ​ Solo el ultimo elemento
print​(mi_funcion()[​1​:​4]
​ ) ​ # Solo desde el segundo hasta el cuarto

python3 funciones.py
[1, 2, 3, 4, 5]
1
5
[2, 3, 4]

En este ejemplo estamos ejecutando la función 4 veces, esto complica un


poco la ejecución del programa, por lo que es recomendable asignar a una
variable el valor que devuelve la función, tal y como hemos visto antes con una
cadena de texto, y utilizar la variable. De ese modo solo se ejecutaría la
función una vez y el resultado sería el mismo:

def​ ​mi_funcion​():
return​ [​1​, ​2​, ​3​, ​4,
​ ​5​]

lista_numeros ​=​ mi_funcion()

print​(lista_numeros)
print​(lista_numeros[​0​])
print​(lista_numeros[​-​1]
​ )
print​(lista_numeros[​1​:4​ ​])

python3 funciones.py
[1, 2, 3, 4, 5]
1
5
[2, 3, 4]

Las funciones en Python no tienen por que devolver solo un dato, pueden
devolver varios datos separados por coma, y estos no tienen porqué ser del
mismo tipo. Veamos un ejemplo en el que nuestra función devuelve tres datos
diferentes, una cadena de texto, un número y una lista:

def​ ​mi_funcion​():
return​ ​'Hola'​, 7
​ 7​, [​1​, 2
​ ​, ​3​]

64
resultado ​=​ mi_funcion()

print​(resultado)

python3 funciones.py
(​'Hola'​, 77, [1, 2, 3])

El valor que de devuelve es una colección de tipo ​tupla​ como las que ya
hemos visto anteriormente, y como ya sabemos, las tuplas son colecciones
inalterables y que pueden contener datos de diferentes tipos.

Podemos aprovechar este retorno de varios datos (del mismo tipo o no) para
asignar cada valor devuelto a diferentes variables en una sola línea de la
siguiente manera:

def​ ​mi_funcion​():
return​ ​'Hola'​, 7
​ 7​, [​1​, 2
​ ​, ​3​]

cadena, numero, lista ​=​ mi_funcion()

print​(cadena) ​print​(numero) ​print​(lista)

python3 funciones.py
Hola
77
[1, 2, 3]

Como se puede ver, se asignará cada valor devuelto, del tipo que sea, a cada
variable, siguiendo el mismo orden separadas por comas.

Enviando valores

Ya hemos visto cómo las funciones pueden devolver información desde su


interior al programa principal, pero también pueden recibir información desde
el programa principal a su interior. Esto se consigue mediante argumentos.
Para ello vamos a crear un nuevo archivo llamado ​suma_de_numeros.py​ y dentro
escribiremos el código de un simple programa Python que utiliza una función
que sume dos números. Estos dos números que sumará serán números que
podrá recibir como parámetros:

def​ ​suma​(a, b):


return​ a ​+​ b

print​(suma(​1​, ​2​))
print​(suma(​27​, ​-​14​))

python3 suma_de_numeros.py
3

65
13

En este caso sí que nos interesa ejecutar la misma función tres veces. El
código de la función que se va a ejecutar cada vez es exactamente el mismo,
lo único que cambia son los argumentos que pasamos a la hora de llamar a la
función, a veces pasamos los valores ​1​ y ​2​, y otras veces pasamos los
valores 2​ 7​ y -​ 14​. No importa qué números pasemos, la función devolverá
mediante la instrucción r​ eturn​ el resultado de lo que pasemos como a​ ​ más lo
que pasemos como b​ ​.

Argumentos y parámetros

Los parámetros son aquellas claves que se definen dentro de los paréntesis a
la hora de definir una función, en este caso los parámetros son ​a​ y ​b​:

def​ ​suma​(a, b):

Y los argumentos son aquellos valores que introducimos dentro de los


paréntesis cuando hacemos uso de la función, en este ejemplo los argumentos
son 1​ ​ y 2​ ​ en la primera llamada a la función s​ uma y 2​ 7​ y -​ 14 en la segunda
llamada a la función s​ uma​:

print​(suma(​1​, ​2​))
print​(suma(​27​, ​-​14​))

Es importante tener en cuenta que el mero hecho de definir parámetros dentro


de los paréntesis de la función cuando definimos esta con ​def​ estamos
creando variables locales, es decir, nombres de variables que solo estarán
disponibles en el cuerpo de la función, en este caso las variables serán ​a​ y ​b​.
Hemos visto que podemos especificar una serie de parámetros en la definición
de una función y luego pasarle valores como argumentos a la hora de hacer
uso de la función, pero tal y como lo hemos visto habrá una correlación entre
los argumentos y los parámetros siguiendo estrictamente el orden en el que
están definidos. Ahora vamos a ver cómo podemos usar argumentos con
nombres o claves que hagan referencia a los nombres de los parámetros, de
ese modo ya no importará el orden en el que se coloquen los argumentos,
véase el siguiente ejemplo en un nuevo archivo llamado r​ esta_de_numeros.py​:

def​ ​resta​(a, b):


return​ a ​-​ b

print​(resta(​b​=​2​, ​a​=​1​))

python3 resta_de_numeros.py
-1

66
A la hora de hacer uso de la función ​resta()​ le estamos diciendo que el
parámetro ​b​ va a valer ​2​ y el parámetro ​a​ va a valer 1. Están desordenados,
pero el resultado es el que se espera.

En los parámetros de una función también podemos especificar valores por


defecto, por ejemplo al parámetro ​b​ le vamos a asignar un valor por defecto
de 5​ ​. Esto quiere decir que ya no sería obligatorio especificar un valor para el
parámetro b​ ​, y si no se especifica un valor para dicho su valor será siempre ​5​:

def​ ​resta​(a, b​=​5​):


return​ a ​-​ b

print​(resta(​2​))

python3 resta_de_numeros.py
-3

Si se especificara un valor para el parámetro ​b​ siempre tendrá preferencia el


valor especificado, el valor por defecto se ignorará:

def​ ​resta​(a, b​=​5​):


return​ a ​-​ b

print​(resta(​7​, ​3​))

python3 resta_de_numeros.py
4

Pasar información por valor y por referencia

Cuando enviamos información a una función generalmente se suele hacer


como hemos visto hasta ahora, que es un envío de información por valor. Esto
significa que se crea una copia de la información que enviamos en sus propias
variables, variables locales. Pero hay un caso excepcional, las colecciones,
listas, diccionarios y conjuntos, estos datos se envían por referencia. Eso
significa que en lugar de manejar una copia del dato dentro de la función
estaremos manejando el dato original, por lo que si le realizamos alguna
modificación los cambios se verán reflejados en el exterior, porque hacen
referencia a la variable externa.

Veamos un ejemplo en el que pasamos información a una función por valor,


para ello crearemos un nuevo archivo llamado ​valor_y_referencia.py​ y aquí
añadiremos el siguiente código:

def​ ​doblar_valor​(numero):
print(numero ​*​ ​2​)

67
n ​=​ ​10
doblar_valor(n)
print​(n)

python3 valor_y_referencia.py
20
10

En este caso tenemos una función llamada ​doblar_valor()​ que recibe un


parámetro llamado ​numero​, lo que hace esta función es doblar el valor del
número que se le pase como argumento, como su nombre bien indica y
finalmente lo imprime. Una vez definida la función, en el programa principal
creamos una variable llamada n​ ​ con valor 1​ 0​, y luego invocamos a la
función d​ oblar_valor()​ pasándole el valor de n​ ​ como argumento.

Finalmente imprimimos el valor de ​n​ después de haber ejecutado la función


que dobla su valor. Si nos fijamos en el resultado de la ejecución del programa
podremos ver que cuando imprimimos el valor de n​ ​ no se ha doblado. Esto es
correcto, la variable n​ ​ sigue valiendo 1​ 0​ después de pasarla como argumento a
la función d​ oblar_valor()​, ya que el valor de n​ ​ solo se copia en el interior de la
función bajo una nueva variable local llamada n​ umero​ y es esta y solo esta
variable la que se dobla.

Ahora vamos a ver un ejemplo en el que vamos a pasar información a una


función por referencia. El ejemplo va a ser muy parecido al anterior, solo que
en este caso en vez de pasar como argumento un número entero vamos a
pasar una lista. Vamos a crear una función llamada ​doblar_valores()​ y en su
cuerpo recorreremos todos los elementos de la lista iterando a través de ellos
mediante un bucle f​ or​. Además de los elementos nos quedaremos con la
posición de cada uno mediante el uso de e​ numerate()​, finalmente a cada
elemento le vamos a doblar su valor actual. En el programa principal
crearemos una variable llamada n​ s​ con una lista de números, después se
ejecuta la función d​ oblar_valores()​ a la que se le pasará la lista n​ s​ como
argumento y finalmente se imprime el valor de la lista n​ s​ en ese momento, tras
ejecutar la función.

def​ ​doblar_valores​(numeros):
for​ i, n ​in​ ​enumerate​(numeros):
numeros[i] ​*=​ 2 ​

ns ​=​ [​10​, ​50​, ​100​]


doblar_valores(ns)

print​(ns)

python3 valor_y_referencia.py
[20, 100, 200]

68
Como se puede ver, en este caso si se ha doblado el valor inicial de cada
elemento de la lista. Esto sucede porque tal y como se explicaba al inicio de
este punto, las colecciones, listas, diccionarios y conjuntos se pasan a las
funciones por referencia, en vez de una copia local dentro de la función se
accede a los elementos que contiene el objeto original.

Si quisiéramos modificar el valor de una variable de tipo entero (​int​) que se


encuentra fuera de la función tendríamos que hacer un pequeño truco y
modificar ligeramente el código del primer ejemplo de la siguiente manera:
def​ ​doblar_valor​(numero):
return​ numero ​*​ ​2

n ​=​ ​10​ n ​=​ doblar_valor(n)


print​(n)

python3 valor_y_referencia.py
20

En este caso si se ha doblado el valor de la variable ​n​ tras ejecutar la


función ​doblar_valor()​, pero hemos tenido que añadir un ​return​ del valor
multiplicado por dos en la función y por otro lado hemos tenido que asignar
este r​ eturn​ de la función como nuevo valor de la variable ​n​.

En el caso de una colección podríamos evitar que se modifique su valor


haciendo otro truco y modificando ligeramente el código del segundo ejemplo.
Tendríamos que pasar como argumento una copia de la lista en vez de la lista
original. En Python esto se consigue mediante el ​slicing​ ​[:]​, por ejemplo:

def​ ​doblar_valores​(numeros):
for​ i, n ​in​ ​enumerate​(numeros):
numeros[i] ​*=​ 2 ​

ns ​=​ [​10​, ​50​, ​100​]


doblar_valores(ns[:])

print​(ns)

python3 valor_y_referencia.py
[10, 50, 100]

En este caso se ha pasado como referencia una copia de la lista ​ns​ no la lista
original, por lo que la función ha duplicado el valor de cada elemento solo de
en la copia, la variable ​ns​ contiene una lista intacta.

69
Argumentos indeterminados

Hasta ahora hemos visto dos maneras de pasar información a una función en
Python, una es mediante una serie de argumentos que luego debe coincidir en
número y posición con los parámetros definidos en la función, y otra manera
que hemos visto es mediante nombres en los parámetros, donde ya no
importa el orden. Pero ¿y si queremos pasar un número indeterminado de
argumentos?

Para ver un ejemplo de cómo se puede hacer crearemos un nuevo archivo


llamado ​indeterminados_posicion.py​ y escribiremos el siguiente código:

def​ ​indeterminados_posicion​(​*​args):
print​(args)

indeterminados_posicion(​5​, '
​ Hola mundo!'​, [​1​, ​2​, ​3​])
indeterminados_posicion(​'aaa'​, ' ​ bbb'​)
indeterminados_posicion(​1​, -​ ​15​, ​27​, ​'My name is Bobby Brown'​, ​3.14​)

python3 indeterminados_posicion.py
(5, ​'Hola mundo!'​, [1, 2, 3])
(​'aaa'​, ​'bbb'​)
(1, -15, 27, ​'My name is Bobby Brown'​, 3.14)

En la definición de la función hemos tenido que añadir ​*args​ (se puede llamar
como se quiera, pero es el nombre más común) como parámetro, lo que
quiere decir que se almacenará en una variable local ​args​ todos los
argumentos, sean los que sean. En cada ejecución de la
función i​ ndeterminados_posicion()​ se ha pasado una cantidad diferente de
argumentos y de diferentes tipos. El resultado en todos los casos es
una t​ upla​ con todos los elementos en el orden en el que fueron pasados.
Recordemos que las tuplas son colecciones inalterables, no se puede
modificar su contenido.

En este ejemplo los argumentos que se pasen en un orden tendrán ese mismo
orden dentro de la función como parámetros.
Pero si quisiéramos pasar un número indeterminado de parámetros mediante
una serie de nombres también podemos hacerlo. Para ello vamos a crear un
nuevo archivo llamado i​ ndeterminados_nombre.py​ y escribiremos en él el
siguiente código:

def​ ​indeterminados_nombre​(*
​ *​kwargs):
print​(kwargs)

indeterminados_nombre(​n​=​5​, c
​ ​=​'Hola mundo!'​, ​l​=​[​1​, ​2​, ​3​])

70
python3 indeterminados_nombre.py
{​'n'​: 5, ​'c'​: ​'Hola mundo!'​, ​'l'​: [1, 2, 3]}

Funciones recursivas

Una función recursiva es una función que se puede ejecutar a sí misma varias
veces. Tienen un comportamiento muy similar a las de las sentencias iterativas
que hemos ido aprendiendo. Vamos a crear un nuevo archivo
llamado f​ uncion_recursiva.py​ y vamos a escribir el siguiente código en el que
podremos ver una función que realiza una cuenta atrás recursivamente y al
finalizar muestra un mensaje:

def​ ​cuenta_atras​(num):
num ​-=​ ​1
if​ num ​>​ ​0​:
print​(num)
cuenta_atras(num)
else​:
​ Despegue!'​)
print​('

cuenta_atras(​5​)

python3 funcion_recursiva.py
4
3
2
1
Despegue​!

Simplemente recibe un número como parámetro, le resta una unidad y a


continuación comprueba si el nuevo valor de este número es mayor que ​0​, en
cuyo caso lo imprime por pantalla y se vuelve a ejecutar a sí misma. Lo más
importante en una función recursiva es que esta deje de invocarse a sí misma
en algún momento, por lo que tenemos que meter un sistema de control en el
que si se da una circunstancia que pueda parar. En este caso, en un momento
dado la variable local n​ um​ valdrá 0​ ​, no se cumplirá la condición y el condicional
saldrá por la sentencia e​ lse​ imprimiendo el mensaje y finalizando el programa.
Vamos a ver otro ejemplo clásico de función recursiva, el cálculo del factorial
de un número. El factorial de un número es el entero que corresponde a ese
mismo número multiplicado por todos los números que van antes de él hasta
el 1, por ejemplo, el factorial de 5​ ​ en 120, y se calcula de la siguiente manera:

5! = 1 x 2 x 3 x 4 x 5 = 120

Vamos a implementar esta función recursiva mediante el siguiente código:

def​ ​factorial​(num):
if​ num ​>​ ​1​:

71
num ​*=​ factorial(num ​-​ ​1​)
return​ num

print​(factorial(​5​))

python3 funcion_recursiva.py
120

En este ejemplo se va multiplicando el número ​5​ por si mismo menos uno,


hasta llegar a ​1​, entonces para. Como resultado da ​120​, que es el factorial de ​5​.

Funciones integradas en Python


Existen una serie de funciones que vienen integradas en el lenguaje Python.
Muchas de ellas ya las estamos utilizando desde el comienzo de esta
documentación, por ejemplo la función ​print()​ para imprimir información por
pantalla, la función ​format()​ para darle formato a la información, las
funciones r​ ange()​ y e​ numerate()​ para hacer más fáciles las iteraciones de
algunos datos, etc. Pero existen muchas otras, en este punto veremos algunas
de ellas, y para ello vamos a crear el archivo f​ uncion_integrada.py​.
Por ejemplo, existen algunas funciones, que también hemos utilizado
anteriormente, que sirven para convertir información de un tipo a otro, esto se
conoce como ​casting​ , como es el caso de las
funciones i​ nt()​, f​ loat()​, s​ tr()​, l​ ist()​, etc.

Veamos un ejemplo en el que tenemos una variable de tipo cadena de texto


o ​string​ y mediante la función ​int()​ vamos a convertirla en una variable con un
dato de tipo entero o ​integer​:

numero ​=​ ​'3'


print​(​'Aquí la variable numero tiene un valor de ​{} de tipo
{}​'​.format(numero, ​type​(numero)))
numero ​=​ ​int​(numero)

print​(​'Aquí la variable numero tiene un valor de ​{} de tipo


{}​'​.format(numero, ​type​(numero)))

python3 funcion_integrada.py
​ ​class '
Aquí la variable numero tiene un valor de 3 de tipo < ​ str'​>
Aquí la variable numero tiene un valor de 3 de tipo <​ ​class '​ int'​>

Lo mismo con los números de coma flotante o ​float,​ podremos usar la


función ​float()​ para hacer el ​casting​ de un valor de tipo cadena de texto
o ​string​ a un dato de tipo ​float​ de la siguiente manera:

numero ​=​ ​'3.14'

72
print​(​'Aquí la variable numero tiene un valor de ​{} de tipo
{}​'​.format(numero, ​type​(numero)))
numero ​=​ ​float​(numero)
print​(​'Aquí la variable numero tiene un valor de ​{} de tipo
{}​'​.format(numero, ​type​(numero)))

python3 funcion_integrada.py
​ ​class '
Aquí la variable numero tiene un valor de 3.14 de tipo < ​ str'​>
Aquí la variable numero tiene un valor de 3.14 de tipo <​ ​class '​ float'​>

También podemos hacerlo a la inversa, tomar una variable de tipo entero y


convertirla en un dato de tipo ​string​, por ejemplo:

numero ​=​ ​3
print​(​'Aquí la variable numero tiene un valor de ​{} de tipo
{}​'​.format(numero, ​type​(numero)))
numero ​=​ ​str​(numero)
print​(​'Aquí la variable numero tiene un valor de ​{} de tipo
{}​'​.format(numero, ​type​(numero)))

python3 funcion_integrada.py
​ ​class '
Aquí la variable numero tiene un valor de 3 de tipo < ​ int'​>
Aquí la variable numero tiene un valor de 3 de tipo <​ ​class '​ str'​>

Existen otras funciones integradas en Python que nos servirán para convertir
datos numéricos en diferentes bases, es decir, los números en base 10 o
decimales como 1​ ​, 2​ ,​ 3​ ​, ... a números en base 2 o binarios
cómo 0​ 000​, 0​ 001​, 0​ 010​, ... mediante la función integrada ​bin()​, por ejemplo:

​ ​ 0
a = ​
b =​ ​ 1 ​
c = ​ ​ 2 ​

print​(​'Decimal\tBinario'​)
print​(​'​{}​\t​{}​'​.format(a, ​bin​(a)))
print​(​'​{}​\t​{}​'​.format(b, ​bin​(b)))
print​(​'​{}​\t​{}​'​.format(c, ​bin​(c)))

python3 funcion_integrada.py
Decimal Binario
0 0b0
1 0b1
2 0b10

En este ejemplo el número binario se muestra siempre con los dos


carácteres ​0b​ por la izquierda para identificarlo como tal, y a continuación los
bits 0​ ​ o 1​ ​ equivalentes al número decimal original.

73
También podemos convertir un número en base decimal a base 16 o
hexadecimal con la función integrada ​hex()​, por ejemplo:

​ ​ 1
a = ​ 5
b =​ ​ 2 ​ 7
c = ​ ​ 2 ​ 09

print​(​'Decimal\tHexadecimal'​)
print​(​'​{}​\t​{}​'​.format(a, ​hex​(a)))
print​(​'​{}​\t​{}​'​.format(b, ​hex​(b)))
print​(​'​{}​\t​{}​'​.format(c, ​hex​(c)))

python3 funcion_integrada.py
Decimal Hexadecimal
15 0xf
27 0x1b
209 0xd1

En este ejemplo el número hexadecimal se muestra siempre con los dos


carácteres ​0x​ por la izquierda para identificarlo como tal, y a continuación los
carácteres que van del ​0​ al ​9​ y letras que van desde la ​a​ hasta la ​f​,
representando al número decimal original.

Si quisiéramos hacerlo al revés, es decir, convertir un número binario o


hexadecimal a un número entero en base decimal, podríamos hacerlo con la
función i​ nt()​ del siguiente modo:

binario ​=​ ​'0b10'


hexadecimal ​=​ ​'0xd1'

print​(​'Bin\tDec'​)
print​(​'​{}​\t​{}​'​.format(binario,
int​(binario, ​2​)))
print​(​'-----------'​)
print​(​'Hex\tDec'​)
print​(​'​{}​\t​{}​'​.format(hexadecimal, ​int​(hexadecimal, ​16​)))

python3 funcion_integrada.py
Bin Dec
0b10 2
-----------
Hex Dec
0xd1 209

La función integrada ​abs()​ devuelve el valor absoluto de un número, es decir,


le quita el signo a los números, por ejemplo:

numero ​=​ ​-​17

74
print​(​abs​(numero))

python3 funcion_integrada.py
17

La función ​round()​ redondea un número decimal, al alza o a la baja,


dependiendo del valor que tenga el número float, por ejemplo:

a ​=​ ​5.5
b ​=​ ​5.4
c ​=​ ​7.1
print​(​round​(a))
print​(​round​(b))
print​(​round​(c))

python3 funcion_integrada.py
6
5
7

Una función integrada que ya hemos utilizado anteriormente es ​len()​, que


devuelve la longitud de un valor de tipo ​string​ o las diferentes colecciones
como listas, tuplas, conjuntos o diccionarios.

a ​=​ ​'Hola mundo!'


b ​=​ [​0​, ​1​, ​2​]
c ​=​ (​'a'​, ​'b'​, ​'c'​, ​'d'​)
d ​=​ {​'nombre'​: ​'Frank'​, ​'apellido'​: ​'Zappa'​}

print​(​len​(a))
print​(​len​(b))
print​(​len​(c))
print​(​len​(d))

python3 funcion_integrada.py
11
3
4
2

Hemos visto algunas de las funciones integradas más conocidas o más


utilizadas, pero existen muchas más, como ​help()​ para invocar la ayuda de
Python, o e​ xit()​ para realizar una salida forzada del programa. Toda la
información acerca de las funciones integradas en Python se puede encontrar
en internet.

75
Manejo de excepciones
En programación muchas veces podríamos tener un error de retorno a la hora
de evaluar una expresión o de invocar a un método, función, procedimiento o
clase, tanto de nuestro propio código como integrado en el lenguaje, tal y
como ya hemos visto en el punto anterior. Para poder tener un control más
exhaustivo sobre el comportamiento de nuestro programa es muy importante
hacer uso de las excepciones que el lenguaje nos permite, de ese modo
podremos indicarle al programa qué hacer cuando algo no suceda como se
espera. En este punto veremos cómo identificar los errores y cómo
gestionarlos mediante excepciones.

Errores

Cuando ejecutamos un programa podemos encontrarnos errores de diferentes


tipos, por ejemplo errores sintácticos, que son aquellos que tienen que ver con
la propia escritura del código, tales como un bloque de código mal identado,
un paréntesis sin cerrar, un carácter de más, la ausencia de un argumento
cuando debería ser obligatorio, etc.

En el siguiente ejemplo vamos a provocar algún error de sintaxis para poder


verlos en detalle. Para ello crearemos un nuevo archivo
llamado ​excepciones.py​ con el siguiente código:

print​(​'Hola mundo!'

python3 excepciones.py
File ​"excepciones.py"​, line 2
^
SyntaxError: unexpected EOF ​while​ parsing

En este caso hemos eliminado el cierre de paréntesis a la hora de usar la


función integrada ​print()​, lo que ha provocado el correspondiente error de
sintaxis. La mayoría de los entornos de desarrollo nos suelen avisar de los
errores de sintaxis como el que acabamos de ver, suele aparecer el error
subrayado e incluso aparece un mensaje emergente que indica en qué nos
hemos equivocado.

Sin embargo, no todos los errores en el código son tan fáciles de identificar,
como pasa con los errores semánticos. Estos errores son los que están
ligados al uso de los recursos que tenemos disponibles para utilizar en nuestro
código. Por ejemplo, algo que para nosotros debería ser correcto en la

76
mayoría de situaciones, pero que bajo una determinada circunstancia podría
no funcionar como se espera. Estos son los errores más difíciles de identificar.

Veamos un ejemplo en el que tenemos una lista de varios elementos,


invocamos varias veces al método ​pop()​ para ir eliminando de la lista uno a
uno todos los elementos, y finalmente veremos qué error nos devuelve el
programa cuando queremos sacar elementos de una lista que ya está vacía.
Dicho de otra manera, vamos a sacar 4 elementos de una lista que solo
contiene 3 elementos:

lista ​=​ [​1​, ​2​, ​3​] ​# Lista con 3 elementos

lista.pop() ​# Sacamos el primer elemento


lista.pop() ​ # Sacamos el segundo elemento
lista.pop() ​ # Sacamos el tercer elemento
lista.pop() ​ # Sacamos un cuarto elemento que no existe

python3 excepciones.py
Traceback (most recent call last):
File ​"excepciones.py"​, line 6, ​in​ ​<​module​>
​lista.pop​() ​# Sacamos un cuarto elemento que no existe
IndexError: pop from empty list

Nos ha dado un error semántico, es decir, no hemos ejecutado nada que


estuviera mal escrito o incompleto, solo hemos usado una instrucción correcta
en una situación en la que ya no se debería usar, ya que no hay más
elementos en la lista. ¿Cómo podemos controlar este tipo de errores para que
no falle el programa?

Podríamos hacerlo de diferentes maneras, pero una buena idea sería


comprobar la longitud de la lista (número de elementos) antes de sacar un
elemento, de modo que si la longitud es ​0​ no sería necesario sacar más
elementos. Veamos cómo en el siguiente ejemplo:

lista ​=​ [​1​, ​2​, ​3​]

print​(lista)

if​ ​len​(lista) ​>​ ​0​: ​# True


lista.pop() ​# Saca elemento de lista
print​(lista) ​# Se imprime valor de lista actualizado

if​ ​len​(lista) ​>​ ​0​: ​# True


lista.pop() ​# Saca elemento de lista
print​(lista) ​# Se imprime valor de lista actualizado

if​ ​len​(lista) ​>​ ​0​: ​# True


lista.pop() ​# Saca elemento de lista
print​(lista) ​# Se imprime valor de lista actualizado

77
if​ ​len​(lista) ​>​ ​0​: ​# False
lista.pop() ​# Esta línea no se ejecutará
print​(lista) ​# No se imprimirá valor de lista

python3 excepciones.py
[1, 2, 3]
[1, 2]
[1]
[]

En este ejemplo aparece una parte del código que se repite varias veces, para
optimizarlo lo modificaremos para usar una función recursiva que ejecute una
acción tantas veces como sea necesario hasta que se cumpla la condición de
que la lista tenga una longitud igual a 0​ ​:

lista ​=​ [​1​, ​2​, ​3​]

def​ ​comprueba_lista​(l):
print​(l)
if​ ​len​(l) ​>​ ​0​:
l.pop()
comprueba_lista(l)

comprueba_lista(lista)

python3 excepciones.py
[1, 2, 3]
[1, 2]
[1]
[]

Al ejecutar el programa obtendremos el mismo resultado, pero habremos


optimizado mucho la ejecución mediante el uso de una función recursiva.

Otro erro bastante común es cuando leemos un valor por teclado e intentamos
realizar una operación matemática con ese valor. Todos los valores que se
introducen por teclado son de tipo ​string​ a no ser que hagamos un ​casting​ y lo
convirtamos en otro tipo diferente. A continuación un ejemplo en el que se
reproduce el error que devuelve el programa al no convertir los datos
introducidos por teclado a un número entero (​int​) o de coma flotante (​float​):

​ ​ i
a = ​ nput​(​'Introduce un número: '​)
b =​ ​ 3​

print​(​'​{}​/​{}​ = ​{}​'​.format(a, b, a​/b


​ ))

python3 excepciones.py
Introduce un número: 15

78
Traceback (most recent call last):
File ​"excepciones.py"​, line 5, ​in​ ​<​module​>
print(​'{}/{} = {}'​.format(a, b, a/b))
TypeError: unsupported operand type(s) ​for​ /: ​'str'​ and ​'int'

Como se ha comentado antes, para solventar este error bastaría con hacer
un ​casting​ del ​input​, en este caso a número de coma flotante o ​float​ de la
siguiente manera:

​ ​ f
a = ​ loat​(​input​(​'Introduce un número: '​))
b =​ ​ 3​ ​ ​print​(​'​{}​/​{}​ = ​{}​'​.format(a, b, a​/​b))

python3 excepciones.py
Introduce un número: 15
15.0/3 = 5.0

Ahora ya no devuelve un error, hace el cálculo correctamente. Pero, ¿y si el


usuario introduce unas letras en vez de un número? Obtendremos el siguiente
error:
python3 excepciones.py

Introduce un número: abc


Traceback (most recent call last):
File ​"excepciones.py"​, line 1, ​in​ ​<​module​>
a = float(input(​'Introduce un número: '​))
ValueError: could not convert string to float: ​'abc'

Esto sucede porque no se puede convertir una cadena de texto a un


número ​float​. ¿Cómo entonces podremos asegurarnos que el usuario va a
introducir un número y cómo se puede controlar el programa en caso de que
este no lo haga correctamente? Para ello existen las excepciones, las cuales
trataremos en el siguiente punto.

Excepciones

Una excepción es un bloque de código excepcional que nos permitirá


continuar con la ejecución del programa aunque ocurra un error. En el punto
anterior hemos visto un programa que pide introducir un número, pero no
estábamos controlando el caso de error en el que el usuario introduzca por
teclado algo diferente de un número. Vamos a crear una excepción para este
caso. Para ello debemos colocar todo el código propenso a errores dentro del
cuerpo de una sentencia llamada t​ ry​. A continuación se creará otro bloque
llamado e​ xcept​ y este contendrá en su cuerpo aquel código que queremos que
se ejecute cuando exista algún error en el código que hay dentro de t​ ry​,
veamos el código:

try​:

79
​ ​ f
a = ​ loat​(​input​(​'Introduce un número: '​))
b =​ ​ 3​

print​(​'​{}​/​{}​ = ​{}​'.
​ format(a, b, a​/​b))
except​:
print​(​'Ha ocurrido un error, introduce bien el número.'​)

python3 excepciones.py
Introduce un número: abc
Ha ocurrido un error, introduce bien el número.

Como se puede ver, el programa intenta capturar un número por teclado, pero
si el usuario no introduce un número el programa no falla, simplemente ejecuta
el código que hay dentro de e​ xcept​, en este caso solo un p​ rint() con un
mensaje personalizado de error. Solo hay una pega, tal y como está hecho el
programa ahora mismo, en caso de error solo se imprime un mensaje y el
programa finaliza. Podría interesarnos que además de mostrar ese mensaje
que volviera a pedir introducir un número de forma indefinida hasta que el
usuario introduzca un valor correctamente. Esto lo podemos conseguir
introduciendo el código actual dentro del cuerpo de un bucle while, por
ejemplo:

while​ ​True​:
try​:
​ ​ f
a = ​ loat​(i
​ nput​(​'Introduce un número: '​))
b =​ ​ 3​

​ ​{}​/{
print​(' ​ }​ = ​{}​'.
​ format(a, b, a​/​b))
break​ ​# Si todo ha ido bien se interrumpe el bucle.
except​:
​ Ha ocurrido un error, introduce bien el número.'​)
print​('

python3 excepciones.py
Introduce un número: abc
Ha ocurrido un error, introduce bien el número.
Introduce un número: wgr
Ha ocurrido un error, introduce bien el número. Introduce un número: 27
27.0/3 = 9.0

Tras dos intentos fallidos hemos introducido un número correctamente y el


programa ejecuta el código que hay en el cuerpo del bloque ​try​ finalizando así
las iteraciones del bucle y saliendo del programa con éxito.

De este modo ya estaríamos haciendo uso de las excepciones de Python, pero


las excepciones abarcan mucho más que esto. En primer lugar permite añadir
un bloque de código e​ lse​ para ejecutar un código en caso de que todo vaya
correctamente, y ese es el lugar idóneo para poner la sentencia b​ reak​ que
hemos añadido antes dentro del bloque t​ ry​, veamos cómo mejorarlo:

80
while​ ​True​:
try​:
​ ​ f
a = ​ loat​(i
​ nput​(​'Introduce un número: '​))
b =​ ​ 3​

​ ​{}​/{
print​(' ​ }​ = ​{}​'.
​ format(a, b, a​/​b))
except​:
​ Ha ocurrido un error, introduce bien el número.'​)
print​('
else​:
​ Todo ha ido bien, saliendo del programa.'​)
print​('
break​ ​# Si todo ha ido bien se interrumpe el bucle.

python3 excepciones.py
Introduce un número: wadf
Ha ocurrido un error, introduce bien el número.
Introduce un número: drgb
Ha ocurrido un error, introduce bien el número.
Introduce un número: 39 39.0/3 = 13.0
Todo ha ido bien, saliendo del programa.

Múltiples excepciones

Python permite crear múltiples excepciones mediante identificadores. Pero


para poder usar el identificador correcto primero debemos saber qué
identificadores tenemos que usar en cada caso. Podríamos saberlo de
antemano, pero es interesante ver cómo obtener el nombre del identificador
para cada caso. Por ejemplo, vamos a reproducir el primer error que hemos
visto en este capítulo, en el que no hacíamos ​casting​ del valor introducido por
teclado y daba un error al no poder usar una cadena de caracteres para
realizar un cálculo como la división. Para obtener el identificador debemos
escribir el siguiente código:

try​:
​ ​ i
a = ​ nput​(​'Introduce un número: '​)
b =​ ​ 3​

print​(​'​{}​/​{}​ = ​{}​'.
​ format(a, b, a​/​b))
except​ ​Exception​ ​as​ e:
print​(​type​(e).​__name__​)

python3 excepciones.py
Introduce un número: 15
TypeError

En este caso obtenemos el identificador ​TypeError​ para este tipo de


excepciones. Una vez que ya sabemos el identificador podemos hacer una
excepción exclusiva con un mensaje personalizado para cuando nuestro
programa dé un error de este tipo, por ejemplo:

81
try​:
​ ​ i
a = ​ nput​(​'Introduce un número: '​)
b =​ ​ 3​

print​(​'​{}​/​{}​ = ​{}​'.
​ format(a, b, a​/​b))
except​ ​TypeError​:
print​(​'No se puede dividir una cadena de texto entre un número.'​)
except​ ​Exception​ ​as​ e:
print​(​type​(e).​__name__​)

python3 excepciones.py
Introduce un número: 15
No se puede dividir una cadena de texto entre un número.

Solventaremos el error del mismo modo que lo hicimos anteriormente,


introduciendo un ​casting​ para convertir el valor introducido por teclado a un
dato de tipo f​ loat​, pero volveremos a ejecutar el programa introduciendo por
teclado una cadena de caracteres, así averiguaremos el identificador de este
nuevo tipo de error:

try​:
​ ​ f
a = ​ loat​(​input​(​'Introduce un número: '​))
b =​ ​ 3​

print​(​'​{}​/​{}​ = ​{}​'.
​ format(a, b, a​/​b))
except​ ​TypeError​:
print​(​'No se puede dividir una cadena de texto entre un número.'​)
except​ ​Exception​ ​as​ e:
print​(​type​(e).​__name__​)

python3 excepciones.py
Introduce un número: abc
ValueError

Este nuevo error que da cuando intentas convertir a ​float​ una cadena de
texto, tiene un identificador llamado ​ValueError​, así que ya podemos hacer otra
excepción personalizada para cuando nuestro programa tenga este tipo de
errores, por ejemplo:

try​:
​ ​ f
a = ​ loat​(​input​(​'Introduce un número: '​))
b =​ ​ 3​

print​(​'​{}​/​{}​ = ​{}​'.
​ format(a, b, a​/​b))
except​ ​TypeError​:
print​(​'No se puede dividir una cadena de texto entre un número.'​)
except​ ​ValueError​:
print​(​'Debes introducir un número.'​)
except​ ​Exception​ ​as​ e:

82
print​(​type​(e).​__name__​)

python3 excepciones.py
Introduce un número: abc
Debes introducir un número.

Ahora nuestro programa controla dos tipos de errores mediante los


identificadores ​TypeError​ y ​ValueError​, y además tenemos una tercera
excepción que nos devuelve el nombre del identificador en caso de que se
trate de un error de tipo distinto a los dos anteriores. Veamos qué pasa si
ejecutamos nuestro programa indicando el número 0​ ​ por teclado y el número
introducido es el divisor:

try​:
​ ​ f
a = ​ loat​(​input​(​'Introduce un número: '​))
b =​ ​ 3​

print​(​'​{}​/​{}​ = ​{}​'.
​ format(b, a, b​/​a)) ​# Orden cambiado.
except​ ​TypeError​:
print​(​'No se puede dividir una cadena de texto entre un número.'​)
except​ ​ValueError​:
print​(​'Debes introducir un número.'​)
except​ ​Exception​ ​as​ e:
print​(​type​(e).​__name__​)

python3 excepciones.py
Introduce un número: 0
ZeroDivisionError

Obtenemos un nuevo identificador para las excepciones en las que se trata de


dividir un número por cero, es algo que no está permitido, pues la división por
cero es infinita. Hagamos una excepción personalizada para este tipo de
errores:
try​:
​ ​ f
a = ​ loat​(​input​(​'Introduce un número: '​))
b =​ ​ 3​

print​(​'​{}​/​{}​ = ​{}​'.
​ format(b, a, b​/​a))
except​ ​TypeError​:
print​(​'No se puede dividir una cadena de texto entre un número.'​)
except​ ​ValueError​:
print​(​'Debes introducir un número.'​)
except​ ​ZeroDivisionError​:
print​(​'No se puede dividir por cero.'​)
except​ ​Exception​ ​as​ e:
print​(​type​(e).​__name__​)

python3 excepciones.py
Introduce un número: 0

83
No se puede dividir por cero.

Invocación de excepciones

Sin duda las excepciones nos ayudan a optimizar nuestros programas y tener
un control más exhaustivo sobre los errores. En este punto veremos cómo se
puede llamar o invocar a una excepción más allá de las maneras que ya
hemos visto.

Vamos a definir una función que reciba un valor, pero si este valor es un valor
especial, por ejemplo un valor nulo, llamaremos a una excepción de
tipo ​ValueError​ que ya vimos anteriormente mediante una instrucción
llamada r​ aise​ seguida del identificador o tipo de error:

def​ ​mi_funcion​(algo​=​None​):
if​ algo ​is​ ​None​:
raise​ V​ alueError​(​'Error, no se permite un valor nulo.'​)

mi_funcion()

python3 excepciones.py
Traceback (most recent call last):
File ​"excepciones.py"​, line 5, ​in​ ​<​module​>
​mi_funcion​()
File ​"excepciones.py"​, line 3, ​in​ mi_funcion
raise ValueError(​'Error, no se permite un valor nulo.'​)
ValueError: Error, no se permite un valor nulo.

Este es la manera en la que se puede invocar a una excepción directamente,


haciendo uso de la instrucción ​raise​, pero lo más común es que el
programador personalice cada posible error utilizando las
instrucciones t​ ry​ y e​ xception​, normalmente no se suelen invocar excepciones
de esta manera.

Clases y objetos
Con todo lo que conocemos hasta ahora ya podemos crear bastantes
programas. Sin embargo, no dejan de ser instrucciones estructuradas. Esto
significa que cuando queremos solucionar un problema tenemos que pensar
de arriba a abajo, y lo único que nos da un poco de juego son las funciones,
las listas y los diccionarios. Así era la programación en el pasado, bastante
aburrida, con mucho código, mucha gestión de recursos y muy difícil de

84
mantener. Hasta que poco a poco fue tomando forma un paradigma de
programación conocido como Programación Orientada a Objetos (POO).

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


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

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


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

Programación estructurada Vs. POO


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

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

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


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

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

85
print​(clientes)

python3 clientes.py
[{​'nombre'​: ​'Richard'​, '​ apellidos'​: ​'Stallman'​, ​'dni'​: ​'01234567A'​},
{​'nombre'​: ​'Aaron'​, ​'apellidos'​: ​'Swartz'​, ​'dni'​: ​'98765432Z'​}]

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


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

def​ ​mostrar_cliente​(clientes, dni):


for​ c ​in​ clientes:
if​ dni ​==​ c[​'dni'​]:
print​('​ ​{}​ {
​ }​'​.format(c[​'nombre'​], c[​'apellidos'​]))
return ​# Si se encuentra el cliente se sale de la
iteración.

print​(​'Clente no encontrado.'​)

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


programa:
mostrar_cliente(clientes, ​'01234567A'​)

python3 clientes.py
Richard Stallman

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

mostrar_cliente(clientes, ​'00000000B'​)

python3 clientes.py
Clente no encontrado.

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

86
llamada ​borrar_cliente​ que reciba también dos argumentos, la lista de clientes
y el DNI del cliente que se quiere borrar, por ejemplo:

def​ ​borrar_cliente​(clientes, dni):


for​ i,c ​in​ ​enumerate​(clientes):
if​ dni ​==​ c[​'dni'​]:
del​ clientes[i]
​ tr​(c), '
print​(s ​ --> Borrado'​)
return ​# Si se encuentra el cliente se sale de la
iteración.

print​(​'Cliente no encontrado.'​)

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


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

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


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

print​(clientes)

borrar_cliente(clientes, ​'01234567A'​)

print​(clientes)

Al ejecutar el programa tendremos el siguiente resultado:

python3 clientes.py
[{​'nombre'​: ​'Richard'​, '​ apellidos'​: ​'Stallman'​, ​'dni'​: ​'01234567A'​},
{​'nombre'​: ​'Aaron'​, ​'apellidos'​: ​'Swartz'​, ​'dni'​: ​'98765432Z'​}]
{​'nombre'​: ​'Richard'​, ​'apellidos'​: '​ Stallman'​, ​'dni'​: ​'01234567A'​}--​>​Borrado
[{​'nombre'​: ​'Aaron'​, ​'apellidos'​: ​'Swartz'​, ​'dni'​: ​'98765432Z'​}]

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


clientes de una lista. Pero podríamos crear más funcionalidades como la de
editar los datos de un cliente o añadir más campos a los diccionarios. Con
esto nos basta para ver un ejemplo de cómo sería un programa de gestión de
clientes hecho con programación tradicional y estructurada, y funciona bien,
atiende a las necesidades del programa tal y como nos lo han pedido. Lo malo

87
de este tipo de programación es que a la larga se hace muy confuso porque se
generaría mucho código y esto es difícil de mantener. Además, como hemos
visto, tendríamos que estar moviendo constantemente la lista de clientes de un
lado a otro para poder manejarla y ejecutar acciones sobre ella, esto no es
muy práctico.

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


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

class​ ​Cliente​:

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


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

def​ ​__str__​(self):
return​ ​'​{}​ ​{}​'.
​ format(​self​.nombre, ​self​.apellidos)

class​ ​Empresa​:

def​ ​__init__​(self, clientes​=​[]):


self​.clientes ​=​ clientes

def​ ​mostrar_cliente​(self, dni​=​None​):


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

def​ ​borrar_cliente​(self, dni​=​None​):


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

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


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

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


palabra reservada ​class​ seguida del nombre que tendrá la clase. Los nombres
de las clases se suelen escribir con la primera letra en mayúscula. en este caso
hemos creado dos clases, una C​ liente​ y otra E​ mpresa​. Ambas clases tienen en
su cuerpo una función interna llamada _​ _init__()​. Se trata de una función

88
especial, es el constructor de la clase, es decir, donde se va a otorgar valores
a los atributos de la clase. En el caso de la clase ​Cliente​ existen tres atributos,
que son el ​dni​, ​nombre​ y ​apellidos​. Para indicar que son atributos de la clase
deben llevar el prefijo s​ elf.​. En el caso de la clase ​Empresa​ solo hay un
atributo c​ lientes​. Si este no se especifica tendrá como valor por defecto una
lista vacía.
En lugar de trabajar con funciones, como era el caso de la programación
estructurada, aquí cambia la sintaxis, cada clase tendrá sus propios métodos.
Lo primero que tenemos que hacer a continuación del código anterior es crear
un cliente en memoria, esto se realiza de la siguiente manera:

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


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

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

beatriz ​=​ Cliente(​'11111111B'​, '


​ Beatriz'​, ​'Gimenez'​)

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

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

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

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

print​(empresa.clientes)

Si guardamos el contenido del programa y lo ejecutamos tendremos la


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

python3 clientes_poo.py
[​<​__main__.Cliente object at 0x7fad555e​9940>​,
<​__main__.Cliente object at 0x7fad555e​9978>​]

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


decir, una lista de dos elementos de tipo ​object​, son objetos de una clase, en

89
este caso concreto de la clase ​Cliente​. Pero de esta manera no podremos ver
más que la dirección de memoria hexadecimal en la que fueron alojados
sendos objetos, ¿cómo podríamos acceder a su atributos? Como por ejemplo
el nombre, apellidos o el dni. La clase ​Empresa​ tiene un método
llamado m​ ostrar_cliente​ que recibe por parámetro el ​dni​ de un cliente.

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

empresa.mostrar_cliente(​dni​=​'00000000A'​)

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


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

python3 clientes_poo.py
[​<​__main__.Cliente object at 0x7f3b32aaf9b​0>​,
<​__main__.Cliente object at 0x7f3b32aaf9e​8>​]
Javier Dominguez

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


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

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

python3 clientes_poo.py
[​<​__main__.Cliente object at 0x7f3b32aaf9b​0>​,
<​__main__.Cliente object at 0x7f3b32aaf9e​8>​]
Javier Dominguez
Beatriz Gimenez --​>​ Borrado
[​<​__main__.Cliente object at 0x7f3b32aaf9b​0>​]

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

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

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

class​ ​Galleta​:
pass

A continuación vamos a escribir estas dos líneas para crear dos objetos de
esta clase:
​ ​ Galleta()
galleta_1 =
galleta_2 =​ ​ Galleta()

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

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


que debemos pasarle un objeto como argumento, por ejemplo:

print​(​type​(galleta_1))

python3 clases_y_objetos.py
<​class ​'__main__.Galleta'​>

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


a la información que nos dice de qué tipo es este objeto, y en este caos nos
indica que se trata de un objeto de la clase ​Galleta​. Pero podríamos usar el
método t​ ype()​ con otro tipo de objetos que ya conocemos, y siempre nos
devolverá el tipo de dato u objeto que se trata, veamos varios ejemplos:

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

def​ ​hola​():
pass

print​(​type​(hola))

python3 clases_y_objetos.py
<​class ​'__main__.Galleta'​>
<​class ​'int'​>
<​class ​'float'​>
<​class ​'str'​>
<​class ​'list'​>
<​class ​'dict'​>
<​class ​'bool'​>
<​class ​'function'​>

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


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

Atributos y métodos de clase

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

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


tener sus propios atributos, una especie de variables internas a las que nos
podremos referir con un puntito después del nombre del objeto y a
continuación el nombre del atributo. Estos atributos se pueden definir fuera de
la clase y se crearán automáticamente solo para esa instancia. Para poder

92
verlo vamos a crear un nuevo archivo llamado ​galletas.py​ con el siguiente
código:

class​ ​Galleta​:
pass

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

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

python3 galletas.py
Esta galleta es de color amarillo y sabe salada

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

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

​ ​ Galleta()
g1 =
g2 =​ ​ Galleta()

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


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

python3 galletas.py
La galleta 1 es de color amarillo y sabe salada
La galleta 2 es de color amarillo y sabe salada

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

Para que cada objeto tenga sus propios valores de atributos lo mejor es
definirlos en el momento que se crea el objeto, para ello es necesario conocer
dos conceptos nuevos de las clases, uno es el método especial
llamado _​ _init__()​, y el otro es la palabra reservada ​self​. Vamos a desarrollar
de nuevo la clase introduciendo estos dos nuevos conceptos:

93
class​ ​Galleta​:

def​ ​__init__​(self, sabor, color):


self​.sabor ​=​ sabor
self​.color ​=​ color

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


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

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

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

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


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

Al ejecutar el programa obtendremos la siguiente salida:


python galletas.py
La galleta 1 es de color amarilla y sabe salada
La galleta 2 es de color verde y sabe dulce

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


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

class​ ​Galleta​:

def​ ​__init__​(self, sabor, color):


self​.sabor ​=​ sabor
self​.color ​=​ color
self​.chocolate ​=​ ​False

def​ ​chocolatear​(self):
self​.chocolate ​=​ ​True

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

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


un valor por defecto ​False​. Si en algún momento del programa se invoca al
método c​ hocolatear()​ el valor del atributo c​ hocolate​ se cambiará a T​ rue​, si no
permanecerá el valor por defecto.

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


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

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

print​(​'\nEstado actual de las galletas'​)


g1.tiene_chocolate()
g2.tiene_chocolate()

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


g1.chocolatear()

print​(​'\nEstado actual de las galletas'​)


g1.tiene_chocolate()
g2.tiene_chocolate()

python galletas.py

Estado actual de las galletas


No tiene chocolate
No tiene chocolate

Añado chocolate a la primera galleta ...

Estado actual de las galletas


Si tiene chocolate
No tiene chocolate

Como se puede comprobar, ahora tenemos clases, objetos, atributos y


métodos de clase que nos sirven para poder tener un control sobre el estado
de las cosas de una manera mucho más sencilla.

95
Métodos especiales

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


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

class​ ​Pelicula​:

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

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


valores que espera recibir el constructor para sus atributos:

p ​=​ Pelicula(​'El padrino'​, 1


​ 75​, ​1972​)

Si ejecutamos el programa obtendremos la siguiente salida:

python peliculas.py
Se ha creado la película El padrino

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


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

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

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


objeto mediante el método integrado ​del()​:

p ​=​ Pelicula(​'El padrino'​, 1


​ 75​, ​1972​)

del​(p)

Al ejecutar el programa obtendremos la siguiente salida por pantalla.


python peliculas.py
Se ha creado la película El padrino
Se está borrando la película El padrino

96
Otro método especial es el método llamado ​__str__()​ que servirá para aquellas
ocasiones en las que se pasa el nombre del objeto por la función
integrada s​ tr()​ de Python, en cuyo caso ya no se mostrará una cadena con la
dirección de memoria en la que está almacenado el objeto, si no un mensaje
personalizado. Por ejemplo:
# Redefinimos el método string
def​ ​__str__​(self):
return ​'​{} lanzada en ​{} con una duración de ​{}
minutos'​.format(​self​.titulo, ​self​.lanzamiento, s
​ elf​.duracion)

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


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

p ​=​ Pelicula(​'El padrino'​, 1


​ 75​, ​1972​)

print​(​str​(p))

Al ejecutar el programa obtendremos la siguiente salida por pantalla.

python peliculas.py
Se ha creado la película El padrino
El padrino lanzada en 1972 con una duración de 175 minutos
Se está borrando la película El padrino

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


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

Objetos dentro de objetos

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

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

# Redefinimos el método string


def​ ​__str__​(self):
return​ ​'​{}​ (​{}​)'​.format(​self​.titulo, ​self​.lanzamiento)

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

class​ ​Catalogo​:
peliculas ​=​ []

def​ ​__init__​(self, peliculas​=​[]):


self​.peliculas ​=​ peliculas

def​ ​agregar​(self, p):


self​.peliculas.append(p)

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

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


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

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


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

c ​=​ Catalogo([p1])

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


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

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

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


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

c.mostrar()

python peliculas.py
Se ha creado la película El padrino
Se ha creado la película El padrino II
El padrino (1972)
El padrino II (1974)

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


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

Encapsulación de atributos

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

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


programación para impedir acceso externo a atributos y métodos. Pero Python
no ofrece esta funcionalidad de base, aún así, se puede simular un
comportamiento parecido precediendo el nombre de estos atributos o
métodos con un guión bajos (_). Para poder ver algún ejemplo vamos a crear
un nuevo archivo llamado e​ ncapsulacion.py​ en el que vamos a crear una
clase E​ jemplo​ con un atributo privado llamado _​ atributo_privado​ y un método
privado llamado _​ metodo_privado()​:

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

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

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

print​(e._atributo_privado)

99
Si ejecutamos el programa ahora obtendremos el siguiente error:

python3 encapsulacion.py
Traceback (most recent call last):
File ​"encapsulacion.py"​, line 9, i
​ n​ ​<​module​>
print(e._atributo_privado)
AttributeError: ​'Ejemplo'​ object has no attribute ' ​ _atributo_privado'

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


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

e._metodo_privado()

El ejecutarlo obtenemos este otro error:

encapsulacion.py
Traceback (most recent call last):
File ​"01_introduccion/01_10_src/encapsulacion.py"​, line 9, ​in​ ​<​module​>
​e._metodo_privado​()
AttributeError: ​'Ejemplo'​ object has no attribute '
​ _metodo_privado'

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


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

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

def​ ​mostrar_atributo​(self):
return​ ​self​._atributo_privado

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


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

print​(e.mostrar_atributo())

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


correctamente:

python3 encapsulacion.py
Soy un atributo inalcanzable desde fuera.

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

def​ ​mostrar_metodo​(self):
return​ ​self​._metodo_privado()

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

e.mostrar_metodo()

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


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

python3 encapsulacion.py
Soy un método inalcanzable desde fuera.

Herencia

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


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

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

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

101
Este sería un buen comienzo para crear productos, de momento una clase
llamada ​Producto​ que solo tiene un método constructor ​__init__()​ en el que se
definirá el valor de unos atributos obligatorios,
como r​ eferencia​, t​ ipo​, n​ ombre​, p​ vp​ y ​descripcion​, y luego otros atributos
opcionales, como p​ roductor​, d​ istribuidor​ y a​ utor​.

Los atributos obligatorios son aquellos que tendremos que especificar siempre
que definamos una instancia u objetos de la clase, en este caso porque son
todos los atributos que todos los productos tendrán en común. Sin embargo
los opcionales podremos especificarlos o no, dependerá del tipo de producto
que queremos instanciar. En los casos en los que no especifiquemos estos
atributos opcionales se le asignará un valor por defecto N​ one​.
Vamos a crear un producto instanciando un objeto de esta clase, por ejemplo:
adorno ​= Producto(​'000A'​, ​'Adorno'​, ​'Jarrón'​, ​15​, ​'Jarrón de porcelana con
dibujos'​)

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


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

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

python3 producto.py
Adorno
Jarrón de porcelana con dibujos

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

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

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

102
def​ ​__str__​(self):
return​ ​"""​\
REFERENCIA\t​{}
NOMBRE\t\t​{}
PVP\t\t​{}
DESCRIPCIÓN\t​{}​"""​.format(​self​.referencia, ​self​.nombre,
self​.pvp, ​self​.descripcion)

Luego tendríamos que hacer tres Subclases, por


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

class​ ​Adorno​(​Producto​):
pass

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


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

ad ​=​ Adorno(​'00000'​, ​'Jarrón'​, 1


​ 5.50​, ​'Jarrón de porcelana con dibujos'​)
print​(ad)

python3 producto.py
REFERENCIA 00000
NOMBRE Jarrón
PVP 15.5
DESCRIPCIÓN Jarrón de porcelana con dibujos

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


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

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

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


obligatorios:

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


virgen extra'​)

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

al.productor ​=​ ​'La aceitera'


al.distribuidor ​=​ ​'Distribuciones S.A.'

print​(al)

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

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

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

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


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

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

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


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

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

104
def​ ​__str__​(self):
return​ ​"""​\
REFERENCIA\t​{}
NOMBRE\t\t​{}
PVP\t\t​{}
DESCRIPCIÓN\t​{}
PRODUCTOR\t​{}
AUTOR\t​{}​"""​.format(​self​.referencia, ​self​.nombre, ​self​.pvp,
self​.descripcion, ​self​.isbn, ​self​.autor)

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


sus dos atributos propios e imprimimos el objeto:

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


de hiper vigilancia'​)
li.isbn ​=​ ​'8417636390'
li.autor ​=​ ​'Marta Peirano'

python3 producto.py
REFERENCIA 00002
NOMBRE El enemigo conoce el sistema
PVP 17.9
DESCRIPCIÓN Libro sobre redes de hiper vigilancia
PRODUCTOR 8417636390
AUTOR Marta Peirano

Clases heredadas

En el punto anterior hemos creado una superclase y tres subclases, de modo


que ahora disponemos de una jerarquía que nos permite poder trabajar con
cierta comodidad en nuestra tienda de productos. Sin embargo, para poder
manejar los productos necesitaremos agruparlos de alguna manera, por
ejemplo en una colección como una lista de productos. Podríamos añadir
todos los productos a una lista sin importar que sean de subclases distintas,
por ejemplo podríamos crear la lista p​ roductos​ y añadir dentro los productos de
tipo A​ limento​ y L​ ibro​ creados anteriormente a​ l​ y l​ i​:

productos ​=​ [al, li]

A esta lista podremos añadirle otros objetos que vayamos creando o que ya
hemos creado, por ejemplo el objeto de tipo ​Adorno​ que creamos al principio:

productos.append(ad)

Si imprimimos la lista obtendremos la siguiente salida por pantalla:

python3 producto.py

105
[​<​__main__.Alimento object at 0x10b341eb​8>​,
<​__main__.Libro object at 0x10b341ef​0>​,
<​__main__.Adorno object at 0x10b341e​80>​]

Ahora podremos recorrer esta lista cómodamente con un bucle ​for​, por
ejemplo:

for​ p ​in​ productos:


print​(p, ​'\n'​)

Se ha añadido un salto de línea (​\n​) para tener un espacio entre productos y


que se pueda visualizar un poco mejor:

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

REFERENCIA 00002
NOMBRE El enemigo conoce el sistema
PVP 17.9
DESCRIPCIÓN Libro sobre redes de hiper vigilancia
PRODUCTOR 8417636390
DISTRIBUIDOR Marta Peirano

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

Podemos modificar la manera de imprimir los datos en el bucle ​for​ de la


siguiente manera para que solo imprima algunos atributos comunes de los
productos:

for​ p ​in​ productos:


print​(p.referencia, p.nombre)

python3 producto.py
00001 Aceite de Oliva
00002 El enemigo conoce el sistema
00000 Jarrón

Lo malo de hacerlo de esta manera es que solo podríamos acceder a los


atributos comunes de todos los productos, pero por poner un ejemplo no
podríamos acceder al atributo ​actor​ de algunos de ellos.

106
Para gestionar objetos de distintas clases con diferentes atributos lo mejor
será utilizar algunos condicionales en cada iteración y una la función
integrada ​isinstance()​ para comprobar si un objeto es una instancia de una
clase determinada. Veamos el siguiente ejemplo:

for​ p ​in​ productos:


if​ ​isinstance​(p, Adorno):
print​(p.referencia, p.nombre)
elif​ ​isinstance​(p, Alimento):
print​(p.referencia, p.nombre, p.productor)
elif​ ​isinstance​(p, Libro):
print​(p.referencia, p.nombre, p.isbn)

python3 producto.py
00001 Aceite de Oliva La aceitera
00002 El enemigo conoce el sistema 8417636390
00000 Jarrón

Ahora se recorren todos los productos y dependiendo del tipo de producto


que sea muestra unos atributos u otros sin error.

Una cosa interesante que podemos hacer es crear una función que reciba un
producto y modifique alguno de sus atributos. Por ejemplo, una función que
rebaje un tanto por ciento el precio de los productos. Veamos un ejemplo con
el producto a​ l​ de tipo A​ limento​ que creamos anteriormente. Primero crearemos
una nueva función llamada r​ ebajar_producto()​ con el siguiente código:

def​ ​rebajar_producto​(p, rebaja):


"""Devuelve un producto con una rebaja en porcentaje de su precio."""
p.pvp ​-=​ (p.pvp ​/​ 1
​ 00​ *
​ ​ rebaja)
return​ p

Ahora vamos a crear una variable llamada ​al_rebajado​ a la que vamos a


asignar como valor el resultado de aplicar un 10% de rebaja en el precio del
producto a​ l​, para ello invocamos a la función ​rebajar_producto()​ pasándole
como argumentos el producto y el porcentaje que queremos aplicarle de
rebaja a su precio. Finalmente imprimimos el producto para ver si se ha
aplicado el descuento correctamente:

al_rebajado ​=​ rebajar_producto(al, ​10​)

print​(al_rebajado)

python3 producto.py
REFERENCIA 00001
NOMBRE Aceite de Oliva
PVP 4.5
DESCRIPCIÓN Botella de aceite de oliva virgen extra
PRODUCTOR La aceitera

107
DISTRIBUIDOR Distribuciones S.A.

Aunque pueda parecerlo, en realidad no se ha realizado una copia del


objeto ​al​ bajo el nombre ​al_rebajado​ y se le ha aplicado el descuento a este
último. Si a continuación hacemos un print de ambos veremos que se ha
modificado el precio en los dos:

print​(al_rebajado)
print​(al)

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

Esto sucede porque los objetos, al igual que las colecciones, se pueden pasar
como argumentos a las funciones pero se pasan por referencia, es decir, el
original, no se realiza una copia de su valor. Recordemos el pase por
referencia en las listas:

lista_1 ​=​ [​1​, ​2​, ​3​]


lista_2 ​=​ lista_1
lista_2.append(​4​) ​print​(lista_1)

python3 ejemplo.py
[1, 2, 3, 4]

En una lista para copiar el valor debíamos usar los corchetes con dos puntos
dentro (​[:]​), de ese modo el append se realizaría sobre la copia, y no sobre
una referencia al original.
Con los objetos pasa lo mismo, solo que para hacer una copia de un objeto
tendremos que usar un método perteneciente a un módulo externo
llamado c​ opy()​ que primero deberemos importar para poder utilizarlo. Al
principio del código debemos escribir la siguiente línea:

import​ copy

Este módulo tiene varios métodos, uno de ellos es el método ​copy()​, y lo


usaremos para hacer una copia de un objeto, de modo que cuando lo

108
modifiquemos el objeto original no se vea afectado por el cambio. Veamos un
ejemplo con el objeto ​ad​ de tipo ​Adorno​:

copia_adorno ​=​ copy.copy(ad)

Ahora a ​copia_adorno​ vamos a cambiar el valor del atributo ​pvp​ poniendo un


nuevo precio del producto:

copia_adorno.pvp ​=​ ​16.25

Por último imprimimos el objeto original ​ad​ y su copia ​copia_adorno​:

print​(ad)
print​(copia_adorno)

python3 producto.py
REFERENCIA 00000
NOMBRE Jarrón
PVP 15.5
DESCRIPCIÓN Jarrón de porcelana con dibujos
REFERENCIA 00000
NOMBRE Jarrón
PVP 16.25
DESCRIPCIÓN Jarrón de porcelana con dibujos

Como se puede comprobar se ha realizado la copia correctamente y se ha


modificado el valor del atributo ​pvp​ solo a la copia, el original sigue teniendo su
valor inicial.

Herencia múltiple

En Python la herencia múltiple hace referencia a la posibilidad de que una


subclase puede heredar de varias superclases a la vez, de modo que se
puedan heredar multitud de atributos y métodos de estas superclases. Sin
embargo hay un problema, y este aparece cuando tenemos comportamientos
comunes en las superclases, es decir, cuando varias superclases tienen
métodos y atributos iguales. En estos casos Python dará prioridad a las clases
situadas más a la izquierda en la posición de declaración. Para poder verlo
mejor crearemos un nuevo archivo llamado h​ erencia_multiple.py​ con el
siguiente código de ejemplo.
Primero crearemos una clase A​ ​ que solo tenga el método especial _​ _init__()​ y
este ejecutará un p​ rint()​ con un mensaje diciendo '​ Soy de clase A'​ al
instanciarse:

class​ ​A​:
def​ ​__init__​(self):
​ Soy de clase A'​)
print​('

109
A continuación haremos una clase ​B​ con el mismo código que la clase ​A​ a
diferencia del mensaje, este dirá ​'Soy de clase B'​:

class​ ​B​:
def​ ​__init__​(self):
​ Soy de clase B'​)
print​('

Por último haremos una clase ​C​ que herede de la clase ​A​ y ​B​, en este orden, y
la clase no hará nada, solo un ​pass​ en su cuerpo:

class​ ​C​(​A​, ​B​):


pass

Ahora instanciamos un objeto de la clase ​C​ y ejecutamos el programa para vér


qué sucede:

c ​=​ C()

python3 herencia_multiple.py
Soy de clase A

Ha heredado el mismo método especial ​__init__()​ de las clases ​A​ y ​B​, pero
solo ha mostrado el mensaje de la clase ​A​. Probemos a cambiar el orden de la
herencia múltiple del siguiente modo:

class​ ​C​(​B​, ​A​):


pass

Si ejecutamos el programa de nuevo obtendremos la siguiente salida:

python3 herencia_multiple.py
Soy de clase B

Como ya se ha explicado antes, si hay herencia múltiple y existe un método o


atributo con el mismo nombre en las clases heredadas, siempre tendrá
preferencia la primera declarada por la izquierda.

Métodos de las colecciones


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

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

Para acceder a la consola de Python basta con abrir un terminal y escribir


simplemente ​python3​, por ejemplo:

python3
Python ​3.7​.0 (default, Jun ​28​ 2​ 018​, 0
​ ​7​:​39​:​16​)
Type ​"help"​, ​"copyright"​, ​"credits"​ o ​ r​ ​"license"​ ​for​ more information.
>>>

Tras los tres carácteres ​>>>​ ya podemos empezar a escribir código Python.
Para salir bastaría con escribir el método ​exit()​:

>>>​ ​exit​()

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


colección.

Métodos en cadenas de texto


Todos los caracteres alfabéticos en mayúsculas:
>>>​ ​'Hola Mundo'​.upper()
'HOLA MUNDO'

Todos los caracteres alfabéticos a minúsculas:

>>>​ ​'Hola Mundo'​.lower()


'hola mundo'

Primera letra del texto en mayúscula:


>>>​ ​'hola mundo'​.capitalize()
'Hola mundo'

Primera letra de cada palabra en mayúscula:

>>>​ ​'hola mundo'​.title()


'Hola Mundo'

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


de una cadena:

111
​ Hola mundo'​.count(​'o'​)
>>>​ '
2
>>>​ '​ Hola mundo'​.count(​'mundo'​)
1

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


aparecen:
>>>​ ​'Hola mundo'​.find(​'mundo'​)
5

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


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

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

>>>​ c ​=​ ​'100'


>>>​ c.isdigit()
True

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


alfanuméricos:

>>>​ c ​=​ ​'abcd1234'


>>>​ c.isalnum()
True

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


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

El espacio no en una letra.


>>>​ c ​=​ ​'Hola mundo'
>>>​ c.isalpha()
False

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

>>>​ c ​=​ ​'hola mundo'


>>>​ c.islower()
True

112
Comprobar si todos los carácteres son letras mayúsculas:
>>>​ c ​=​ ​'HOLA MUNDO'
>>>​ c.isupper()
True

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

>>>​ c ​=​ ​'Hola Mundo'


>>>​ c.istitle()
True

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

>>>​ c ​=​ ​' '


>>>​ c.isspace()
True

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


>>>​ c ​=​'Hola mundo'
>>>​ c.startswith(​'H'​)
True
>>>​ c.startswith(​'Hola'​)
True

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

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

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

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


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

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

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

113
Añadir un carácter o subcadena entre cada carácter de una cadena, por
ejemplo una coma o un guión bajo:

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

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


una cadena, por ejemplo espacios:

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


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

O el mismo ejemplo con guiones medios:


>>>​ c ​=​'--------Hola mundo---'
>>>​ c.strip(​'-'​)
'Hola mundo'

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


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

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


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

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


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

Métodos en listas
Añadir elementos a una lista:
>>>​ l ​=​ [​1​, ​2​, ​3​]
>>>​ l.append(​4​)
>>>​ l
[​1​, ​2​, ​3​, ​4​]

Eliminar todos los elementos de una lista:

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

Unir los elementos de dos listas en una:


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

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

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


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

Mostrar la posición del índice en la que aparece por primera vez un elemento
en una lista.
>>>​ l ​=​ [​'Hola'​, ​'mundo'​, ​'mundo'​]
>>>​ l.index(​'Hola'​)
0
>>>​ l.index(​'mundo'​)
1

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

>>>​ l ​=​ [​5​, ​10​, 1


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

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


ninguna posición sacará el último elemento.

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


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

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


borra el primero:

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

Invertir el orden de los elementos de una lista:

>>>​ l ​=​ [​1​, ​2​, ​3​, ​4​, ​5]



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

Ordenar elementos de una lista:

>>>​ l ​=​ [​3​, ​-​15​, ​27​, ​-9


​ ​, ​0]

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

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

>>>​ c ​=​ ​set​()


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

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


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

116
{​'dos'​, ​'uno'​, ​'tres'​}
>>>​ c.discard(​'dos'​)
>>>​ c
{​'uno'​, ​'tres'​}

Hacer una copia de un conjunto existente.

>>>​ c1 ​=​ {​1​, ​2​, ​3​}


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

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


método ​copy()​ del módulo ​copy​ que importamos para hacer copias de objetos
de clases.
Vaciar o eliminar por completo todos los elementos de un conjunto:
>>>​ c ​=​ {​1​, ​2​, ​3​}
>>>​ c {​1​, ​2​, ​3​}
>>>​ c.clear()
>>>​ c
set​()

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


elemento en común con otro conjunto:

>>>​ c1 ​=​ {​1​, ​2​, ​3​}


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

Comprobar si un conjunto es un subconjunto de otro:


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

Comprobar si un conjunto es un superconjunto de un subconjunto:


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

117
Unión de dos conjuntos. Si hay elementos repetidos estos no se añaden varias
veces:
>>>​ c1 ​=​ {​1​, ​2​, ​3​, 4​ ​, 5 ​ ​}
>>>​ c2 ​=​ {​3​, ​4​, ​5​, 6 ​ ​, 7 ​ ​}
>>>​ c1.union(c2)
{​1​, ​2​, ​3​, ​4​, ​5​, ​6​, 7 ​ ​}

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

>>>​ c1
{​1​, ​2​, 3​ ​, 4​ ​, 5​ ​}
>>>​ c2
{​3​, ​4​, 5​ ​, 6​ ​, 7​ ​}

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

>>>​ c1 ​=​ {​1​, ​2​, ​3​, 4​ ​, 5 ​ ​}


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

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

>>>​ c1 ​=​ {​1​, ​2​, ​3​}


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

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


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

Encontrar los elementos comunes entre dos conjuntos:

>>>​ c1 ​=​ {​1​, ​2​, ​3​}

118
>>>​ c2 ​=​ {​3​, ​4​, ​5​}
>>>​ c1.intersection(c2)
{​3​}

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

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

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


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

Métodos en diccionarios
Obtener un valor por defecto cuando queremos acceder a una clave que no
existe en un diccionario:
>>>​ colores ​=​ {​'amarillo'​: '
​ yellow'​, ​'azul'​: ​'blue'​, ​'verde'​: ​'green'​}
>>>​ colores.get(​'amarillo'​, ​'No se encuentra'​)
'yellow'
>>>​ colores.get(​'negro'​, ​'No se encuentra'​)
'No se encuentra'

Obtener una lista con las claves de un diccionario:

>>>​ colores ​=​ {​'amarillo'​: '


​ yellow'​, ​'azul'​: ​'blue'​, ​'verde'​: ​'green'​}
>>>​ colores.keys()
dict_keys([​'amarillo'​, ' ​ azul'​, '
​ verde'​])

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

>>>​ colores ​=​ {​'amarillo'​: '


​ yellow'​, ​'azul'​: ​'blue'​, ​'verde'​: ​'green'​}
>>>​ colores.values()
dict_values([​'yellow'​, ' ​ blue'​, '
​ green'​])

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


diccionario:
>>>​ colores ​=​ {​'amarillo'​: '
​ yellow'​, ​'azul'​: '
​ blue'​, ​'verde'​: ​'green'​}
>>>​ colores.items()
dict_items([(​'amarillo'​, ​'yellow'​), (​'azul'​, ' ​ blue'​), (​'verde'​, ​'green'​)])

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

>>>​ colores ​=​ {​'amarillo'​: '


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

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


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

>>>​ colores ​=​ {​'amarillo'​: '


​ yellow'​, ​'azul'​: ​'blue'​, ​'verde'​: ​'green'​}
>>>​ colores.pop(​'negro'​, ​'No se encuentra'​)
'No se encuentra'

Vaciar o eliminar todos los elementos de un diccionario:


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

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

En esta unidad vamos a ver cómo podemos crear nuestros propios módulos y
paquetes en Python, así como su manejo, y un repaso por algunos de los
módulos standard más útiles.

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

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

120
Ahora crearemos en el mismo directorio otro archivo llamado ​test.py​ donde
importaremos el módulo ​saludos​ de la siguiente manera:

import​ saludos

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

saludos.saludar()

Si ejecutamos nuestros programa ​test.py​ obtendremos la siguiente salida por


pantalla:

python3 test.py
Hola, te estoy saludando desde la función saludar del módulo saludos

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

saludar()

python3 test.py
Hola, te estoy saludando desde la función saludar del módulo saludos

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


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

from​ saludos ​import​ ​*

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


queremos usar unas pocas.

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


ejemplo:

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

121
Ahora en el archivo ​test.py​ podríamos importar el módulo entero y hacer uso
de la clase ​Saludo​ de la siguiente manera:

import​ saludos
saludos.Saludo()

python3 test.py
Hola, te estoy saludando desde el ​__init__​() de la clase Saludo

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

from​ saludos ​import​ Saludo


Saludo()

python3 test.py
Hola, te estoy saludando desde el ​__init__​() de la clase Saludo

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


utilizados.

Collections

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


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

from​ collections ​import​ Counter

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


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

l ​=​ [​1​, ​2​, ​4​, ​3​, ​3​, ​5​, 1


​ ​, ​3​, ​1,
​ ​1​, 6
​ ​]

print​(Counter(l))

python3 test_collections.py
Counter({1: 4, 3: 3, 2: 1, 4: 1, 5: 1, 6: 1})

En este resultado se puede observar que el número ​1​ aparece cuatro veces, el
número ​3​ tres veces, el número ​2​ una vez, etc.

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

p ​=​ ​'Hola mundo!'

122
print​(Counter(p))

python3 test_collections.py
Counter({​'o'​: 2, ​'H'​: 1, ​'l'​: 1, ​'a'​: 1, ​' '​: 1, ​'m'​: 1, ​'u'​: 1, ​'n'​: 1, ​'d'​:
1, ​'!'​: 1})

En este caso el programa nos dice que el carácter ​'o'​ aparece dos veces, el
carácter ​'H'​ una vez, el caracter ​'l'​ una vez, etc.
Podría darse el caso en el que tenemos una cadena de caracteres con una
varias palabras separadas por un espacio, por ejemplo:

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

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

print​(s.split())

python3 test_collections.py
[​'rojo'​, ​'verde'​, ​'azul'​, ​'rojo'​, ​'morado'​, ​'rojo'​, ​'blanco'​, ​'blanco'​]

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


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

print​(Counter(s.split()))

python3 test_collections.py
Counter({​'rojo'​: 3, ​'blanco'​: 2, ​'verde'​: 1, ​'azul'​: 1, ​'morado'​: 1})

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


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

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

c ​=​ Counter(n)

print​(c.most_common(​1​))

python3 test_collections.py [(10, 4)]

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

print​(c.most_common(​2​))

python3 test_collections.py
[(10, 4), (20, 3)]

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


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

from​ collections ​import​ OrderedDict

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

print​(OrderedDict(d))

python3 test_collections.py
​ dog'​), (​'gato'​, ​'cat'​), (​'loro'​, ​'parrot'​)])
OrderedDict([(​'perro'​, '

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


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

​ ​ {​'perro'​: ​'dog'​, ​'gato'​: '


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

print​(d1 ​==​ d2)


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

Datetime

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

import​ datetime

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

dt ​=​ datetime.datetime.now()

124
print​(dt)

python3 test_datetime.py
2019-10-04 14:10:50.879112

De esta manera la variable ​dt​ nos devuelve la fecha y hora actual. También
podemos acceder a cada uno de los atributos del objeto ​dt​, como solo el año,
el mes, día, hora, minuto, segundo o microsegundos, de la siguiente manera:

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

python3 test_datetime.py
2019
10
4
14
25
45
315492

Ahora que ya sabemos cómo podemos acceder a cada uno de los elementos
de los que se compone una fecha y hora podremos darle el formato que se
prefiera, por ejemplo:
print​(​'​{}​/​{}​/​{}​'​.format(dt.year, dt.month, dt.day))
print​(​'​{}​:​{}​:​{}​ ​{}​'​.format(dt.hour, dt.minute, dt.second, dt.microsecond))

python3 test_datetime.py
2019/10/5 8:14:11 307892

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


siguiente ejemplo:
dt ​=​ datetime.datetime(​2000​, ​1,
​ ​1​, 0
​ ​, ​0​)

print​(dt)

python3 test_datetime.py
2000-01-01 00:00:00

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


asignación de un nuevo valor de forma standard, ya que los datos se

125
almacenan en una tupla, y las tuplas son inmutables. Por ejemplo, si queremos
cambiar el año asignando un nuevo valor al atributo ​year​ de la siguiente forma
nos daría el siguiente error:

dt.year ​=​ ​3000

python3 test_datetime.py
Traceback (most recent call last):
File ​"test_datetime.py"​, line 5, i
​ n​ ​<​module​>
dt.year = 3000
AttributeError: attribute ​'year'​ of ​'datetime.date'​ objects is not writable

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


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

dt ​=​ dt.replace(​year​=​3000​)

print​(dt)

python3 test_datetime.py
3000-01-01 00:00:00

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


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

dt ​=​ datetime.datetime.now()

print​(dt.isoformat())

python3 test_datetime.py
2019-10-07T19:46:08.409881

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


método ​strftime()​, por ejemplo:

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

python3 test_datetime.py
Monday 07 October 2019 07:52

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


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

import​ locale

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

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


cambiado:
python3 test_datetime.py
lunes 07 octubre 2019 19:58

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

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

python3 test_datetime.py
星期一 07 十月 2019 20:01

Math

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

import​ math

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


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

print​(​round​(​1.4​))

python3 test_math.py
1

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


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

print​(​round​(​1.5​))

python3 test_math.py
2

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

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

python3 test_math.py
1
1
1

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


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

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

python3 test_math.py
2
2
2

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


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

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


​ ​]

print​(math.fsum(numeros))

python3 test_math.py
15.0

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

numeros ​=​ [​0.9999999​, ​1​, ​2,


​ ​3​]

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

python3 test_math.py
6.999999900000001
6.9999999

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


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

128
También es interesante es el método ​trunc()​, que trunca un número decimal y
devuelve la parte entera, por ejemplo:

print​(math.trunc(​3.14159265359​))

python3 test_math.py
3

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


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

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

python3 test_math.py
8.0
625.0

También tenemos el método ​sqrt()​ que nos permitirá realizar raíces


cuadradas, por ejemplo:

print​(math.sqrt(​9​))

python3 test_math.py
3.0

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

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

python3 test_math.py
3.141592653589793
2.718281828459045

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


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

Random

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


funcionalidades para trabajar y generar números aleatorios. Se utiliza mucho
en el desarrollo de videojuegos o en desarrollos en los que se necesita cierto

129
grado de seguridad. Veamos algunos ejemplos en un nuevo archivo
llamado ​test_random.py​. Lo primero que hay que hacer es importar el
módulo r​ andom​ de la siguiente manera:

import​ random

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


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

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

python3 test_random.py
0.06823749155608883
0.9070119606268106
0.4445508707984924

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


iguales que uno y mayores de cero. Si quisiéramos números random entre un
rango de dos números podríamos usar el método ​uniform()​ y solo tendríamos
que pasar estos dos números como argumentos, por ejemplo números
random entre uno y diez:

​ 1
print​(random.uniform(​1, ​ 0​))
print​(random.uniform(​1,​ 1 ​ 0​))
print​(random.uniform(​1, ​ 1 ​ 0​))

python3 test_random.py
2.382198826548743
4.8697236381240865
3.8781201434396864

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


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

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

python3 test_random.py
8
1
4

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

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

130
print​(random.randrange(​0​, 1
​ 00​))
print​(random.randrange(​0​, 1​ 00​))

python3 test_random.py
95
52
91

Y si añadimos un número ​2​ como tercer argumento solo nos sacará números
random pares entre cero y diez:

print​(random.randrange(​0​, 1
​ 00​, 2 ​ ​))
print​(random.randrange(​0​, 1​ 00​, 2 ​ ​))
print​(random.randrange(​0​, 1 ​ 00​, 2 ​ ​))

python3 test_random.py
94
46
32

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


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

cadena ​=​ ​'Hola mundo!'

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

python3 test_random.py
n
u
o

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

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


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

python3 test_random.py
4
5
3

131
Además del método ​choice()​ también podremos usar un método
llamado ​shuffle()​ para desordenar los elementos de una lista y que
permanezcan guardados de ese modo en la lista de origen, mezcla los
elementos de forma aleatoria, por ejemplo:

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


print​(lista)

random.shuffle(lista)

print​(lista)

python3 test_random.py
[1, 2, 3, 4, 5]
[3, 4, 1, 2, 5]

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


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

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


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

python3 test_random.py
[4, 5, 1]
[4, 2, 5]
[3, 4, 2]

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


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

Paquetes
Utilizar paquetes nos ofrece varias ventajas. En primer lugar nos permite
unificar distintos módulos bajo un mismo número de paquetes. Así podemos
utilizar jerarquías de módulos o submódulos y también subpaquetes. Por otra
parte nos permiten distribuir y manejar fácilmente nuestro código como si

132
fueran librerías instalables de Python. De este modo se pueden utilizar como
módulos standard desde el intérprete sin cargarlos previamente.

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

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


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

from​ paquete.saludos ​import​ ​*

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


hacerlo del siguiente modo:

saludar()

Al ejecutar el programa obtendremos la siguiente salida por pantalla:


python3 test.py
Hola, te estoy saludando desde la función saludar del módulo saludos

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


forma:

Saludo()

python3 test.py
Hola, te estoy saludando desde el ​__init__​() de la clase Saludo

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

133
queden persistentemente en un archivo que luego podría volver a cargarse al
ejecutar el programa de nuevo, de ese modo no se perdería la información.

Python nos permite realizar las siguientes operaciones con ficheros:

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

Para poder trabajar con ficheros debemos importar el paquete ​open​ de la


librería ​io​ (​input/output)​ al inicio de nuestros programas de la siguiente
manera:

from​ io ​import​ ​open

Creación

Una vez importado el paquete ​open​ de la librería ​io​ podemos comenzar con la
creación de un programa que escriba una línea de texto en un archivo, de
modo que si el archivo no existe lo cree. Para ello será necesario crear una
nuevo archivo llamado t​ est_ficheros.py​ con el siguiente código:

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

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

fichero.write(texto)
fichero.close()

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

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


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

134
Si ejecutamos el programa veremos que no hay salida por pantalla alguna,
pero si listamos los archivos que se encuentran en el directorio actual
podremos ver que se ha creado un archivo nuevo llamado ​fichero.txt​, el cual
podremos abrir para ver qué contiene, y podremos comprobar que en su
interior aparece nuestras líneas de texto de la variable t​ exto​.

Apertura o lectura

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


siguiente manera:
fichero ​=​ ​open​(​'fichero.txt'​, ​'r'​)

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

texto ​=​ fichero.read()

fichero.close()

print​(texto)

python3 test_ficheros.py
Esta es una línea de texto.
Y esta es otra línea de texto.

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


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

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

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


lineas ​=​ fichero.readlines()

print​(lineas)

fichero.close()

python3 test_ficheros.py [​'Esta es una línea de texto.\n'​, ​'Y esta es otra


línea de texto.\n'​]

135
Modificación

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


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

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

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


fichero.close()

Esta modalidad no solo sirve para añadir líneas al final del archivo, si no que
también lo crea si este no existe. Si abrimos el fichero ​fichero.txt​ veremos
que ha añadido al final de este una nueva línea con un texto.
Existe una manera un poco más óptima de leer el contenido de un fichero, esta
es mediante la sentencia w​ ith​, veamos un ejemplo:

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


​for​ linea ​in​ fichero:
​print​(linea)

python3 test_ficheros.py
Esta es una línea de texto.

Y esta es otra línea de texto.

Esta es una línea nueva.

Manejo del puntero

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


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

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

fichero.seek(​10​)

texto ​=​ fichero.read()

fichero.close()

print​(texto)

136
python3 test_ficheros.py
a línea de texto.
Y esta es otra línea de texto.
Esta es una línea nueva.

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

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


texto ​=​ fichero.read(​6) ​

fichero.close()

print​(texto)

python3 test_ficheros.py
Esta e

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

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

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

texto ​=​ fichero.read()

fichero.close()

print​(texto)

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


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

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


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

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


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

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

137
texto ​=​ fichero.read()

fichero.close()

print​(texto)

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


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

Esta es una línea de texto.


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

Ficheros y objetos con pickle


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

import​ pickle

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

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


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

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


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

pickle.dump(lista, fichero)

fichero.close()

Si ejecutamos el programa veremos que se ha creado un nuevo


archivo ​lista.bin​. Si tratamos de abrirlo con un editor de texto veremos
carácteres extraños. Esto es porque es un archivo binario, no de texto plano,
pero el contenido del fichero es correcto, contiene nuestra lista.

138
Ahora veremos cómo podemos hacer para leer el fichero una vez lo hemos
generado y cómo poder recuperar nuestra lista. Primero abrimos el fichero en
modo lectura binaria (​'rb'​) y luego creamos una variable llamada ​lista​ que
tenga por valor una llamada al método ​load()​ del módulo ​pickle​ al que le
pasamos el fichero como argumento. Cerramos el fichero e imprimimos el
contenido de lista:

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

lista ​=​ pickle.load(fichero)

fichero.close()

print​(lista)

python3 test_ficheros.py
[1, 2, 3, 4, 5]

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


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

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

Importación de Pandas

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


realiza de la siguiente manera:

import​ pandas ​as​ pd

A partir de este momento podremos utilizar el alias ​pd para invocar cualquier
método de la librería Pandas. Por ejemplo, para importar un fichero CSV lo
haríamos de la siguiente manera:

139
df = pd.read_csv(
​r'file.csv'​,
index_col=​0​,
nrows=​5​,
encoding=​'ISO-8859-1'​,
delimiter=​';'
)

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


donde:

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


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

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


carpeta ​datasets​ con los siguiente atributos:

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

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


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

df.head()

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

df.head(​20​)

Ordenación de los datos

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


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

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

141
)

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


conviene ponerlo con valor ​True​ o ​False​, según nos interese.

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


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

Visualización con Matplotlib

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


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

import​ matplotlib.pyplot ​as​ plt

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

year = [​2020​, ​2021​, ​2022​]


value = [​5​, ​6​, ​9​]

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


de la librería ​matplotlib​, al que le tendremos que pasar como argumentos
primero el array de datos que queremos utilizar en el eje ​x y segundo el array
que usaremos para el eje y​ ​:

plt.plot(year, value)

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

plt.scatter(year, value)

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

import​ pandas ​as​ pd

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

Comprobamos que se ha cargado correctamente:

df

A continuación usamos el método ​plot() de la instancia ​plt al que le


pasaremos el nombre del dataframe, en este caso ​df y entre corchetes el
nombre de la columna que queremos utilizar para representarlo en el eje ​y​, si
no se especifica el eje x​ en este se utilizarán los valores del índice de los
registros de datos:

plt.plot(df[​'temperature'​])

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


el índice de los datos en el eje ​x​.

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

df[​'humidity'​].plot()

Si eliminamos el nombre de la columna entre corchetes y especificamos


únicamente el nombre del dataframe ​df podremos ver en el mismo gráfico
todos los registros de cada columna en líneas diferentes, cada una con un color
distinto.

df.plot()

145
Ejemplo Standard & Poor's 500

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

Primero creamos un dataframe llamado ​df_sp500 en el que importamos el


archivo CSV ​SP500_data.csv​ de la siguiente manera:

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

Si imprimimos la cabecera podremos visualizar los 5 primeros registros:

df_sp500.head()

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


siguiente manera:

import​ matplotlib.pyplot ​as​ plt

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

df_sp500[​'Close'​].plot()

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

df_sp500.index = df_sp500[​'Date'​]

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


índice los valores del campo Date:

df_sp500.head()

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

df_sp500[​'Close'​].plot()

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

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

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

Primero creamos un dataframe llamado ​df_covid19_ccaas en el que importamos


el archivo CSV ​datos_ccaas.csv​ de la siguiente manera:

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

Si imprimimos la cabecera podremos visualizar los 5 primeros registros:

df_covid19_ccaas.head()

148
Como siempre, importamos la librería ​matplotlib si no lo tuviéramos importada
de ejecuciones anteriores.

import​ matplotlib.pyplot ​as​ plt

Estableceremos tal y como hemos visto en el ejemplo anterior el campo ​fecha


como índice y los datos de la columna ​num_casos en el eje ​y de la siguiente
manera:

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

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

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


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

df = pd.read_csv(

149
​r'../datasets/Info_pais.csv'​,
encoding=​'ISO-8859-1'​,
delimiter=​';'
)

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


columna ​Esperanza de vida​ de la siguiente manera:

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

df_order.head()

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


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

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

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

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

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


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

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

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

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


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

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

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

df_order.head()

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

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


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

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

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


usamos la columna ​Poblacion_normalizada​.

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

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

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

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

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


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

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

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

154
fig = plt.gcf()
fig.set_size_inches(​14.5​, ​10​)

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


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

También podremos añadir la etiqueta del nombre del país dentro de cada
burbuja utilizando el método ​annotate()​, por ejemplo añadiendolo solo a los ​10
primeros países con el siguiente código:

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

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

155
for​ i ​in​ range(​1​, ​10​):
plt.annotate(
df_order[​'País'​][i],
(
df_order[​'Renta per capita'​][i],
df_order[​'Esperanza de vida'​][i]
)
)

Otro aspecto que podríamos mejorar es la representación de los datos del eje
y​, son los datos de ​120 países y actualmente se ven muy juntos, se ve mal. esto
se soluciona fácilmente con la función y​ ticks() a la que le pasaremos tres
argumentos, que son un 1​ representando el primer dato, 1​ 20 representando el
último dato, y 1​ 0​ para indicar que los queremos mostrar de diez en diez.

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

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

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

for​ i ​in​ range(​1​, ​10​):


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

plt.yticks(ticks=range(​1​, ​120​, 1
​ 0​))

De este modo hemos creado una visualización de los datos muy potente y de
una manera muy sencilla. La conclusión que podemos sacar de este gráfico es
que efectivamente existe una correlación entre la renta per cápita y la
esperanza de vida según el país. Podemos ver que conforme la renta per
cápita aumenta la esperanza de vida también aumenta. También podemos

157
deducir que el número de población no afecta a la esperanza de vida, ya que
en la visualización que hemos creado se pueden ver países con una gran
cantidad de población que no están entre los valores más bajos en cuanto a
esperanza de vida se refiere.

Conceptos estadísticos básicos

Variables discretas y continuas

Se dice que una variable es ​discreta cuando no puede tomar ningún valor entre
dos consecutivos, y que es ​continua cuando puede tomar cualquier valor dentro
de un intervalo. Por ejemplo:

● Variable discreta:

numero_alumnos_por_clase = [​28​, ​31​, ​30​, ​27​, ​25​, ​36​]

● Variable continua:

temperatura_madrid = [​28.3​, ​29.0​, ​22.47​, ​30.02​, ​17.6​]

Cálculo de la media y de la mediana

La ​media y la ​mediana son dos conceptos estadísticos básicos que debemos


conocer. La media se calcula sumando todos los valores de un conjunto y
dividiendo el resultado entre el número total de valores.

Veamos un ejemplo, supongamos que tenemos la variable ​v1 con la siguiente


lista de valores:

v1 = [​4​, ​6​, ​3​, ​5​, ​8​, ​9,


​ ​2​, 1
​ 2​, 1
​ 6​, 4
​ ​, ​7​, ​42​, ​13​, ​6​, ​7​]

Para calcular la media primero debemos sumar todos los valores de la variable
v1​ y dividirlo entre la cantidad de valores:

sum(v1)/len(v1)

158
9.6

En este caso la media de los valores de la variable ​v1​ es ​9.6​.

La mediana es el valor central de los valores de una variable, una vez estos
están ordenados de manera ascendente. Veamos un ejemplo, utilizando los
valores de la variable ​v1 de antes, solo que con los elementos de la lista
ordenados de forma ascendente y almacenados en una nueva variable llamada
v2​:

v2 = [​2​, ​3​, ​4​, ​4​, ​5​, ​6,


​ ​6​, 7
​ ​, ​7​, ​8,
​ ​9​, ​12​, ​13​, ​16​, ​42​]

Para calcular la mediana podríamos hacer una función llamada ​mediana que
reciba como argumento una lista de números, en nuestro caso le pasamos la
variable v​ 2​, por ejemplo:

def​ ​mediana​(lista):
lst_sorted = sorted(lista)
lst_len = len(lista)
index = (lst_len - ​1​) // ​2​ ​# Floor division.

​if​ (lst_len % ​2​):


​return​ lst_sorted[index]
​else​:
​return​ (lst_sorted[index] + lst_sorted[index + ​1]
​ )/​2.0

mediana(v2)
7.0

Como resultado obtenemos que la mediana de estos valores es ​7​, que coincide
con la posición central de los elementos de la variable ​v2​. Si la variable tuviese
un número par de elementos, la mediana sería la suma de los dos elementos
centrales dividido entre dos.

En la siguiente representación gráfica podemos ver la distribución de los


valores del ejemplo anterior en los que la media (​9.6​) se representa como una
línea azul y la mediana (​7.0​) como línea verde.

159
Existe una diferencia entre la media y la mediana porque hay un valor que dista
mucho del resto de valores de la secuencia, en nuestro caso es el ​45​. A este
tipo de datos se los denomina ​outliers​. Si por el contrario todos los valores de la
secuencia tuvieran un valor más o menos parecido podríamos ver que la media
y la media tendrían también un valor similar.

Otro ejemplo con valores en los que encontramos un ​outlier​ aún más elevado:

v1 = [​4​, ​6​, ​3​, ​5​, 8


​ ​, ​9,​ ​2​, 1
​ 2​, 1
​ 6​, 4
​ ​, 7
​ ​, ​282​, 1​ 3​, 6​ ​, ​7​]
v2 = [​31​, ​23​, ​25​, 2 ​ 0​, ​21​, ​29​, ​24​, ​26​, 3 ​ 0​, ​27​, 2​ 5​, 2​ 4​, ​23​, ​32​, ​24​]

def​ ​mediana​(lista):
lst_sorted = sorted(lista)
lst_len = len(lista)
index = (lst_len - ​1​) // ​2​ ​# Floor division.

​if​ (lst_len % ​2​):


​return​ lst_sorted[index]
​else​:
​return​ (lst_sorted[index] + lst_sorted[index + ​1]
​ )/​2.0

mediana(v2)

print(​f'La media de v1 es: ​{sum(v1)/len(v1)}​'​)


print(​f'La media de v2 es: ​{sum(v2)/len(v2)}​'​)
print(​f'La mediana de v1 es: ​{mediana(v1)}​'​)
print(​f'La mediana de v2 es: ​{mediana(v2)}​'​)

Obtenemos el siguiente resultado:

La media de v1 es: ​25.6

160
La media de v2 es: ​25.6
La mediana de v1 es: ​7
La mediana de v2 es: ​25

Aquí vemos que la media en ambas variables ​v1 y ​v2 es ​25.6​, sin embargo en
las medianas existe una enorme diferencia, debido a que el resultado se ve
más impactado por estos outliers, en este caso ​282​, que dista mucho del resto
de valores de la secuencia.

La media es un valor estadístico muy importante, pero habitualmente se utiliza


la mediana ya que acaba siendo más representativo sobre la distribución de
nuestros datos, y evita todos estos posibles outliers.

Varianza y desviación de una variable

La varianza de una variable nos indica la dispersión de un conjunto de datos


respecto a su valor medio. Esto significa que si tenemos una varianza alta en
nuestra variable, los valores de la variable van a estar más alejados del valor
medio.

Matemáticamente la varianza de una variable se calcula de la siguiente


manera:

1. A cada valor de la variable hay que restarle el promedio de la variable.


2. El resultado anterior se eleva al cuadrado.
3. Hay que hacer un sumatorio con el resultado de las operaciones
anteriores para cada valor de la variable.
4. Se divide entre el número de valores de la variable.

La desviación estándar se puede calcular con la raíz cuadrada de la varianza:

Veamos un caso de uso. Tenemos una variable ​x que tiene como valor el peso
de los tomates que hemos recolectado durante un periodo de 8 días.

● Tomate x_1 = 60gr


● Tomate x_2 = 56gr
● Tomate x_3 = 61gr
● Tomate x_4 = 68gr

161
● Tomate x_5 = 51gr
● Tomate x_6 = 53gr
● Tomate x_7 = 69gr
● Tomate x_8 = 54gr

x = [​60​, ​56​, ​61​, ​68​, ​51​, ​53​, ​69​, ​54​]

El primer paso que hay que dar es calcular la media de la variable ​x​, tal y como
hemos visto anteriormente:

sum(x)/len(x)
59.0

En el numerador tenemos la suma de todos los valores de la variable ​x​, que en


este caso de uso es ​472​, en el denominador tenemos el número de elementos o
datos que tiene la variable x​ ​, en este caso 8​ ​. Como resultado la media es 5​ 9
gramos.

En el segundo paso tendremos restar a cada valor de la variable ​x la media y


luego elevar el resultado al cuadrado. Al final hay que sumar todos los valores
obtenidos en esta última operación, tal y como se muestra en la siguiente tabla:

for​ n ​in​ map(​lambda​ i : pow(i​-59​, ​2​), x):

162
print(n)
1
9
4
81
64
36
100
25
sum(map(​lambda​ i : pow(i​-59​, ​2)
​ , x))
320

El último paso para calcular la varianza de ​x es dividir ​320 entre el número de


elementos de la variable ​x​, que en este caso es ​8​. Como resultado tenemos
que la varianza de ​x​ es ​40​.

320​/​8
40.0

Mas allá de la varianza podemos calcular también la desviación estándar de la


variable ​x​, basta con calcular la raíz cuadrada de la varianza.

import​ math
math.sqrt(​40​)
6.324555320336759

Obtenemos como resultado ​6.32​. Esto nos permite tener un valor que se
encuentra dentro del orden de magnitud de nuestra variable ​x​, es decir, entre
todos nuestros tomates existe una desviación estándar de ​6.32​ gramos.

Veamos qué pasaría si tuviéramos otro caso en el que recogiéramos otros 8


tomates y almacenamos el peso de cada uno de estos tomates en esta nueva
variable y​ ​:

● Tomate y_1 = 50gr


● Tomate y_2 = 66gr
● Tomate y_3 = 51gr

163
● Tomate y_4 = 78gr
● Tomate y_5 = 41gr
● Tomate y_6 = 63gr
● Tomate y_7 = 59gr
● Tomate y_8 = 64gr

y = [​50​, ​66​, ​51​, ​78​, ​41​, ​63​, ​59​, ​64​]

Vemos que hay una mayor dispersión en los datos de partida, sin embargo la
media es ​59​, la misma que en el caso de la variable ​x​.

sum(y)/len(y)
59.0

Si ahora calculamos del mismo modo que antes el numerador del término de la
varianza podremos ver que en la tabla hay valores mucho más elevados que lo
que teníamos previamente:

for​ n ​in​ map(​lambda​ i : pow(i​-59​, ​2​), y):


print(n)
81
49
64
361
324
16
0

164
25
sum(map(​lambda​ i : pow(i​-59​, ​2)
​ , y))
920

Esto se debe a que la secuencia original de datos dista mucho mas del valor
promedio de ​59 gramos. Con estos nuevos datos, si calculamos la varianza en
este caso obtendremos un valor de ​115​.

920​/​8
115.0

Y la desviación estándar serían ​10.72​ gramos

import​ math

math.sqrt(​115​)
10.723805294763608

Lo que hemos aprendido con estos dos ejemplos es que conforme el conjunto
de valores tiene una mayor dispersión respecto al valor promedio, al final
tendrá un valor de varianza superior. Por lo tanto la varianza nos proporciona
una medida de la volatilidad o incertidumbre de una determinada variable o
conjunto de datos. De hecho, si todos los valores de la variable fueran iguales
el valor de la varianza sería 0​ ​, es decir, no habría ningún tipo de incertidumbre.

Se suele utilizar más la varianza puesto que está menos influenciada por los
valores positivos o negativos que pudiera haber en la diferencia entre el valor y
el promedio, por lo tanto la varianza es más representativa a la hora de calcular
la dispersión de los datos de nuestro conjunto de datos.

NumPy
NumPy es una librería de Python enfocada en el cálculo numérico que nos
permite realizar operaciones de una manera sencilla y rápida. Su objeto base
es un vector de números denominado Array. Es una alternativa a las listas que
hemos visto hasta ahora y nos va a permitir realizar una serie de funciones muy

165
potentes. A diferencia de las listas, donde se opera de forma independiente en
cada uno de los elementos, en los Arrays las operaciones se van a realizar
sobre todo el Array simultáneamente. A continuación se muestra una diferencia
entre listas y Arrays de NumPy a la hora de realizar una suma de elementos,
por ejemplo, tenemos estas dos listas:

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

Si sumamos las dos listas obtenemos lo siguiente:

a + b
[​1​, ​2​, ​3​, ​4​, ​5​, ​6​]

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


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

a = np.array([​1​, 2
​ ​, 3​ ​])
b = np.array([​4​, 5​ ​, 6​ ​])

a + b
array([​5​, ​7​, ​9​])

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

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

Importación de NumPy

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


realiza de la siguiente manera:

import​ numpy ​as​ np

166
Ejemplo Índice de Masa Corporal (IMC)

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

altura = [​1.7​, ​1.65​, ​1.82​]


peso = [​67​, ​55​, ​72​]

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

peso / altura**​2

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


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

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


[​23.18339100346021​, ​20.202020202020204​, ​21.736505252988767​]

El cálculo está bien hecho, pero de este modo es más complicado, entre otras
cosas porque el cálculo de va haciendo secuencialmente elemento a elemento
entre las listas, y en este caso no es mucho problema ya que son listas de solo
3 elementos, pero podría ser un problema al trabajar con listas de miles de
elementos. Se realizar el mismo cálculo de una manera mucho más eficiente
usando los Arrays de NumPy, veamos el ejemplo:

np_altura = np.array([​1.7​, 1 ​ .65​, ​1.82​])


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

imc = np_peso / np_altura**​2


imc
array([​23.183391​ , ​20.2020202​ , ​21.73650525​])

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

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

167
imc > ​21
array([ ​True​, ​False​, ​True​])

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

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

imc[imc > ​21​]


array([​23.183391​ , ​21.73650525​])

Ejemplo cálculo áreas de triángulos

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


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

base * altura / ​2

Para ello tenemos los siguiente dos Arrays:

bases_tri = np.array([​2​, ​2.37​, 3


​ .05​, ​1.75​, ​4​, 2​ .81​])
alturas_tri = np.array([​1.21​, ​2.6​, 4 ​ .4​, ​7.03​, ​4.01​, ​5.25​])

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


siguiente manera:

areas_tri = bases_tri * alturas_tri / ​2


areas_tri
array([​1.21​ , ​3.081​ , ​6.71​ ​ .15125​, ​8.02​
, 6 , ​7.37625​])

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

areas_tri[areas_tri > 6​ .5​]


array([​6.71​ , ​8.02​ , ​7.37625​])

168
También podemos hacer una conjunción de condiciones, por ejemplo para
obtener solo las áreas que son ​> 6.5 y también ​< 8​, para ello usaremos el AND
lógico mediante el símbolo ​&​ de la siguiente manera:

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


array([​6.71​ , ​7.37625​])

Arrays de dos dimensiones en NumPy

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


es una matriz de ​m​ filas por ​n​ columnas.

La sintaxis para crearla es la siguiente:

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

Veamos cómo crear un array bidimensional como este:

a = np.array([[​2​, ​7​, ​8]


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

Si quisiéramos obtener el valor ​10 de nuestro array de dos dimensiones


tendríamos que especificar el índice de la fila seguido del índice de la columna,

169
teniendo en cuenta que los índices siempre empiezan por el número ​0​. En este
ejemplo el valor ​10 se encuentra en la fila con índice ​1 y la columna con índice
2​.

a[​1​, ​2​]
10

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

a[:, ​0​:​2​]
array([[​2​, 7 ​ ​],
[​4​, 8​ ​]])

Cálculo estadístico con NumPy

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

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

temperaturas = np.array([​12​, ​13.5​, 1 ​ 3​, ​14​, ​13.2​, ​14.8​, ​15​, 1


​ 5.16​, ​16​, ​16.2​,
15.7​, ​17​, ​17.2​, ​16.8​, ​14​, ​14.2​, ​14.7​, ​16​, ​17.5​])

Podríamos calcular la media o promedio usando la función ​mean(array) de la


siguiente manera:

np.mean(temperaturas)
15.050526315789472

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

np.median(temperaturas)
15.0

170
Podremos obtener los valores mínimos y máximos con las funciones ​min(array)
y ​max(array)​.

np.min(temperaturas)
12.0
np.max(temperaturas)
17.5

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


manera:

np.var(temperaturas)
2.2893207756232683

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


siguiente manera:

np.std(temperaturas)
1.5130501563475245

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


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

np.percentile(temperaturas, ​90​)
17.04

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


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

Generación de datos random con NumPy

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

nombre_array = np.random.normal(

171
media,
desviacion_estandar,
numero_muestras
)

Por ejemplo, si quisiéramos

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

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


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

media = np.mean(array_gauss)
media
2.00020348250122

desviacion = np.std(array_gauss)
desviacion
0.496490256867947

Si visualizamos los datos se ven de la siguiente manera:

import​ matplotlib.pyplot ​as​ plt


import​ scipy.stats ​as​ stats

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

172

También podría gustarte