Aprende Python
Aprende Python
10 de enero de 2024
Core
1 Introducción 3
1.1 Hablando con la máquina . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Algo de historia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3 Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2 Entornos de desarrollo 25
2.1 Thonny . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.2 Contexto real . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3 VSCode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3 Tipos de datos 47
3.1 Datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.2 Números . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.3 Cadenas de texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
6 Modularidad 221
6.1 Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
6.2 Objetos y Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
6.3 Excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
i
6.4 Módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
10 Scraping 561
10.1 requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 562
10.2 beautiulsoup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 570
10.3 selenium . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 585
ii
Aprende Python
Curso gratuito para aprender el lenguaje de programación Python con un enoque práctico,
incluyendo ejercicios y cobertura para distintos niveles de conocimiento.1
Este proyecto va de la mano con pycheck una herramienta que permite trabajar todos los
ejercicios propuestos con casos de prueba incluidos y vericación de los resultados.
Licencia: Creative Commons Reconocimiento 4.0 Internacional: CC BY 4.0.
Consejo: «Programming is not about typing, it’s about thinking.» – Rich Hickey
1
En la oto de portada aparecen los Monty Python. Fuente: noticiascyl
Core 1
Aprende Python
2 Core
CAPÍTULO 1
Introducción
Este capítulo es una introducción a la programación para conocer, desde un enoque sencillo
pero aclaratorio, los mecanismos que hay detrás de ello.
3
Aprende Python
Los ordenadores son dispositivos complejos pero están diseñados para hacer una cosa bien:
ejecutar aquello que se les indica. La cuestión es cómo indicar a un ordenador lo que
queremos que ejecute. Esas indicaciones se llaman técnicamente instrucciones y se expresan
en un lenguaje. Podríamos decir que programar consiste en escribir instrucciones para que
sean ejecutadas por un ordenador. El lenguaje que utilizamos para ello se denomina lenguaje
de programación.1
Pero aún seguimos con el problema de cómo hacer que un ordenador (o máquina) entienda
el lenguaje de programación. A priori podríamos decir que un ordenador sólo entiende un
lenguaje muy «simple» denominado código máquina. En este lenguaje se utilizan únicamente
los símbolos 0 y 1 en representación de los niveles de tensión alto y bajo, que al n y al
cabo, son los estados que puede manejar un circuito digital. Hablamos de sistema binario. Si
tuviéramos que escribir programas de ordenador en este ormato sería una tarea ardua, pero
aortunadamente se han ido creando con el tiempo lenguajes de programación intermedios
que, posteriormente, son convertidos a código máquina.
Si intentamos visualizar un programa en código máquina, únicamente obtendríamos una
secuencia de ceros y unos:
1.1.2 Ensamblador
SYS_SALIDA equ 1
section .data
msg db "Hello, World",0x0a
len equ $ - msg ;longitud de msg
section .text
global _start ;para el linker
_start: ;marca la entrada
mov eax, 4 ;llamada al sistema (sys_write)
(continué en la próxima página)
1
Foto original por Garett Mizunaka en Unsplash.
4 Capítulo 1. Introducción
Aprende Python
Aunque resulte diícil de creer, lo «único» que hace este programa es mostrar en la pantalla
de nuestro ordenador la rase «Hello, World», pero además teniendo en cuenta que sólo
uncionará para una arquitectura x86.
1.1.3 C
Aunque el lenguaje ensamblador nos acilita un poco la tarea de desarrollar programas, sigue
siendo bastante complicado ya que las instrucciones son muy especícas y no proporcionan
una semántica entendible. Uno de los lenguajes que vino a suplir – en parte – estos obstáculos
ue C. Considerado para muchas personas como un reerente en cuanto a los lenguajes de
programación, permite hacer uso de instrucciones más claras y potentes. El mismo ejemplo
anterior del programa «Hello, World» se escribiría así en lenguaje C :
#include <stdio.h>
int main() {
printf("Hello, World");
return 0;
}
1.1.4 Python
print(Hello, World)
¡Pues así de ácil! Hemos pasado de código máquina (ceros y unos) a código Python en el
que se puede entender perectamente lo que estamos indicando al ordenador. La pregunta
que surge es: ¿cómo entiende una máquina lo que tiene que hacer si le pasamos un programa
hecho en Python (o cualquier otro lenguaje de alto nivel)? La respuesta es un compilador.
1.1.5 Compiladores
Los compiladores son programas que convierten un lenguaje «cualquiera» en código máquina.
Se pueden ver como traductores, permitiendo a la máquina interpretar lo que queremos hacer.
print(Hello, World)
0 0 RESUME 0
1 2 PUSH_NULL
4 LOAD_NAME 0 (print)
6 LOAD_CONST 0 (Hello, World)
8 PRECALL 1
12 CALL 1
22 RETURN_VALUE
2
Iconos originales por Flaticon.
3
Véase más inormación sobre el intérprete de bytecode.
6 Capítulo 1. Introducción
Aprende Python
Nota: Si queremos ver una dierencia entre un lenguaje compilado como C y un lenguaje
«interpretado» como Python es que, aunque ambos realizan un proceso de traducción del
código uente, la compilación de C genera un código objeto que debe ser ejecutado en una
segunda ase explícita, mientras que la compilación de Python genera un «bytecode» que se
ejecuta (interpreta) de orma «transparente».
4
Imagen extraída del artículo Python bytecode analysis.
1
Foto original por Dario Veronesi en Unsplash.
8 Capítulo 1. Introducción
Aprende Python
Luego los avances en las ciencias inormáticas han sido muy acelerados, se reemplazaron los
tubos de vacío por transistores en 1958 y en el mismo año, se sustituyeron por circuitos
integrados, y en 1961 se miniaturizaron en chips de silicio. En 1971 apareció el primer
microprocesador de Intel; y en 1973 el primer sistema operativo CP/M. El primer computador
personal es comercializado por IBM en el año 1980.
2
Fuente: Meatze.
10 Capítulo 1. Introducción
Aprende Python
De acuerdo a este breve viaje por la historia, la programación está vinculada a la aparición
de los computadores, y los lenguajes tuvieron también su evolución. Inicialmente, como ya
hemos visto, se programaba en código binario, es decir en cadenas de 0s y 1s, que es el
lenguaje que entiende directamente el computador, tarea extremadamente diícil; luego se
creó el lenguaje ensamblador, que aunque era lo mismo que programar en binario, al estar
en letras era más ácil de recordar. Posteriormente aparecieron lenguajes de alto nivel,
que en general, utilizan palabras en inglés, para dar las órdenes a seguir, para lo cual utilizan
un proceso intermedio entre el lenguaje máquina y el nuevo código llamado código uente,
este proceso puede ser un compilador o un intérprete.
Un compilador lee todas las instrucciones y genera un resultado; un intérprete ejecuta
y genera resultados línea a línea. En cualquier caso han aparecido nuevos lenguajes de
programación, unos denominados estructurados y en la actualidad en cambio los lenguajes
orientados a objetos y los lenguajes orientados a eventos.3
12 Capítulo 1. Introducción
Aprende Python
1.3 Python
14 Capítulo 1. Introducción
Aprende Python
Ventajas
Desventajas
1.3. Python 15
Aprende Python
16 Capítulo 1. Introducción
Aprende Python
1.3. Python 17
Aprende Python
También podemos reseñar el inorme anual que realiza GitHub sobre el uso de tecnologías en
su plataorma. En la edición de 2023 del estado del opensource de GitHub, Python ocupaba
el segundo puesto de los lenguajes de programación más usados, sólo por detrás de
JavaScript:
18 Capítulo 1. Introducción
Aprende Python
Un dato curioso, o directamente un «rikismo»: Desde Python 3.8, cada nueva versión estable
sale a la luz en el mes de Octubre. En este escenario de Python 3.version se cumplen las
siguientes igualdades:
1.3. Python 19
Aprende Python
1.3.4 CPython
Nivel avanzado
Existen múltiples implementaciones de Python según el lenguaje de programación que se
ha usado para desarrollarlo. Veamos algunas de ellas:
Implementación Lenguaje
CPython C
Jython Java
IronPython C#
Brython JavaScript
RustPython Rust
MicroPython C
Existen una serie de reglas «losócas» que indican una manera de hacer y de pensar dentro
del mundo pitónico2 creadas por Tim Peters, llamadas el Zen de Python y que se pueden
aplicar incluso más allá de la programación:
2
Dícese de algo/alguien que sigue las convenciones de Python.
20 Capítulo 1. Introducción
Aprende Python
En su traducción de la Wikipedia:
• Bello es mejor que eo.
• Explícito es mejor que implícito.
• Simple es mejor que complejo.
• Complejo es mejor que complicado.
• Plano es mejor que anidado.
• Espaciado es mejor que denso.
• La legibilidad es importante.
• Los casos especiales no son lo sucientemente especiales como para romper las reglas.
• Sin embargo la practicidad le gana a la pureza.
• Los errores nunca deberían pasar silenciosamente.
• A menos que se silencien explícitamente.
• Frente a la ambigüedad, evitar la tentación de adivinar.
• Debería haber una, y preeriblemente solo una, manera obvia de hacerlo.
• A pesar de que esa manera no sea obvia a menos que seas Holandés.
1.3. Python 21
Aprende Python
22 Capítulo 1. Introducción
Aprende Python
1.3. Python 23
Aprende Python
24 Capítulo 1. Introducción
CAPÍTULO 2
Entornos de desarrollo
Para poder utilizar Python debemos preparar nuestra máquina con las herramientas
necesarias. Este capítulo trata sobre la instalación y conguración de los elementos adecuados
para el desarrollo con el lenguaje de programación Python.
25
Aprende Python
2.1 Thonny
Thonny es un programa muy interesante para empezar a aprender Python, ya que engloba
tres de las herramientas undamentales para trabajar con el lenguaje: intérprete, editor y
depurador.1
Cuando vamos a trabajar con Python debemos tener instalado, como mínimo, un intérprete
del lenguaje (para otros lenguajes sería un compilador). El intérprete nos permitirá
ejecutar nuestro código para obtener los resultados deseados. La idea del intéprete es lanzar
instrucciones «sueltas» para probar determinados aspectos.
Pero normalmente queremos ir un poco más allá y poder escribir programas algo más largos,
por lo que también necesitaremos un editor. Un editor es un programa que nos permite
crear cheros de código (en nuestro caso con extensión *.py), que luego son ejecutados por
el intérprete.
Hay otra herramienta interesante dentro del entorno de desarrollo que sería el depurador.
Lo podemos encontrar habitualmente en la bibliograía por su nombre inglés debugger.
Es el módulo que nos permite ejecutar paso a paso nuestro código y visualizar qué está
ocurriendo en cada momento. Se suele usar normalmente para encontrar allos (bugs) en
nuestros programas y poder solucionarlos (debug/x).
Cuando nos encontramos con un programa que proporciona estas unciones (e incluso otras
adicionales) para el trabajo de programación, nos reerimos a él como un Entorno Integrado de
Desarrollo, conocido popularmente por sus siglas en inglés IDE (por Integrated Development
1
Foto original de portada por reddie marriage en Unsplash.
2.1.1 Instalación
Para instalar Thonny debemos acceder a su web y descargar la aplicación para nuestro
sistema operativo. La ventaja es que está disponible tanto para Windows, Mac y Linux.
Una vez descargado el chero lo ejecutamos y seguimos su instalación paso por paso.
Una vez terminada la instalación ya podemos lanzar la aplicación que se verá parecida a la
siguiente imagen:
Nota: Es posible que el aspecto del programa varíe ligeramente según el sistema operativo,
conguración de escritorio, versión utilizada o idioma (en mi caso está en inglés), pero a
eectos de uncionamiento no hay dierencia.
2.1. Thonny 27
Aprende Python
Para hacer una prueba inicial del intérprete vamos a retomar el primer programa que se
suele hacer. Es el llamado «Hello, World». Para ello escribimos lo siguiente en el intérprete
y pulsamos la tecla ENTER:
Lo que hemos hecho es indicarle a Python que ejecute como entrada la instrucción
print(Hello, World). La salida es el texto Hello, World que lo vemos en la siguiente
línea (ya sin el prompt >>>).
Importante: Los cheros que contienen programas hechos en Python siempre deben tener
la extensión .py
2
Término inglés que se reere al símbolo que precede la línea de comandos.
3
La carpeta donde se guarden los archivos de código no es crítico para su ejecución, pero sí es importante
mantener un orden y una organización para tener localizados nuestros cheros y proyectos.
Ahora ya podemos ejecutar nuestro chero helloworld.py. Para ello pulsamos el botón verde
con triángulo blanco (en la barra de herramientas) o bien damos a la tecla F5. Veremos que
en el panel de Shell nos aparece la salida esperada. Lo que está pasando «entre bambalinas»
es que el intérprete de Python está recibiendo como entrada el chero que hemos creado; lo
ejecuta y devuelve la salida para que Thonny nos lo muestre en el panel correspondiente.
Aunque ya lo veremos en proundidad, lo que hemos hecho es añadir una variable msg en
la línea 1 para luego utilizarla al mostrar por pantalla su contenido. Si ahora volvemos a
ejecutar nuestro programa veremos que en el panel de variables nos aparece la siguiente
inormación:
Name Value
msg Hello, World
2.1. Thonny 29
Aprende Python
sesión de depuración y podemos avanzar instrucción por instrucción usando la tecla F7:
Hemos visto que Thonny es una herramienta especialmente diseñada para el aprendizaje de
Python, integrando dierentes módulos que acilitan su gestión. Si bien lo podemos utilizar
para un desarrollo más «serio», se suele recurrir a un fujo de trabajo algo dierente en
contextos más reales.1
2.2.1 Python
La orma más habitual de instalar Python (junto con sus librerías) es descargarlo e instalarlo
desde su página ocial:
• Versiones de Python para Windows
• Versiones de Python para Mac
• Versiones de Python para Linux
Anaconda
Otra de las alternativas para disponer de Python en nuestro sistema y que además es muy
utilizada, es Anaconda. Se trata de un conjunto de herramientas, orientadas en principio a
la ciencia de datos, pero que podemos utilizarlas para desarrollo general en Python (junto
con otras librerías adicionales).
Existen versiones de pago, pero la distribución Individual Edition es «open-source» y
gratuita. Se puede descargar desde su página web. Anaconda trae por deecto una gran
cantidad de paquetes Python en su distribución.
Ver también:
Miniconda es un instalador mínimo que trae por deecto Python y un pequeño número de
paquetes útiles.
La instalación limpia2 de Python ya orece de por sí muchos paquetes y módulos que vienen
por deecto. Es lo que se llama la librería estándar. Pero una de las características más
destacables de Python es su inmenso «ecosistema» de paquetes disponibles en el Python
Package Index (PyPI).
Para gestionar los paquetes que tenemos en nuestro sistema se utiliza la herramienta pip,
una utilidad que también se incluye en la instalación de Python. Con ella podremos instalar,
1
Foto original de portada por SpaceX en Unsplash.
2
También llamada «vanilla installation» ya que es la que viene por deecto y no se hace ningúna
personalización.
Consejo: Para el caso de Anaconda usaríamos conda install pandas (aunque ya viene
preinstalado).
Nivel intermedio
Cuando trabajamos en distintos proyectos, no todos ellos requieren los mismos paquetes
ni siquiera la misma versión de Python. La gestión de estas situaciones no es sencilla si
únicamente instalamos paquetes y manejamos conguraciones a nivel global (a nivel de
máquina). Es por ello que surge el concepto de entornos virtuales. Como su propio nombre
indica se trata de crear distintos entornos en unción de las necesidades de cada proyecto, y
esto nos permite establecer qué versión de Python usaremos y qué paquetes instalaremos.
La manera más sencilla de crear un entorno virtual es la siguiente:
1 $ cd myproject
2 $ python -m venv --prompt myproject .venv
3 $ source .venv/bin/activate
virtualenv
Si bien con virtualenv tenemos las uncionalidades necesarias para trabajar con entornos
virtuales, destacaría una herramienta llamada virtualenvwrapper que unciona por encima
de virtualenv y que acilita las operaciones sobre entornos virtuales. Su instalación es
equivalente a cualquier otro paquete Python:
$ pip install virtualenvwrapper
pyenv
pyenv permite cambiar ácilmente entre múltiples versiones de Python en un mismo sistema.
Su instalación engloba varios pasos y está bien explicada en la página del proyecto.
La mayor dierencia con respecto a virtualenv es que no instala las distintas versiones de
Python a nivel global del sistema. En vez de eso, se suele crear una carpeta .pyenv en el
HOME del usuario, donde todo está aislado sin generar intrusión en el sistema operativo.
Podemos hacer cosas como:
• Listar las versiones de Python instaladas:
$ pyenv versions
3.7.4
* 3.5.0 (set by /Users/yuu/.pyenv/version)
miniconda3-3.16.0
pypy-2.6.0
$ python --version
Python 3.5.0
$ python --version
Python 3.7.4
$ cd /cool-project
$ pyenv local 3.9.1
$ python --version
Python 3.9.1
2.2.4 Editores
Existen multitud de editores en el mercado que nos pueden servir perectamente para escribir
código Python. Algunos de ellos incorporan uncionalidades extra y otros simplemente nos
permiten editar cheros. Cabe destacar aquí el concepto de Entorno de Desarrollo
Integrado, más conocido por sus siglas en inglés IDE3 . Se trata de una aplicación
inormática que proporciona servicios integrales para el desarrollo de sotware.
Podríamos decir que Thonny es un IDE de aprendizaje, pero existen muchos otros. Veamos
un listado de editores de código que se suelen utilizar para desarrollo en Python:
• Editores generales o IDEs con soporte para Python:
– Eclipse + PyDev
– Sublime Text
– Atom
– GNU Emacs
– Vi-Vim
– Visual Studio (+ Python Tools)
– Visual Studio Code (+ Python Tools)
• Editores o IDEs especícos para Python:
3
Integrated Development Environment.
– PyCharm
– Spyder
– Thonny
Cada editor tiene sus características (ventajas e inconvenientes). Supongo que la preerencia
por alguno de ellos estará en base a la experiencia y a las necesidades que surjan. La parte
buena es que hay diversidad de opciones para elegir.
Truco: Visual Studio Code también dispone de integración con Jupyter Notebooks.
2.2.6 repl.it
Figura 4: repl.it
• Almacenamiento de 500MB.
• Python 3.8.2 (ebrero de 2022).
• 117 paquetes preinstalados (ebrero de 2022).
• Navegador (y subida) de cheros integrado.
• Gestor de paquetes integrado.
• Integración con GitHub.
• Gestión de secretos (datos sensibles).
• Base de datos clave-valor ya integrada.
• Acceso (limitado) al sistema operativo y sistema de cheros.
2.2.7 WSL
Linux nativo. Es importante también saber que existen dos versiones de WSL hoy en día:
WSL y WSL2. La segunda es bastante reciente (publicada a mediados de 2019), tiene mejor
rendimiento y se adhiere más al comportamiento de un Linux nativo.
Para la instalación de WSL7 hay que seguir los siguientes pasos:
1. Lanzamos Powershell con permisos de administrador.
2. Activamos la característica de WSL:
4. Finalmente, la instalamos:
$ Add-AppxPackage .\Ubuntu.appx
En este punto, WSL debería estar instalado correctamente, y debería también aparecer en
el menú Inicio.
7
Tutorial de instalación de WSL.
2.3 VSCode
2.3.1 Instalación
VSCode tiene disponibles paquetes autoinstalables para todos los sistemas operativos.
Extensiones recomendadas
Para escribir un «mejor» código Python en VSCode sería deseable tener instaladas las
siguientes extensiones:
• Python
• Flake8
• Black Formatter
• Mypy Type Checker
1
También conocido por IDE siglas en inglés de Integrated Development Environment.
2
Foto original de portada por Kelly Sikkema en Unsplash.
2.3. VSCode 39
Aprende Python
• isort
• Python Indent
• Python Type Hint
Conocer los atajos de teclado de tu editor avorito es undamental para mejorar el fujo de
trabajo y ser más productivo. Veamos los principales atajos de teclado de Visual Studio
Code3 .
Acción Atajo
Abrir paleta de comandos Ctrl + Shift + P
Abrir archivo Ctrl + P
Nueva ventana Ctrl + Shift + N
Cerrar ventana Ctrl + Shift + W
Ajustes del perl Ctrl + ,
Acción Atajo
Crear un nuevo archivo Ctrl + N
Abrir archivo Ctrl + O
Guardar archivo Ctrl + S
Cerrar Ctrl + F4
Abrir Terminal Ctrl +
Panel de problemas Ctrl + Shift + M
Acción Atajo
Cortar linea Ctrl + X
Copiar linea Ctrl + C
Borrar linea Ctrl + Shift + K
Insertar linea abajo Enter
Insertar linea arriba Ctrl + Shift + Enter
Buscar en archivo abierto Ctrl + F
Reemplazar Ctrl + H
Linea de comentario Ctrl + /
Bloque de comentario Shift + Alt + A
Salto de linea Alt + Z
Seleccionar lineas Alt + Click Mouse
continué en la próxima página
3
Fuente: Gastón Danielsen en Dev.To.
Acción Atajo
Acercar Zoom Ctrl + +
Alejar Zoom Ctrl + -
Barra lateral Ctrl + B
Abrir debug Ctrl + Shift + D
Panel de salida Ctrl + Shift + U
Control de source Ctrl + Shift + G
Acceder a extensiones Ctrl + Shift + X
Abrir terminal integrado Ctrl + Shift + Ñ
2.3. VSCode 41
Aprende Python
Ahora ya podemos lanzar la depuración pulsando la tecla F5. Nos aparecerá el siguiente
mensaje en el que dejaremos la opción por deecto Archivo de Python y pulsamos la tecla
:
2.3. VSCode 43
Aprende Python
2.3. VSCode 45
Aprende Python
Tipos de datos
Igual que en el mundo real cada objeto pertenece a una categoría, en programación
manejamos objetos que tienen asociado un tipo determinado. En este capítulo se verán
los tipos de datos básicos con los que podemos trabajar en Python.
47
Aprende Python
3.1 Datos
Los programas están ormados por código y datos. Pero a nivel interno de la memoria del
ordenador no son más que una secuencia de bits. La interpretación de estos bits depende del
lenguaje de programación, que almacena en la memoria no sólo el puro dato sino distintos
metadatos.1
Cada «trozo» de memoria contiene realmente un objeto, de ahí que se diga que en Python
todo son objetos. Y cada objeto tiene, al menos, los siguientes campos:
• Un tipo del dato almacenado.
• Un identicador único para distinguirlo de otros objetos.
• Un valor consistente con su tipo.
A continuación se muestran los distintos tipos de datos que podemos encontrar en Python,
sin incluir aquellos que proveen paquetes externos:
1
Foto original de portada por Alexander Sinn en Unsplash.
3.1. Datos 49
Aprende Python
3.1.2 Variables
Las variables son undamentales ya que permiten denir nombres para los valores que
tenemos en memoria y que vamos a usar en nuestro programa.
Podemos obtener un listado de las palabras reservadas del lenguaje de la siguiente orma:
>>> help(keywords)
Here is a list of the Python keywords. Enter any keyword to get more help.
Nota: Por lo general se preere dar nombres en inglés a las variables que utilicemos, ya que
así hacemos nuestro código más «internacional» y con la posibilidad de que otras personas
puedan leerlo, entenderlo y – llegado el caso – modicarlo. Es sólo una recomendación, nada
impide que se haga en castellano.
Importante: Los nombres de variables son «case-sensitive»3 . Por ejemplo, stuff y Stuff
son nombres dierentes.
3.1. Datos 51
Aprende Python
Mientras se sigan las reglas que hemos visto para nombrar variables no hay problema en la
orma en la que se escriban, pero sí existe una convención para la nomenclatura de las
variables. Se utiliza el llamado snake_case en el que utilizamos caracteres en minúsculas
(incluyendo dígitos si procede) junto con guiones bajos – cuando sean necesarios para su
legibilidad –.4 Por ejemplo, para nombrar una variable que almacene el número de canciones
en nuestro ordenador, podríamos usar num_songs.
Esta convención, y muchas otras, están denidas en un documento denominado PEP 8. Se
trata de una guía de estilo para escribir código en Python. Los PEPs5 son las propuestas
que se hacen para la mejora del lenguaje.
Aunque hay múltiples herramientas disponibles para la comprobación del estilo de código,
una bastante accesible es http://pep8online.com/ ya que no necesita instalación, simplemente
pegar nuestro código y vericar.
Constantes
Un caso especial y que vale la pena destacar son las constantes. Podríamos decir que es un
tipo de variable pero que su valor no cambia a lo largo de nuestro programa. Por ejemplo
la velocidad de la luz. Sabemos que su valor es constante de 300.000 km/s. En el caso
de las constantes utilizamos mayúsculas (incluyendo guiones bajos si es necesario) para
nombrarlas. Para la velocidad de la luz nuestra constante se podría llamar: LIGHT_SPEED.
Se suele decir que una persona programadora (con cierta experiencia), a lo que dedica más
tiempo, es a buscar un buen nombre para sus variables. Quizás pueda resultar algo excesivo
pero da una idea de lo importante que es esta tarea. Es undamental que los nombres de
variables sean autoexplicativos, pero siempre llegando a un compromiso entre ser concisos
y claros.
Supongamos que queremos buscar un nombre de variable para almacenar el número de
elementos que se deben manejar en un pedido:
1. n
2. num_elements
3. number_of_elements
4. number_of_elements_to_be_handled
4
Más inormación sobre convenciones de nombres en PEP 8.
5
Del término inglés «Python Enhancement Proposals».
No existe una regla mágica que nos diga cuál es el nombre perecto, pero podemos aplicar
el sentido común y, a través de la experiencia, ir detectando aquellos nombres que sean más
adecuados. En el ejemplo anterior, quizás podríamos descartar de principio la opción 1 y la
4 (por ser demasiado cortas o demasiado largas); nos quedaríamos con las otras dos. Si nos
jamos bien, casi no hay mucha inormación adicional de la opción 3 con respecto a la 2. Así
que podríamos concluir que la opción 2 es válida para nuestras necesidades. En cualquier
caso esto dependerá siempre del contexto del problema que estemos tratando.
Como regla general:
• Usar nombres para variables (ejemplo article).
• Usar verbos para unciones (ejemplo get_article()).
• Usar adjetivos para booleanos (ejemplo available).
3.1.3 Asignación
3.1. Datos 53
Aprende Python
Python nos orece la posibilidad de hacer una asignación múltiple de la siguiente manera:
En este caso las tres variables utilizadas en el «lado izquierdo» tomarán el valor 3.
Recordemos que los nombres de variables deben seguir unas reglas establecidas, de lo contrario
obtendremos un error sintáctico del intérprete de Python:
Las asignaciones que hemos hecho hasta ahora han sido de un valor literal a una variable.
Pero nada impide que podamos hacer asignaciones de una variable a otra variable:
Eso sí, la variable que utilicemos como valor de asignación debe existir previamente, ya
que si no es así, obtendremos un error inormando de que no está denida:
Hemos visto previamente cómo asignar un valor a una variable, pero aún no sabemos cómo
«comprobar» el valor que tiene dicha variable. Para ello podemos utilizar dos estrategias:
1. Si estamos en un intérprete («shell» o consola) de Python, basta con que usemos el
nombre de la variable:
final_stock = 38934
print(final_stock)
Nota: print() sirve también cuando estamos en una sesión interactiva de Python («shell»)
Para poder descubrir el tipo de un literal o una variable, Python nos orece la unción type().
Veamos algunos ejemplos de su uso:
>>> type(9)
int
>>> type(1.2)
float
3.1. Datos 55
Aprende Python
Advertencia: Aunque está permitido, NUNCA llames type a una variable porque
destruirías la unción que nos permite conocer el tipo de un objeto.
Ejercicio
Utilizando la consola interactiva de Python >>>, realiza las siguientes tareas:
1. Asigna un valor entero 2001 a la variable space_odyssey y muestra su valor.
2. Descubre el tipo del literal Good night & Good luck.
3. Identica el tipo del literal True.
4. Asigna la expresión 10 * 3.0 a la variable result y muestra su tipo.
3.1.4 Mutabilidad
Nivel avanzado
Las variables son nombres, no lugares. Detrás de esta rase se esconde la refexión de que
cuando asignamos un valor a una variable, lo que realmente está ocurriendo es que se hace
apuntar el nombre de la variable a una zona de memoria en el que se representa el objeto
(con su valor):
>>> a = 5
Si ahora «copiamos» el valor de a en otra variable b se podría esperar que hubiera otro
espacio en memoria para dicho valor, pero como ya hemos dicho, son reerencias a memoria:
>>> b = a
>>> id(a)
4445989712
>>> id(b)
4445989712
3.1. Datos 57
Aprende Python
>>> id(10)
4333546384
>>> id(20)
4333546704
Cada vez que asignamos un nuevo valor a una variable, ésta apunta a una nueva zona de
memoria:
>>> a = 5
>>> id(a)
4310690224
>>> a = 7
>>> id(a)
4310690288
Cuando la zona de memoria que ocupa el objeto se puede modicar hablamos de tipos de
datos mutables. En otro caso hablamos de tipos de datos inmutables.
Por ejemplo, las listas son un tipo de dato mutable ya que podemos modicar su contenido
(aunque la asignación de un nuevo valor sigue generando un nuevo espacio de memoria).
Inmutable Mutable
bool list
int set
float dict
str
tuple
Importante: El hecho de que un tipo de datos sea inmutable signica que no podemos
modicar su valor «in-situ», pero siempre podremos asignarle un nuevo valor (hacerlo apuntar
a otra zona de memoria).
Nivel intermedio
Hemos ido usando una serie de unciones sin ser especialmente conscientes de ello. Esto se
debe a que son unciones «built-in» o incorporadas por deecto en el propio lenguaje Python.
3.1. Datos 59
Aprende Python
>>> help(id)
Help on built-in function id in module builtins:
id(obj, /)
Return the identity of an object.
>>> id?
Signature: id(obj, /)
Docstring:
Return the identity of an object.
AMPLIAR CONOCIMIENTOS
3.2 Números
En esta sección veremos los tipos de datos númericos que orece Python centrándonos en
booleanos, enteros y fotantes.1
3.2.1 Booleanos
George Boole es considerado como uno de los undadores del campo de las ciencias de la
computación y ue el creador del Álgebra de Boole que da lugar, entre otras estructuras
algebraicas, a la Lógica binaria. En esta lógica las variables sólo pueden tomar dos valores
discretos: verdadero o also.
El tipo de datos bool proviene de lo explicado anteriormente y admite dos posibles valores:
• True que se corresponde con verdadero (y también con 1 en su representación
numérica).
• False que se corresponde con also (y también con 0 en su representación numérica).
Veamos un ejemplo de su uso:
3.2. Números 61
Aprende Python
La primera variable is_opened está representando el hecho de que algo esté abierto, y al
tomar el valor True podemos concluir que sí. La segunda variable has_sugar nos indica si
una bebida tiene azúcar; dado que toma el valor False inerimos que no lleva azúcar.
Atención: Tal y como se explicó en este apartado, los nombres de variables son
«case-sensitive». De igual modo el tipo booleano toma valores True y False con la
primera letra en mayúsculas. De no ser así obtendríamos un error sintáctico.
3.2.2 Enteros
Los números enteros no tienen decimales pero sí pueden contener signo y estar expresados
en alguna base distinta de la usual (base 10).
Literales enteros
>>> 8
8
>>> 0
0
>>> 08
File "<stdin>", line 1
08
^
SyntaxError: invalid token
(continué en la próxima página)
A continuación se muestra una tabla con las distintas operaciones sobre enteros que podemos
realizar en Python:
>>> 2 + 8 + 4
14
>>> 4 ** 4
256
>>> 7 / 3
2.3333333333333335
>>> 7 // 3
2
(continué en la próxima página)
3.2. Números 63
Aprende Python
Es de buen estilo de programación dejar un espacio entre cada operador. Además hay que
tener en cuenta que podemos obtener errores dependiendo de la operación (más bien de los
operandos) que estemos utilizando, como es el caso de la división por cero.
Igualmente es importante tener en cuenta la prioridad de los distintos operadores:
Prioridad Operador
1 (mayor) ()
2 **
3 -a +a
4 * / // %
5 (menor) +-
>>> 2 ** 2 + 4 / 2
6.0
>>> 2 ** (2 + 4) / 2
32.0
>>> 2 ** (2 + 4 / 2)
16.0
Asignación aumentada
Estas dos ormas son equivalentes a nivel de resultados y uncionalidad, pero obviamente
tienen dierencias de escritura y legibilidad. De este mismo modo, podemos aplicar un ormato
compacto al resto de operaciones:
>>> random_number = 15
>>> random_number += 5
>>> random_number
20
>>> random_number *= 3
>>> random_number
60
3.2. Números 65
Aprende Python
Módulo
La operación módulo (también llamado resto), cuyo símbolo en Python es %, se dene como
el resto de dividir dos números. Veamos un ejemplo para enteder bien su uncionamiento:
>>> dividendo = 17
>>> divisor = 5
>>> cociente
3
>>> resto
2
Exponenciación
>>> 4 ** 3
64
>>> 4 * 4 * 4
64
Se debe tener en cuenta que también podemos elevar un número entero a un número
decimal. En este caso es como si estuviéramos haciendo una raíz 2 . Por ejemplo:
1 √
4 2 = 40.5 = 4=2
Hecho en Python:
2
No siempre es una raíz cuadrada porque se invierten numerador y denominador.
>>> 4 ** 0.5
2.0
Ejercicio
pycheck: quadratic
Valor absoluto
>>> abs(-1)
1
>>> abs(1)
1
>>> abs(-3.14)
3.14
>>> abs(3.14)
3.14
Límite de un entero
Nivel avanzado
¿Cómo de grande puede ser un int en Python? La respuesta es de cualquier tamaño. Por
poner un ejemplo, supongamos que queremos representar un centillón. Este valor viene a ser
un «1» seguido por ¡600 ceros! ¿Será capaz Python de almacenarlo?
>>> centillion
100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Nota: En muchos lenguajes tratar con enteros tan largos causaría un «integer overfow».
No es el caso de Python que puede manejar estos valores sin problema.
¿Qué pasaría si quisiéramos «romper» todas las barreras? Pongamos 10.000 dígitos…
3.2. Números 67
Aprende Python
>>> 10 ** 10_000
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Exceeds the limit (4300) for integer string conversion; use sys.set_int_
˓→max_str_digits() to increase the limit
Obtenemos un error… pero subsanable, ya que hay orma de ampliar este límite inicial de
4300 dígitos usando la unción sys.set_int_max_str_digits()
3.2.3 Flotantes
Los números en punto fotante3 tienen parte decimal. Veamos algunos ejemplos de
fotantes en Python.
Conversión de tipos
Conversión implícita
>>> True + 25
26
>>> 7 * False
0
>>> True + False
1
>>> 21.8 + True
22.8
>>> 10 + 11.3
21.3
Se puede ver claramente que la conversión numérica de los valores booleanos es:
• True 1
• False 0
Conversión explícita
Aunque más adelante veremos el concepto de unción, desde ahora podemos decir que
existen una serie de unciones para realizar conversiones explícitas de un tipo a otro:
bool() Convierte el tipo a booleano.
int() Convierte el tipo a entero.
float() Convierte el tipo a fotante.
Veamos algunos ejemplos de estas unciones:
>>> bool(1)
True
>>> bool(0)
(continué en la próxima página)
3.2. Números 69
Aprende Python
En el caso de que usemos la unción int() sobre un valor fotante nos retornará su parte
baja:
⌊ ⌋
() =
Por ejemplo:
>>> int(3.1)
3
>>> int(3.5)
3
>>> int(3.9)
3
>>> sound_level = 35
>>> type(sound_level)
int
Pero también existe la posibilidad seguimos comprobar el tipo que tiene una variable
mediante la unción isinstance():
Ejercicio
pycheck: sin_approx
Errores de aproximación
Nivel intermedio
Supongamos el siguiente cálculo:
Debería dar 1.0, pero no es así puesto que la representación interna de los valores en coma
fotante sigue el estándar IEEE 754 y estamos trabajando con aritmética nita.
Aunque existen distintas ormas de solventar esta limitación, de momento veremos una de las
más sencillas utilizando la unción «built-in» round() que nos permite redondear un número
fotante a un número determinado de decimales:
>>> pi = 3.14159265359
>>> round(pi)
3
>>> round(pi, 1)
3.1
>>> round(pi, 2)
3.14
>>> round(pi, 3)
3.142
>>> round(pi, 4)
3.1416
>>> round(pi, 5)
3.14159
3.2. Números 71
Aprende Python
>>> round(result, 1)
1.0
Prudencia: round() aproxima al valor más cercano, mientras que int() obtiene siepre
el entero «por abajo».
Límite de un flotante
A dierencia de los enteros, los números fotantes sí que tienen un límite en Python. Para
descubrirlo podemos ejecutar el siguiente código:
>>> sys.float_info.min
2.2250738585072014e-308
>>> sys.float_info.max
1.7976931348623157e+308
3.2.4 Bases
Nivel intermedio
Los valores numéricos con los que estamos acostumbrados a trabajar están en base 10 (o
decimal). Esto indica que disponemos de 10 «símbolos» para representar las cantidades. En
este caso del 0 al 9.
Pero también es posible representar números en otras bases. Python nos orece una serie
de prejos y unciones para este cometido.
Base binaria
>>> 0b1001
9
>>> 0b1100
12
Función: bin()
>>> bin(9)
0b1001
>>> bin(12)
0b1100
Base octal
>>> 0o6243
3235
>>> 0o1257
687
Función: oct()
>>> oct(3235)
0o6243
>>> oct(687)
0o1257
Base hexadecimal
>>> 0x7F2A
32554
>>> 0x48FF
18687
Función: hex()
3.2. Números 73
Aprende Python
>>> hex(32554)
0x7f2a
>>> hex(18687)
0x48ff
EJERCICIOS DE REPASO
1. pycheck: circle_area
2. pycheck: sphere_volume
3. pycheck: triangle_area
4. pycheck: interest_rate
5. pycheck: euclid_distance
6. pycheck: century_year
7. pycheck: red_square
8. pycheck: igic
9. pycheck: super_ast
10. pycheck: move_twice
11. pycheck: pillars
12. pycheck: clock_time
13. pycheck: xor
14. pycheck: ring_area
EJERCICIOS EXTERNOS
AMPLIAR CONOCIMIENTOS
3.2. Números 75
Aprende Python
Las cadenas de texto son secuencias de caracteres. También se les conoce como «strings»
y nos permiten almacenar inormación textual de orma muy cómoda.1
Es importante destacar que Python 3 almacena los caracteres codicados en el estándar
Unicode, lo que es una gran ventaja con respecto a versiones antiguas del lenguaje. Además
permite representar una cantidad ingente de símbolos incluyendo los amosos emojis .
Para escribir una cadena de texto en Python basta con rodear los caracteres con comillas
simples6 :
Para incluir comillas dobles dentro de la cadena de texto no hay mayor inconveniente:
1
Foto original de portada por Roman Krat en Unsplash.
6
También es posible utilizar comillas dobles. Yo me he decantado por las comillas simples ya que quedan
más limpias y suele ser el ormato que devuelve el propio intérprete de Python.
Para incluir comillas simples dentro de la cadena de texto cambiamos las comillas exteriores
a comillas dobles:
Truco: Eectivamente, como se puede ver, las cadenas de texto en Python se pueden escribir
con comillas simples o con comillas dobles. Es indierente. En mi caso personal preero
usar comillas simples.
Elijas lo que elijas, ¡haz siempre lo mismo!
Comillas triples
Hay una orma alternativa de crear cadenas de texto y es utilizar comillas triples. Su uso
está pensado principalmente para cadenas multilínea:
En este caso sí que se debería utilizar comillas dobles siguiendo las indicaciones de la
guía de estilo de Python:
In Python, single-quoted strings and double-quoted strings are the same. This
PEP does not make a recommendation or this. Pick a rule and stick to it. When
a string contains single or double quote characters, however, use the other one
to avoid backslashes in the string. It improves readability.
For triple-quoted strings, always use double quote characters to be consistent
with the docstring convention in PEP 257.
Importante: Los tres puntos ... que aparecen a la izquierda de las líneas no están incluidos
en la cadena de texto. Es el símbolo que orece el intérprete de Python cuando saltamos de
línea.
Cadena vacía
La cadena vacía es aquella que no contiene ningún carácter. Aunque a priori no lo pueda
parecer, es un recurso importante en cualquier código. Su representación en Python es la
siguiente:
>>>
3.3.2 Conversión
Podemos crear «strings» a partir de otros tipos de datos usando la unción str():
>>> str(True)
True
>>> str(10)
10
>>> str(21.7)
21.7
>>> int(10)
10
>>> float(21.7)
21.7
Pero hay que tener en cuenta un detalle. La unción int() también admite la base en la
que se encuentra el número. Eso signica que podemos pasar un número, por ejemplo, en
hexadecimal (como «string») y lo podríamos convertir a su valor entero:
Nota: La base por deecto que utiliza int() para convertir cadenas de texto es la base
decimal.
Python permite escapar el signicado de algunos caracteres para conseguir otros resultados.
Si escribimos una barra invertida \ antes del carácter en cuestión, le otorgamos un signicado
especial.
Quizás la secuencia de escape más conocida es \n que representa un salto de línea, pero
existen muchas otras:
# Salto de línea
>>> msg = Primera línea\nSegunda línea\nTercera línea
>>> print(msg)
Primera línea
Segunda línea
Tercera línea
# Tabulador
>>> msg = Valor = \t40
>>> print(msg)
Valor = 40
# Comilla simple
>>> msg = Necesitamos \escapar\ la comilla simple
>>> print(msg)
Necesitamos escapar la comilla simple
# Barra invertida
>>> msg = Capítulo \\ Sección \\ Encabezado
>>> print(msg)
Capítulo \ Sección \ Encabezado
Nota: Al utilizar la unción print() es cuando vemos realmente el resultado de utilizar los
caracteres escapados.
Expresiones literales
Nivel intermedio
Hay situaciones en las que nos interesa que los caracteres especiales pierdan ese signicado y
poder usarlos de otra manera. Existe un modicar de cadena que proporciona Python para
tratar el texto en bruto. Es el llamado «raw data» y se aplica anteponiendo una r a la cadena
de texto.
Veamos algunos ejemplos:
Hemos estado utilizando la unción print() de orma sencilla, pero admite algunos
parámetros interesantes:
Línea 4: Podemos imprimir todas las variables que queramos separándolas por comas.
Línea 7: El separador por deecto entre las variables es un espacio, podemos cambiar el
carácter que se utiliza como separador entre cadenas.
Línea 10: El carácter de nal de texto es un salto de línea, podemos cambiar el carácter
que se utiliza como nal de texto.
Los programas se hacen para tener interacción con el usuario. Una de las ormas de
interacción es solicitar la entrada de datos por teclado. Como muchos otros lenguajes de
programación, Python también nos orece la posibilidad de leer la inormación introducida
por teclado. Para ello se utiliza la unción input():
>>> name = input(Introduzca su nombre: )
Introduzca su nombre: Sergio
>>> name
Sergio
>>> type(name)
str
Nota: La unción input() siempre nos devuelve un objeto de tipo cadena de texto o str.
Tenerlo muy en cuenta a la hora de trabajar con números, ya que debemos realizar una
conversión explícita.
Advertencia: Aunque está permitido, NUNCA llames input a una variable porque
destruirías la unción que nos permite leer datos desde teclado. Y tampoco uses nombres
derivados como _input o input_ ya que no son nombres representativos que identiquen
el propósito de la variable.
Ejercicio
Escriba un programa en Python que lea por teclado dos números enteros y muestre por
pantalla el resultado de realizar las operaciones básicas entre ellos.
Ejemplo
• Valores de entrada 7 y 4.
• Salida esperada:
7+4=11
7-4=3
(continué en la próxima página)
Consejo:
• Aproveche todo el potencial que orece print() para conseguir la salida esperada
• No utilice «-strings».
• Guarde el programa en un chero calc.py y ejecútelo desde la terminal con: python
calc.py
Combinar cadenas
Repetir cadenas
>>> reaction * 4
WowWowWowWow
Obtener un carácter
Los «strings» están indexados y cada carácter tiene su propia posición. Para obtener un
único carácter dentro de una cadena de texto es necesario especicar su índice dentro de
corchetes [...].
>>> sentence[0]
H
>>> sentence[-1]
o
>>> sentence[4]
,
>>> sentence[-5]
M
Truco: Nótese que existen tanto índices positivos como índices negativos para acceder
a cada carácter de la cadena de texto. A priori puede parecer redundante, pero es muy útil
en determinados casos.
En caso de que intentemos acceder a un índice que no existe, obtendremos un error por uera
de rango:
>>> sentence[50]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range
Las cadenas de texto son tipos de datos inmutables. Es por ello que no podemos modicar
un carácter directamente:
Truco: Existen ormas de modicar una cadena de texto que veremos más adelante,
aunque realmente no estemos transormando el original sino creando un nuevo objeto con
las modicaciones.
Advertencia: No hay que conundir las constantes con los tipos de datos inmutables.
Es por ello que las variables que almacenan cadenas de texto, a pesar de ser inmutables,
no se escriben en mayúsculas.
2
El término usado en inglés es slice.
>>> proverb[:]
Agua pasada no mueve molino
>>> proverb[12:]
no mueve molino
>>> proverb[:11]
Agua pasada
>>> proverb[5:11]
pasada
>>> proverb[5:11:2]
psd
Importante: El troceado siempre llega a una unidad menos del índice nal que hayamos
especicado. Sin embargo el comienzo sí coincide con el que hemos puesto.
Para obtener la longitud de una cadena podemos hacer uso de len(), una unción común a
prácticamente todos los tipos y estructuras de datos en Python:
>>> empty =
>>> len(empty)
0
Pertenencia de un elemento
Si queremos comprobar que una determinada subcadena se encuentra en una cadena de texto
utilizamos el operador in para ello. Se trata de una expresión que tiene como resultado un
valor «booleano» verdadero o also:
>>> proverb = Más vale malo conocido que bueno por conocer
Habría que prestar atención al caso en el que intentamos descubrir si una subcadena no está
en la cadena de texto:
Limpiar cadenas
Cuando leemos datos del usuario o de cualquier uente externa de inormación, es bastante
probable que se incluyan en esas cadenas de texto, caracteres de relleno3 al comienzo y
al nal. Python nos orece la posibilidad de eliminar estos caracteres u otros que no nos
interesen.
La unción strip() se utiliza para eliminar caracteres del principio y del nal de un
«string». También existen variantes de esta unción para aplicarla únicamente al comienzo
o únicamente al nal de la cadena de texto.
Supongamos que debemos procesar un chero con números de serie de un determinado
artículo. Cada línea contiene el valor que nos interesa pero se han «colado» ciertos caracteres
de relleno que debemos limpiar:
>>> serial_number.strip()
48374983274832
Nota: Si no se especican los caracteres a eliminar, strip() usa por deecto cualquier
3
Se suele utilizar el término inglés «padding» para reerirse a estos caracteres.
A continuación vamos a hacer «limpieza» por la izquierda (comienzo) y por la derecha (nal)
utilizando la unción lstrip() y rstrip() respectivamente:
Como habíamos comentado, también existe la posibilidad de especicar los caracteres que
queremos borrar:
>>> serial_number.strip(\n)
\t \n 48374983274832 \n\n\t \t
Importante: La unción strip() no modica la cadena que estamos usando (algo obvio
porque los «strings» son inmutables) sino que devuelve una nueva cadena de texto con las
modicaciones pertinentes.
Realizar búsquedas
Aunque hemos visto que la orma pitónica de saber si una subcadena se encuentra dentro
de otra es a través del operador in, Python nos orece distintas alternativas para realizar
búsquedas en cadenas de texto.
Vamos a partir de una variable que contiene un trozo de la canción Mediterráneo de Joan
Manuel Serrat para ejemplicar las distintas opciones que tenemos:
>>> lyrics.startswith(Quizás)
True
>>> lyrics.endswith(Final)
False
>>> lyrics.find(amor)
93
>>> lyrics.find(universo)
-1
>>> lyrics.index(universo)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: substring not found
>>> lyrics.count(mi)
2
>>> lyrics.count(tu)
3
>>> lyrics.count(él)
0
Ejercicio
pycheck: lost_word
Reemplazar elementos
Mayúsculas y minúsculas
Python nos permite realizar variaciones en los caracteres de una cadena de texto para pasarlos
a mayúsculas y/o minúsculas. Veamos las distintas opciones disponibles:
>>> proverb
quien a buen árbol se arrima Buena Sombra le cobija
>>> proverb.capitalize()
Quien a buen árbol se arrima buena sombra le cobija
>>> proverb.title()
Quien A Buen Árbol Se Arrima Buena Sombra Le Cobija
>>> proverb.upper()
QUIEN A BUEN ÁRBOL SE ARRIMA BUENA SOMBRA LE COBIJA
>>> proverb.lower()
quien a buen árbol se arrima buena sombra le cobija
>>> proverb.swapcase()
QUIEN A BUEN ÁRBOL SE ARRIMA bUENA sOMBRA LE COBIJA
Identificando caracteres
Hay veces que recibimos inormación textual de distintas uentes de las que necesitamos
identicar qué tipo de caracteres contienen. Para ello Python nos orece un grupo de
unciones.
Veamos algunas de estas unciones:
En este apartado veremos cómo interpolar valores dentro de cadenas de texto utilizando
dierentes ormatos. Interpolar (en este contexto) signica sustituir una variable por su valor
dentro de una cadena de texto.
Veamos los estilos que proporciona Python para este cometido:
Aunque aún podemos encontrar código con el estilo antiguo y el estilo nuevo en el ormateo
de cadenas, vamos a centrarnos en el análisis de los «-strings» que se están utilizando
bastante en la actualidad.
«f-strings»
Los -strings aparecieron en Python 3.6 y se suelen usar en código de nueva creación. Es
la orma más potente – y en muchas ocasiones más eciente – de ormar cadenas de texto
incluyendo valores de otras variables.
La interpolación en cadenas de texto es un concepto que existe en la gran mayoría de
lenguajes de programación y hace reerencia al hecho de sustituir los nombres de variables
por sus valores cuando se construye un «string».
Para indicar en Python que una cadena es un «-string» basta con precederla de una f e
incluir las variables o expresiones a interpolar entre llaves {...}.
Supongamos que disponemos de los datos de una persona y queremos ormar una rase de
bienvenida con ellos:
>>> fMe llamo {name}, tengo {age} años y una fortuna de {fortune} millones
Me llamo Elon Musk, tengo 49 años y una fortuna de 43300 millones
Podría surgir la duda de cómo incluir llaves dentro de la cadena de texto, teniendo en cuenta
que las llaves son símbolos especiales para la interpolación de variables. La respuesta es
duplicar las llaves:
>>> x = 10
Formateando cadenas
Nivel intermedio
Los «-strings» proporcionan una gran variedad de opciones de ormateado: ancho del
texto, número de decimales, tamaño de la cira, alineación, etc. Muchas de estas acilidades
se pueden consultar en el artículo Best o Python3.6 -strings4
Dando ormato a valores enteros:
>>> f{mount_height:10d}
3718
>>> f{mount_height:010d}
0000003718
>>> PI = 3.14159265
>>> f{PI:.3f}
3.142
>>> f{PI:12f}
3.141593
>>> f{PI:7.2f}
3.14
>>> f{PI:07.2f}
0003.14
>>> f{PI:.010f}
3.1415926500
>>> f{PI:e}
3.141593e+00
>>> f{text1:<7s}|{text2:^11s}|{text3:>7s}
how | are | you
>>> f{text1:-<7s}|{text2:·^11s}|{text3:->7s}
how----|····are····|----you
>>> f{value:o}
177451
>>> f{value:x}
ff29
Por supuesto en el caso de otras bases también es posible aplicar los mismos modicadores
de ancho y de relleno vistos para números enteros decimales. Por ejemplo:
>>> f{value:07x}
000ff29
Ver también:
Nótese la dierencia de obtener el cambio de base con este método rente a las unciones de
cambio de base ya vistas previamente que añaden el prejo de cada base 0b, 0o y 0x.
Modo «debug»
A partir de Python 3.8, los «-strings» permiten imprimir el nombre de la variable y su valor,
como un atajo para depurar nuestro código. Para ello sólo tenemos que incluir un símbolo =
después del nombre de la variable:
>>> f{serie=}
"serie=The Simpsons"
>>> f{imdb_rating=}
imdb_rating=8.7
Modo «representación»
>>> print(f{name})
Steven Spielberg
Pero si quisiéramos ver la representación del objeto, tal y como se almacena internamente,
podríamos utilizar el modicador !r en el «-string»:
>>> print(f{name!r})
Steven Spielberg
En este caso se han añadido las comillas denotando que es una cadena de texto. Este
modicador se puede aplicar a cualquier otro tipo de dato.
Ejercicio
Dada la variable:
e = 2.71828
2.718
2.718280
2.72 # 4 espacios en blanco
2.718280e+00
00002.7183
2.71828 # 12 espacios en blanco
Python trabaja por deecto con caracteres Unicode. Eso signica que tenemos acceso a la
amplia carta de caracteres que nos orece este estándar de codicación.
Supongamos un ejemplo sobre el típico «emoji» de un cohete denido en este cuadro:
>>> \N{ROCKET}
Ver también:
Tabla ASCII
Ejercicio
pycheck: nd_unicode
Comparar cadenas
>>> ord(c)
99
>>> ord(p)
112
Nota: Internamente se utiliza la unción ord() para comparar qué carácter está «antes».
Otros ejemplos:
Tener en cuenta que en Python la letras mayúsculas van antes que las minúsculas:
>>> ord(A)
65
>>> ord(a)
97
Nivel avanzado
Hemos estado usando muchas unciones de objetos tipo «string» (y de otros tipos
previamente). Pero quizás no sabemos aún como podemos descubrir todo lo que podemos
hacer con ellos y los casos de uso que nos orece.
Python proporciona una unción «built-in» llamada dir() para inspeccionar un determinado
tipo de objeto:
>>> dir(text)
[__add__,
__class__,
__contains__,
__delattr__,
__dir__,
__doc__,
__eq__,
__format__,
__ge__,
__getattribute__,
__getitem__,
__getnewargs__,
__gt__,
__hash__,
__init__,
__init_subclass__,
__iter__,
__le__,
__len__,
__lt__,
__mod__,
__mul__,
__ne__,
__new__,
__reduce__,
__reduce_ex__,
__repr__,
__rmod__,
__rmul__,
__setattr__,
__sizeof__,
__str__,
__subclasshook__,
(continué en la próxima página)
Esto es aplicable tanto a variables como a literales e incluso a tipos de datos (clases)
explícitos:
>>> dir(10)
[__abs__,
__add__,
__and__,
__bool__,
...
imag,
numerator,
real,
to_bytes]
>>> dir(float)
[__abs__,
__add__,
__bool__,
__class__,
...
hex,
imag,
is_integer,
real]
EJERCICIOS DE REPASO
1. pycheck: switch_name
2. pycheck: samba_split
3. pycheck: ni_digit
4. pycheck: n_repeat
5. pycheck: str_metric
6. pycheck: h2md
7. pycheck: count_sheeps
8. pycheck: strip1
9. pycheck: swap_name
10. pycheck: nd_integral
11. pycheck: multiply_jack
12. pycheck: rst_last_digit
AMPLIAR CONOCIMIENTOS
Control de flujo
Todo programa inormático está ormado por instrucciones que se ejecutan en orma
secuencial de «arriba» a «abajo», de igual manera que leeríamos un libro. Este orden
constituye el llamado fujo del programa. Es posible modicar este fujo secuencial para
que tome biurcaciones o repita ciertas instrucciones. Las sentencias que nos permiten hacer
estas modicaciones se engloban en el control de fujo.
103
Aprende Python
4.1 Condicionales
En esta sección veremos las sentencias if y match-case junto a las distintas variantes que
pueden asumir, pero antes de eso introduciremos algunas cuestiones generales de escritura
de código.1
A dierencia de otros lenguajes que utilizan llaves para denir los bloques de código,
cuando Guido Van Rossum creó el lenguaje quiso evitar estos caracteres por considerarlos
innecesarios. Es por ello que en Python los bloques de código se denen a través de
espacios en blanco, preeriblemente 4.2 En términos técnicos se habla del tamaño
de indentación.
Consejo: Esto puede resultar extraño e incómodo a personas que vienen de otros lenguajes
de programación pero desaparece rápido y se siente natural a medida que se escribe código.
1
Foto original de portada por ali naezare en Unsplash.
2
Reglas de indentación denidas en PEP 8
4.1.2 Comentarios
Los comentarios son anotaciones que podemos incluir en nuestro programa y que nos
permiten aclarar ciertos aspectos del código. Estas indicaciones son ignoradas por el
intérprete de Python.
Los comentarios se incluyen usando el símbolo almohadilla # y comprenden hasta el nal de
la línea.
Los comentarios también pueden aparecer en la misma línea de código, aunque la guía de
estilo de Python no aconseja usarlos en demasía:
Los programas suelen ser más legibles cuando las líneas no son excesivamente largas. La
longitud máxima de línea recomendada por la guía de estilo de Python es de 80 caracteres.
Sin embargo, esto genera una cierta polémica hoy en día, ya que los tamaños de pantalla
han aumentado y las resoluciones son mucho mayores que hace años. Así las líneas de más
de 80 caracteres se siguen visualizando correctamente. Hay personas que son más estrictas
en este límite y otras más fexibles.
En caso de que queramos romper una línea de código demasiado larga, tenemos dos
opciones:
1. Usar la barra invertida \:
>>> factorial = 4 * 3 * 2 * 1
>>> factorial = 4 * \
... 3 * \
... 2 * \
... 1
>>> factorial = 4 * 3 * 2 * 1
>>> factorial = (4 *
... 3 *
... 2 *
... 1)
4.1.4 La sentencia if
La sentencia condicional en Python (al igual que en muchos otros lenguajes de programación)
es if. En su escritura debemos añadir una expresión de comparación terminando con
dos puntos al nal de la línea. Veamos un ejemplo:
>>> temperature = 40
En el caso anterior se puede ver claramente que la condición se cumple y por tanto se ejecuta
la instrucción que tenemos dentro del cuerpo de la condición. Pero podría no ser así. Para
controlar ese caso existe la sentencia else. Veamos el mismo ejemplo anterior pero añadiendo
esta variante:
>>> temperature = 20
>>> temperature = 28
Python nos orece una mejora en la escritura de condiciones anidadas cuando aparecen
consecutivamente un else y un if. Podemos sustituirlos por la sentencia elif:
3
El anidamiento (o «nesting») hace reerencia a incorporar sentencias unas dentro de otras mediante la
inclusión de diversos niveles de prounidad (indentación).
>>> temperature = 28
Nivel intermedio
Supongamos que queremos asignar un nivel de riesgo de incendio en unción de la
temperatura. En su versión clásica escribiríamos:
>>> temperature = 35
Sin embargo, esto lo podríamos abreviar con una asignación condicional de una única
línea:
>>> fire_risk
HIGH
Cuando escribimos condiciones debemos incluir alguna expresión de comparación. Para usar
estas expresiones es undamental conocer los operadores que nos orece Python:
Operador Símbolo
Igualdad ==
Desigualdad !=
Menor que <
Menor o igual que <=
Mayor que >
Mayor o igual que >=
A continuación vamos a ver una serie de ejemplos con expresiones de comparación. Téngase
en cuenta que estas expresiones habría que incluirlas dentro de la sentencia condicional en
el caso de que quisiéramos tomar una acción concreta:
>>> value == 8
True
>>> value != 8
False
Python orece la posibilidad de ver si un valor está entre dos límites de manera directa. Así,
por ejemplo, para descubrir si x está entre 4 y 12 haríamos:
Nota:
Ejercicio
pycheck: leap_year
Cortocircuito lógico
>>> power = 10
>>> signal_4g = 60
Dado que estamos en un and y la primera condición power > 25 no se cumple, se produce
un cortocircuito y no se sigue evaluando el resto de la expresión porque ya se sabe que va
a dar False.
Otro ejemplo. Para poder hacer una llamada VoIP necesitamos tener al menos un 40%
de batería o al menos un 30% de cobertura:
>>> power = 50
>>> signal_4g = 20
«Booleanos» en condiciones
>>> if is_cold:
... print(Coge chaqueta)
... else:
... print(Usa camiseta)
...
Coge chaqueta
Hemos visto una comparación para un valor «booleano» verdadero (True). En el caso de que
la comparación uera para un valor also lo haríamos así:
Ejercicio
pycheck: marvel_akinator
Valor nulo
Nivel intermedio
None es un valor especial de Python que almacena el valor nulo4 . Veamos cómo se comporta
al incorporarlo en condiciones de veracidad:
>>> if value:
... print(Value has some useful value)
... else:
... # value podría contener None, False (u otro)
... print(Value seems to be void)
...
Value seems to be void
Para distinguir None de los valores propiamente booleanos, se recomienda el uso del operador
is. Veamos un ejemplo en el que tratamos de averiguar si un valor es nulo:
De igual orma, podemos usar esta construcción para el caso contrario. La orma «pitónica»
de preguntar si algo no es nulo es la siguiente:
>>> value = 99
Nivel avanzado
Cabe preguntarse por qué utilizamos is en vez del operador == al comprobar si un valor es
nulo, ya que ambas aproximaciones nos dan el mismo resultado7 :
4
Lo que en otros lenguajes se conoce como nil, null, nothing.
7
Uso de is en comparación de valores nulos explicada aquí por Jared Grubb.
>>> id(None)
4314501456
Cualquier variable que igualemos al valor nulo, únicamente será una reerencia al mismo
objeto None en memoria:
>>> id(value)
4314501456
Por lo tanto, ver si un objeto es None es simplemente comprobar que su identicador coincida
con el de None, que es exactamente el cometido de la unción is():
Truco: Python carga inicialmente en memoria objetos como True o False, pero también
los números enteros que van desde el -5 hasta el 256. Se entiende que tiene que ver con
optimizaciones a nivel de rendimiento.
4.1.8 Veracidad
Nivel intermedio
Cuando trabajamos con expresiones que incorporan valores booleanos, se produce una
conversión implícita que transorma los tipos de datos involucrados a valores True o False.
Lo primero que debemos entender de cara comprobar la veracidad son los valores que
evalúan a also o evalúan a verdadero.
Veamos las únicas «cosas» que son evaluadas a False en Python:
>>> bool(False)
False
>>> bool(None)
False
>>> bool(0)
False
>>> bool(0.0)
False
>>> bool(False)
True
>>> bool(1e-10)
True
>>> bool([0])
True
>>> bool( )
True
Asignación lógica
>>> b = 0
>>> c = 5
>>> a = b or c
>>> a
5
En la línea resaltada podemos ver que se está aplicando una expresión lógica, por lo tanto
se aplica una conversión implícita de los valores enteros a valores «booleanos». En este sentido
el valor 0 se evalúa a also y el valor 5 se evalúa a verdadero. Como estamos en un or el
resultado será verdadero, que en este caso es el valor 5 asignado nalmente a la variable a.
Veamos el mismo ejemplo de antes pero utilizando el operador and:
>>> b = 0
>>> c = 5
>>> a = b and c
>>> a
0
En este caso, como estamos en un and el resultado será also, por lo que el valor 0 es asignado
nalmente a la variable a.
Una de las novedades más esperadas (y quizás controvertidas) de Python 3.10 ue el
llamado Structural Pattern Matching que introdujo en el lenguaje una nueva sentencia
condicional. Ésta se podría asemejar a la sentencia «switch» que ya existe en otros lenguajes
de programación.
Comparando valores
En su versión más simple, el «pattern matching» permite comparar un valor de entrada con
una serie de literales. Algo así como un conjunto de sentencias «i» encadenadas. Veamos
esta aproximación mediante un ejemplo:
¿Qué ocurre si el valor que comparamos no existe entre las opciones disponibles? Pues en
principio, nada, ya que este caso no está cubierto. Si lo queremos controlar, hay que añadir
una nueva regla utilizando el subguión _ como patrón:
Ejercicio
pycheck: simple_op
Patrones avanzados
Nivel avanzado
La sentencia match-case va mucho más allá de una simple comparación de valores. Con ella
podremos deconstruir estructuras de datos, capturar elementos o mapear valores.
Para ejemplicar varias de sus uncionalidades, vamos a partir de una tupla que representará
un punto en el plano (2 coordenadas) o en el espacio (3 coordenadas). Lo primero que vamos
a hacer es detectar en qué dimensión se encuentra el punto:
Por lo tanto, en un siguiente paso, podemos restringir nuestros patrones a valores enteros:
Imaginemos ahora que nos piden calcular la distancia del punto al origen. Debemos tener en
cuenta que, a priori, desconocemos si el punto está en el plano o en el espacio:
>>> dist_to_origin
9.899494936611665
Con este enoque, nos aseguramos que los puntos de entrada deben tener todas sus
coordenadas como valores enteros:
Cambiando de ejemplo, a continuación veremos un código que nos indica si, dada la edad de
una persona, puede beber alcohol:
1 >>> age = 21
2
Nivel avanzado
A partir de Python 3.8 se incorpora el operador morsa5 que permite unicar sentencias de
asignación dentro de expresiones. Su nombre proviene de la orma que adquiere :=
Supongamos un ejemplo en el que computamos el perímetro de una circunerencia, indicando
al usuario que debe incrementarlo siempre y cuando no llegue a un mínimo establecido.
Versión tradicional
Consejo: Como hemos comprobado, el operador morsa permite realizar asignaciones dentro
de expresiones, lo que, en muchas ocasiones, permite obtener un código más compacto. Sería
conveniente encontrar un equilibrio entre la expresividad y la legibilidad.
EJERCICIOS DE REPASO
1. pycheck: rps
2. pycheck: min3values
3. pycheck: blood_donation
4. pycheck: acemoji
5. pycheck: shortcuts
EJERCICIOS EXTERNOS
6. Simple multiplication
7. Quarter o the year
8. Grade book
9. Transportation on vacation
10. Saen User Input Part I - htmlspecialchars
11. Remove an exclamation mark rom the end o string
12. Pythagorean triple
13. How much water do I need?
14. Set Alarm
15. Compare within margin
16. Will you make it?
17. Plural
18. Student’s nal grade
19. Drink about
20. Switch it up!
21. Floating point comparison
22. No zeros or heros
23. Tip calculator
24. Grader
25. Evil or Odious
26. Validate code with simple regex
27. Fuel calculator
AMPLIAR CONOCIMIENTOS
4.2 Bucles
Cuando queremos hacer algo más de una vez, necesitamos recurrir a un bucle. En esta
sección veremos las distintas sentencias en Python que nos permiten repetir un bloque de
código.1
El primer mecanismo que existe en Python para repetir instrucciones es usar la sentencia
while. La semántica tras esta sentencia es: «Mientras se cumpla la condición haz algo».
Veamos un sencillo bucle que repite un saludo mientras así se desee:
>>> MAX_GREETS = 4
>>> num_greets = 0
>>> want_greet = S
Como hemos visto en este ejemplo, break nos permite nalizar el bucle una vez que hemos
llegado al máximo número de saludos. Pero si no hubiéramos llegado a dicho límite, el bucle
habría seguido hasta que el usuario indicara que no quiere más saludos.
Otra orma de resolver este ejercicio sería incorporar una condición al bucle:
Comprobar la rotura
Nivel intermedio
Python nos orece la posibilidad de detectar si el bucle ha acabado de orma ordinaria,
esto es, ha nalizado por no cumplirse la condición establecida. Para ello podemos hacer uso
de la sentencia else como parte del propio bucle. Si el bucle while naliza normalmente (sin
llamada a break) el fujo de control pasa a la sentencia opcional else.
Veamos su comportamiento siguiendo con el ejemplo que venimos trabajando:
>>> MAX_GREETS = 4
>>> num_greets = 0
>>> want_greet = S
Continuar un bucle
Nivel intermedio
Hay situaciones en las que, en vez de romper un bucle, nos interesa saltar adelante hacia
la siguiente repetición. Para ello Python nos orece la sentencia continue que hace
precisamente eso, descartar el resto del código del bucle y saltar a la siguiente iteración.
Continuamos con el ejemplo anterior y vamos a contar el número de respuestas válidas:
Bucle infinito
El problema que surje es que la variable num toma los valores 1, 3, 5, 7, 9, 11, ..
. por lo que nunca se cumple la condición de parada del bucle. Esto hace que repitamos
«eternamente» la instrucción de incremento.
Ejecución paso a paso a través de Python Tutor:
https://cutt.ly/ArZroa
Una posible solución a este error es reescribir la condición de parada en el bucle:
>>> num = 1
Truco: Para abortar una situación de bucle innito podemos pulsar en el teclado
la combinación CTRL-C. Se puede ver refejado en el intérprete de Python por
KeyboardInterrupt.
Hay veces que un supuesto bucle «innito» puede ayudarnos a resolver un problema.
Imaginemos que queremos escribir un programa que ayude al proesorado a introducir las
notas de un examen. Si la nota no está en el intervalo [0, 10] mostramos un mensaje de error,
en otro caso seguimos pidiendo valores:
>>> while True:
... mark = float(input(Introduzca nueva nota: ))
... if not(0 <= mark <= 10):
... print(Nota fuera de rango)
... break
(continué en la próxima página)
>>> while 0 <= (mark := float(input(Introduzca una nueva nota: ))) <= 10:
... print(mark)
... print(Nota fuera de rango)
Introduzca una nueva nota: 5
5.0
Introduzca una nueva nota: 3
3.0
Introduzca una nueva nota: 11
Nota fuera de rango
Ejercicio
Escriba un programa que encuentre todos los múltiplos de 5 menores que un valor dado:
Ejemplo
• Entrada: 36
• Salida: 5 10 15 20 25 30 35
Python permite recorrer aquellos tipos de datos que sean iterables, es decir, que admitan
iterar 2 sobre ellos. Algunos ejemplos de tipos y estructuras de datos que permiten ser iteradas
(recorridas) son: cadenas de texto, listas, diccionarios, cheros, etc. La sentencia for nos
permite realizar esta acción.
A continuación se plantea un ejemplo en el que vamos a recorrer (iterar) una cadena de
texto:
2
Realizar cierta acción varias veces. En este caso la acción es tomar cada elemento.
La clave aquí está en darse cuenta que el bucle va tomando, en cada iteración, cada uno de
los elementos de la variable que especiquemos. En este caso concreto letter va tomando
cada una de las letras que existen en word, porque una cadena de texto está ormada por
elementos que son caracteres.
Ejecución paso a paso a través de Python Tutor:
https://cutt.ly/Pt6R2e
Importante: La variable que utilizamos en el bucle for para ir tomando los valores puede
tener cualquier nombre. Al n y al cabo es una variable que denimos según nuestras
necesidades. Tener en cuenta que se suele usar un nombre en singular.
Una sentencia break dentro de un for rompe el bucle, igual que veíamos para los bucles
while. Veamos un ejemplo con el código anterior. En este caso vamos a recorrer una cadena
de texto y pararemos el bucle cuando encontremos una letra t minúscula:
Ejercicio
pycheck: count_vowels
Secuencias de números
Es muy habitual hacer uso de secuencias de números en bucles. Python no tiene una
instrucción especíca para ello. Lo que sí aporta es una unción range() que devuelve un
fujo de números en el rango especicado. Una de las grandes ventajas es que la «lista»
generada no se construye explícitamente, sino que cada valor se genera bajo demanda. Esta
técnica mejora el consumo de recursos, especialmente en términos de memoria.
La técnica para la generación de secuencias de números es muy similar a la utilizada en los
«slices» de cadenas de texto. En este caso disponemos de la unción range(start, stop,
step):
• start: Es opcional y tiene valor por deecto 0.
• stop: es obligatorio (siempre se llega a 1 menos que este valor).
• step: es opcional y tiene valor por deecto 1.
range() devuelve un objeto iterable, así que iremos obteniendo los valores paso a paso con
una sentencia for ... in3 . Veamos dierentes ejemplos de uso:
Rango: [0, 1, 2]
3
O convertir el objeto a una secuencia como una lista.
Rango: [1, 3, 5]
Rango: [2, 1, 0]
Ejercicio
pycheck: prime
Nivel avanzado
Hay situaciones en las que no necesitamos usar la variable que toma valores en el rango,
sino que únicamente queremos repetir una acción un número determinado de veces.
Para estos casos se suele recomendar usar el guión bajo _ como nombre de variable, que
da a entender que no estamos usando esta variable de orma explícita:
Ejercicio
Imprima los 100 primeros números de la sucesión de Fibonacci:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, . . .
Como ya vimos en las sentencias condicionales, el anidamiento es una técnica por la que
incluimos distintos niveles de encapsulamiento de sentencias, unas dentro de otras, con mayor
nivel de proundidad. En el caso de los bucles también es posible hacer anidamiento.
Veamos un ejemplo de 2 bucles anidados en el que generamos todas las tablas de multiplicar:
Lo que está ocurriendo en este código es que, para cada valor que toma la variable i, la otra
variable j toma todos sus valores. Como resultado tenemos una combinación completa de
los valores en el rango especicado.
Ejecución paso a paso a través de Python Tutor:
https://cutt.ly/pwwtctK6
Nota:
• Podemos añadir todos los niveles de anidamiento que queramos. Eso sí, hay que tener
en cuenta que cada nuevo nivel de anidamiento supone un importante aumento de la
complejidad ciclomática de nuestro código, lo que se traduce en mayores tiempos de
ejecución.
• Los bucles anidados también se pueden aplicar en la sentencia while.
Ejercicio
Dado su tamaño, muestre por pantalla un mosaico donde la diagonal principal esté
representada por X, la parte inerior por D y la parte superior por U.
Ejemplo
• Entrada: 5
• Salida:
X U U U U
D X U U U
D D X U U
D D D X U
D D D D X
EJERCICIOS DE REPASO
• Entrada: 45
• Salida: 0, 3, 6, 9, 12, 15
2. Escriba un programa que pida nombre y apellidos de una persona (usando un solo
input) y repita la pregunta mientras el nombre no esté en ormato título (solución).
¿Su nombre? ana torres blanco
Error. Debe escribirlo correctamente
¿Su nombre? Ana torres blanco
Error. Debe escribirlo correctamente
¿Su nombre? Ana Torres blanco
Error. Debe escribirlo correctamente
¿Su nombre? Ana Torres Blanco
7. Escriba un programa que muestre (por las) la Tabla ASCII, empezando con el código
33 y terminando con el 127 (solución):
033 ! 034 " 035 # 036 $ 037 %
038 & 039 040 ( 041 ) 042 *
043 + 044 , 045 - 046 . 047 /
048 0 049 1 050 2 051 3 052 4
053 5 054 6 055 7 056 8 057 9
058 : 059 ; 060 < 061 = 062 >
063 ? 064 @ 065 A 066 B 067 C
068 D 069 E 070 F 071 G 072 H
073 I 074 J 075 K 076 L 077 M
078 N 079 O 080 P 081 Q 082 R
083 S 084 T 085 U 086 V 087 W
088 X 089 Y 090 Z 091 [ 092 \
093 ] 094 ^ 095 _ 096 097 a
098 b 099 c 100 d 101 e 102 f
103 g 104 h 105 i 106 j 107 k
108 l 109 m 110 n 111 o 112 p
113 q 114 r 115 s 116 t 117 u
118 v 119 w 120 x 121 y 122 z
123 { 124 | 125 } 126 ~ 127
9. pycheck: gcd
10. pycheck: hamming
11. pycheck: sprod_cart
12. pycheck: cumsq_prod
13. pycheck: isalphabetic
14. pycheck: tennis_game
15. pycheck: tennis_set
16. pycheck: kpower
EJERCICIOS EXTERNOS
1. Summation
2. Find nearest square number
3. Bin to decimal
4. altERnaTIng cAsE
5. Fake binary
6. Correct the mistakes o the character recognition sotware
7. String cleaning
8. Sum o multiples
9. ASCII Total
10. Collatz Conjecture (3n+1)
AMPLIAR CONOCIMIENTOS
Estructuras de datos
Si bien ya hemos visto una sección sobre Tipos de datos, podríamos hablar de tipos de datos
más complejos en Python que se constituyen en estructuras de datos. Si pensamos en
estos elementos como átomos, las estructuras de datos que vamos a ver sería moléculas. Es
decir, combinamos los tipos básicos de ormas más complejas. De hecho, esta distinción se
hace en el Tutorial ocial de Python. Trataremos distintas estructuras de datos como listas,
tuplas, diccionarios y conjuntos.
143
Aprende Python
5.1 Listas
Las listas permiten almacenar objetos mediante un orden denido y con posibilidad
de duplicados. Las listas son estructuras de datos mutables, lo que signica que podemos
añadir, eliminar o modicar sus elementos.1
Una lista está compuesta por cero o más elementos. En Python debemos escribir estos
elementos separados por comas y dentro de corchetes. Veamos algunos ejemplos de listas:
>>> empty_list = []
>>> data = [Tenerife, {cielo: limpio, temp: 24}, 3718, (28.2933947, -16.
˓→5226597)]
Nota: Una lista puede contener tipos de datos heterogéneos, lo que la hace una estructura
1
Foto original de portada por Mike Arney en Unsplash.
Advertencia: Aunque está permitido, NUNCA llames list a una variable porque
destruirías la unción que nos permite crear listas. Y tampoco uses nombres derivados
como _list o list_ ya que no son nombres representativos que identiquen el propósito
de la variable.
Ejercicio
Entre en el intérprete interactivo de Python (>>>) y cree una lista con las 5 ciudades que
más le gusten.
5.1.2 Conversión
Para convertir otros tipos de datos en una lista podemos usar la unción list():
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Lista vacía
Existe una manera particular de usar list() y es no pasarle ningún argumento. En este
caso estaremos queriendo convertir el «vacío» en una lista, con lo que obtendremos una lista
vacía:
>>> list()
[]
Truco: Para crear una lista vacía, se suele recomendar el uso de [] rente a list(), no
sólo por ser más pitónico sino por tener (en promedio) un mejor rendimiento en tiempos de
ejecución.
Obtener un elemento
Igual que en el caso de las cadenas de texto, podemos obtener un elemento de una lista a
través del índice (lugar) que ocupa. Veamos un ejemplo:
>>> shopping = [Agua, Huevos, Aceite]
>>> shopping[0]
Agua
>>> shopping[1]
Huevos
>>> shopping[2]
Aceite
El índice que usemos para acceder a los elementos de una lista tiene que estar comprendido
entre los límites de la misma. Si usamos un índice antes del comienzo o después del nal
obtendremos un error (excepción):
>>> shopping = [Agua, Huevos, Aceite]
>>> shopping[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
(continué en la próxima página)
>>> shopping[-5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> shopping[0:3]
[Agua, Huevos, Aceite]
>>> shopping[:3]
[Agua, Huevos, Aceite]
>>> shopping[2:4]
[Aceite, Sal]
>>> shopping[-1:-4:-1]
[Limón, Sal, Aceite]
>>> shopping
[Agua, Huevos, Aceite, Sal, Limón]
>>> shopping[10:]
[]
>>> shopping[-100:2]
[Agua, Huevos]
Python nos orece, al menos, tres mecanismos para invertir los elementos de una lista:
Conservando la lista original: Opción 1: Mediante troceado de listas con step negativo:
>>> shopping
[Agua, Huevos, Aceite, Sal, Limón]
>>> shopping[::-1]
[Limón, Sal, Aceite, Huevos, Agua]
>>> shopping
[Agua, Huevos, Aceite, Sal, Limón]
>>> list(reversed(shopping))
[Limón, Sal, Aceite, Huevos, Agua]
Modicando la lista original: Utilizando la unción reverse() (nótese que es sin «d» al
nal):
>>> shopping
[Agua, Huevos, Aceite, Sal, Limón]
>>> shopping.reverse()
>>> shopping
[Limón, Sal, Aceite, Huevos, Agua]
Una de las operaciones más utilizadas en listas es añadir elementos al nal de las mismas.
Para ello Python nos orece la unción append(). Se trata de un método «destructivo» que
modica la lista original:
>>> shopping.append(Atún)
>>> shopping
[Agua, Huevos, Aceite, Atún]
Una orma muy habitual de trabajar con listas es empezar con una vacía e ir añadiendo
elementos poco a poco. Se podría hablar de un patrón creación.
Supongamos un ejemplo en el que queremos construir una lista con los números pares del
[0, 20):
>>> even_numbers = []
>>> even_numbers
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Ya hemos visto cómo añadir elementos al nal de una lista. Sin embargo, Python orece
una unción insert() que vendría a ser una generalización de la anterior, para incorporar
elementos en cualquier posición. Simplemente debemos especicar el índice de inserción y el
elemento en cuestión. También se trata de una unción destructiva 2 :
2
Cuando hablamos de que una unción/método es «destructiva/o» signica que modica la lista (objeto)
original, no que la destruye.
>>> shopping
[Agua, Jamón, Huevos, Aceite]
>>> shopping
[Agua, Jamón, Huevos, Queso, Aceite]
Al igual que ocurría con el troceado de listas, en este tipo de inserciones no obtendremos un
error si especicamos índices uera de los límites de la lista. Estos se ajustarán al principio
o al nal en unción del valor que indiquemos:
>>> shopping = [Agua, Huevos, Aceite]
>>> shopping
[Agua, Huevos, Aceite, Mermelada]
>>> shopping
[Arroz, Agua, Huevos, Aceite, Mermelada]
Consejo: Aunque es posible utilizar insert() para añadir elementos al nal de una
lista, siempre se recomienda usar append() por su mayor legibilidad:
>>> values = [1, 2, 3]
>>> values.append(4)
>>> values
[1, 2, 3, 4]
Repetir elementos
Al igual que con las cadenas de texto, el operador * nos permite repetir los elementos de una
lista:
>>> shopping = [Agua, Huevos, Aceite]
>>> shopping * 3
[Agua,
Huevos,
Aceite,
Agua,
Huevos,
Aceite,
Agua,
Huevos,
Aceite]
Combinar listas
>>> shopping.extend(fruitshop)
>>> shopping
[Agua, Huevos, Aceite, Naranja, Manzana, Piña]
Hay que tener en cuenta que extend() unciona adecuadamente si pasamos una lista como
argumento. En otro caso, quizás los resultados no sean los esperados. Veamos un ejemplo:
>>> shopping = [Agua, Huevos, Aceite]
>>> shopping.extend(Limón)
(continué en la próxima página)
>>> shopping
[Agua, Huevos, Aceite, L, i, m, ó, n]
El motivo es que extend() «recorre» (o itera) sobre cada uno de los elementos del objeto
en cuestión. En el caso anterior, al ser una cadena de texto, está ormada por caracteres. De
ahí el resultado que obtenemos.
Se podría pensar en el uso de append() para combinar listas. La realidad es que no unciona
exactamente como esperamos; la segunda lista se añadiría como una sublista de la principal:
>>> shopping.append(fruitshop)
>>> shopping
[Agua, Huevos, Aceite, [Naranja, Manzana, Piña]]
Del mismo modo que se accede a un elemento utilizando su índice, también podemos
modicarlo:
>>> shopping[0]
Agua
>>> shopping
[Jugo, Huevos, Aceite]
No sólo es posible modicar un elemento de cada vez, sino que podemos asignar valores a
trozos de una lista:
>>> shopping[1:4]
[Huevos, Aceite, Sal]
>>> shopping
[Agua, Atún, Pasta, Limón]
Nota: La lista que asignamos no necesariamente debe tener la misma longitud que el trozo
que sustituimos.
Borrar elementos
Python nos orece, al menos, cuatro ormas para borrar elementos en una lista:
Por su índice: Mediante la sentencia del:
>>> shopping
[Agua, Huevos, Aceite, Limón]
>>> shopping.remove(Sal)
>>> shopping
[Agua, Huevos, Aceite, Limón]
>>> shopping
[Agua, Huevos, Aceite, Sal]
>>> shopping
[Agua, Huevos, Sal]
Nota: Si usamos la unción pop() sin pasarle ningún argumento, por deecto usará
el índice -1, es decir, el último elemento de la lista. Pero también podemos indicarle el
índice del elemento a extraer.
>>> shopping[1:4] = []
>>> shopping
[Agua, Limón]
3
Más adelante veremos el comportamiento de las unciones. Devolver o retornar un valor es el resultado
de aplicar una unción.
Python nos orece, al menos, dos ormas para borrar una lista por completo:
1. Utilizando la unción clear():
>>> shopping
[]
>>> shopping
[]
Nivel avanzado
La dierencia entre ambos métodos tiene que ver con cuestiones internas de gestión de
memoria y de rendimiento:
Ver también:
La memoria que queda «en el limbo» después de asignar un nuevo valor a la lista es detectada
por el recolector de basura de Python, quien se encarga de liberar aquellos datos que no
están reerenciados por ninguna variable.
A eectos de velocidad de ejecución, shopping.clear() «parece» ir más rápido que
shopping = [].
Encontrar un elemento
>>> shopping.index(Huevos)
1
Tener en cuenta que si el elemento que buscamos no está en la lista, obtendremos un error:
>>> shopping.index(Pollo)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Pollo is not in list
Nota: Si buscamos un valor que existe más de una vez en una lista, la unción index() sólo
nos devolverá el índice de la primera ocurrencia.
Pertenencia de un elemento
Ejercicio
pycheck: isogram
Número de ocurrencias
Para contar cuántas veces aparece un determinado valor dentro de una lista podemos usar
la unción count():
>>> sheldon_greeting = [Penny, Penny, Penny]
>>> sheldon_greeting.count(Howard)
0
>>> sheldon_greeting.count(Penny)
3
Una tarea muy común al trabajar con cadenas de texto es dividirlas por algún tipo
de separador. En este sentido, Python nos orece la unción split(), que debemos usar
anteponiendo el «string» que queramos dividir:
>>> proverb = No hay mal que por bien no venga
>>> proverb.split()
[No, hay, mal, que, por, bien, no, venga]
La unción split() devuelve una lista donde cada elemento es una parte de la cadena de
texto original:
>>> game = piedra-papel-tijera
>>> game_tools
[piedra, papel, tijera]
Ejercicio
pycheck: num_words
Existe una orma algo más «elaborada» de dividir una cadena a través del particionado.
Para ello podemos valernos de la unción partition() que proporciona Python.
Esta unción toma un argumento como separador, y divide la cadena de texto en 3 partes: lo
que queda a la izquierda del separador, el separador en sí mismo y lo que queda a la derecha
del separador:
>>> text = 3 + 4
>>> text.partition(+)
(3 , +, 4)
Ver también:
En este caso el resultado de la unción partition() es una tupla.
Dada una lista, podemos convetirla a una cadena de texto, uniendo todos sus elementos
mediante algún separador. Para ello hacemos uso de la unción join() con la siguiente
estructura:
>>> ,.join(shopping)
Agua,Huevos,Aceite,Sal,Limón
>>> .join(shopping)
Agua Huevos Aceite Sal Limón
>>> |.join(shopping)
Agua|Huevos|Aceite|Sal|Limón
Hay que tener en cuenta que join() sólo unciona si todos sus elementos son cadenas de
texto:
Ejercicio
pycheck: xdate
Python proporciona, al menos, dos ormas de ordenar los elementos de una lista:
Conservando lista original: Mediante la unción sorted() que devuelve una nueva lista
ordenada:
>>> sorted(shopping)
[Aceite, Agua, Huevos, Limón, Sal]
>>> shopping
[Aceite, Agua, Huevos, Limón, Sal]
Ambos métodos admiten un parámetro «booleano» reverse para indicar si queremos que
la ordenación se haga en sentido inverso:
Podemos conocer el número de elementos que tiene una lista con la unción len():
>>> len(shopping)
5
Al igual que hemos visto con las cadenas de texto, también podemos iterar sobre los elementos
de una lista utilizando la sentencia for:
Nota: También es posible usar la sentencia break en este tipo de bucles para abortar su
ejecución en algún momento que nos interese.
Ejercicio
pycheck: chars_list
Hay veces que no sólo nos interesa «visitar» cada uno de los elementos de una lista, sino
que también queremos saber su índice dentro de la misma. Para ello Python nos orece la
unción enumerate():
Truco: Es posible utilizar el parámetro start con enumerate() para indicar el índice en el
que queremos comenzar. Por deecto es 0.
Nota: En el caso de que las listas no tengan la misma longitud, la unción zip() realiza la
combinación hasta que se agota la lista más corta.
Dado que zip() produce un iterador, si queremos obtener una lista explícita con la
combinación en paralelo de las listas, debemos construir dicha lista de la siguiente manera:
Ejercicio
pycheck: dot_product
Comparar listas
Python llega a la conclusión de que la lista [1, 2, 3] es menor que [1, 2, 4] porque va
comparando elemento a elemento:
• El 1 es igual en ambas listas.
• El 2 es igual en ambas litas.
• El 3 es menor que el 4, por lo que la primera lista es menor que la segunda.
Entender la orma en la que se comparan dos listas es importante para poder aplicar otras
unciones y obtener los resultados deseados.
Ver también:
Esta comparación unciona de orma totalmente análoga a la comparación de cadenas de
texto.
Nivel intermedio
Las listas son estructuras de datos mutables y esta característica nos obliga a tener cuidado
cuando realizamos copias de listas, ya que la modicación de una de ellas puede aectar a la
otra.
Veamos un ejemplo sencillo:
>>> original_list[0] = 15
>>> original_list
[15, 3, 7, 1]
>>> copy_list
[15, 3, 7, 1]
Nota: A través de Python Tutor se puede ver claramente el motivo de por qué ocurre esto.
Dado que las variables «apuntan» a la misma zona de memoria, al modicar una de ellas, el
cambio también se ve refejado en la otra.
Una posible solución a este problema es hacer una «copia dura». Para ello Python
proporciona la unción copy():
>>> original_list[0] = 15
>>> original_list
(continué en la próxima página)
>>> copy_list
[4, 3, 7, 1]
Truco: En el caso de que estemos trabajando con listas que contienen elementos mutables,
debemos hacer uso de la unción deepcopy() dentro del módulo copy de la librería estándar.
Veamos la versión con veracidad múltiple usando all(), donde se comprueba que se
cumplan todas las expresiones:
>>> if is_cool_word:
... print(Cool word!)
... else:
... print(No thanks)
...
Cool word!
Veamos la versión con veracidad múltiple usando any(), donde se comprueba que se
cumpla alguna expresión:
>>> if is_fine_word:
... print(Fine word!)
... else:
... print(No thanks)
...
Fine word!
Consejo: Este enoque puede ser interesante cuando se manejan muchas condiciones o bien
cuando queremos separar las condiciones y agruparlas en una única lista.
A tener en cuenta la peculiaridad de estas unciones cuando trabajan con la lista vacía:
>>> all([])
True
>>> any([])
False
Nivel intermedio
Las listas por comprensión establecen una técnica para crear listas de orma más
compacta basándose en el concepto matemático de conjuntos denidos por comprensión.
Podríamos decir que su sintaxis sigue un modelo VLC (Value-Loop-Condition) tal y
como se muestra en la siguiente gura:
En primer lugar veamos un ejemplo en el que convertimos una cadena de texto con valores
numéricos en una lista con los mismos valores pero convertidos a enteros. En su versión
clásica haríamos algo tal que así:
>>> int_values = []
>>> int_values
[32, 45, 11, 87, 20, 48]
>>> int_values
[32, 45, 11, 87, 20, 48]
Condiciones en comprensiones
>>> int_values
[45, 48]
Anidamiento en comprensiones
Nivel avanzado
En la iteración que usamos dentro de la lista por comprensión es posible usar bucles anidados.
Veamos un ejemplo en el que generamos todas las combinaciones de una serie de valores:
>>> combinations
[32x32,
32x45,
32x11,
32x87,
32x20,
32x48,
45x32,
45x45,
...
48x45,
48x11,
48x87,
48x20,
48x48]
Consejo: Las listas por comprensión son una herramienta muy potente y nos ayuda en
muchas ocasiones, pero hay que tener cuidado de no generar expresiones excesivamente
complejas. En estos casos es mejor una aproximación clásica.
Ejercicio
pycheck: comp
5.1.7 sys.argv
1 import sys
2
3 number = int(sys.argv[1])
4 tobase = int(sys.argv[2])
(continué en la próxima página)
6 match tobase:
7 case 2:
8 result = f{number:b}
9 case 8:
10 result = f{number:o}
11 case 16:
12 result = f{number:x}
13 case _:
14 result = None
15
16 if result is None:
17 print(fBase {tobase} not implemented!)
18 else:
19 print(result)
Python nos orece, entre otras4 , estas tres unciones matemáticas básicas que se pueden
aplicar sobre listas.
Suma de todos los valores: Mediante la unción sum():
Ejercicio
Lea desde línea de comandos una serie de números y obtenga la media de dichos valores
(redondeando a 2 ciras decimales).
La llamada se haría de la siguiente manera:
$ python avg.py 32 56 21 99 12 21
import sys
Ejemplo
• Entrada: 32 56 21 99 12 17
• Salida: 40.17
4
Existen multitud de paquetes cientícos en Python para trabajar con listas o vectores numéricos. Una
de las más amosas es la librería Numpy.
Nivel intermedio
Como ya hemos visto en varias ocasiones, las listas son estructuras de datos que pueden
contener elementos heterogéneos. Estos elementos pueden ser a su vez listas.
A continuación planteamos un ejemplo del contexto deportivo. Un equipo de útbol suele
tener una disposición en el campo organizada en líneas de jugadores/as. En aquella alineación
con la que España ganó la copa del mundo en 2023 había una disposición 4-3-3 con las
siguientes jugadoras:
Veamos una posible representación de este equipo de útbol usando una lista compuesta
de listas. Primero denimos cada una de las líneas:
>>> team
[Cata,
[Olga, Laia, Irene, Ona],
[Jenni, Teresa, Aitana],
[Mariona, Salma, Alba]]
Ejercicio
pycheck: mulmatrix2
Ejercicio
pycheck: mulmatrix
EJERCICIOS DE REPASO
1. pycheck: max_value
2. pycheck: max_value_with_min
3. pycheck: remove_dups
4. pycheck: fatten_list
5. pycheck: remove_consecutive_dups
6. pycheck: all_same
7. pycheck: sum_diagonal
8. pycheck: powers2
9. pycheck: dec2bin
10. pycheck: sum_mixed
11. pycheck: n_mult
12. pycheck: remove_second
13. pycheck: nth_power
14. pycheck: name_initials
15. pycheck: non_consecutive
16. pycheck: mult_reduce
17. pycheck: digit_rev_list
18. pycheck: time_plus_minutes
19. pycheck: sum_positive
20. pycheck: add_inverse
21. pycheck: descending_numbers
22. pycheck: merge_sorted
23. pycheck: min_value
24. pycheck: min_value_with_max
25. pycheck: trimmed_sum
AMPLIAR CONOCIMIENTOS
5.2 Tuplas
El concepto de tupla es muy similar al de lista. Aunque hay algunas dierencias menores, lo
undamental es que, mientras una lista es mutable y se puede modicar, una tupla no admite
cambios y por lo tanto, es inmutable.1
Podemos pensar en crear tuplas tal y como lo hacíamos con listas, pero usando paréntesis
en lugar de corchetes:
>>> empty_tuple = ()
Truco: Al igual que con las listas, las tuplas admiten dierentes tipos de datos: (a, 1,
True)
1
Foto original de portada por engin akyurt en Unsplash.
Tuplas de un elemento
Hay que prestar especial atención cuando vamos a crear una tupla de un único elemento.
La intención primera sería hacerlo de la siguiente manera:
>>> one_item_tuple
Papá Noel
>>> type(one_item_tuple)
str
Realmente, hemos creado una variable de tipo str (cadena de texto). Para crear una tupla
de un elemento debemos añadir una coma al nal:
>>> one_item_tuple
(Papá Noel,)
>>> type(one_item_tuple)
tuple
Según el caso, hay veces que nos podemos encontrar con tuplas que no llevan paréntesis.
Quizás no está tan extendido, pero a eectos prácticos tiene el mismo resultado. Veamos
algunos ejemplos de ello:
Advertencia: Aunque está permitido, NUNCA llames tuple a una variable porque
destruirías la unción que nos permite crear tuplas. Y tampoco uses nombres derivados
como _tuple o tuple_ ya que no son nombres representativos que identiquen el propósito
de la variable.
Como ya hemos comentado previamente, las tuplas con estructuras de datos inmutables.
Una vez que las creamos con un valor, no podemos modicarlas. Veamos qué ocurre si lo
intentamos:
5.2.3 Conversión
Para convertir otros tipos de datos en una tupla podemos usar la unción tuple():
>>> tuple(shopping)
(Agua, Aceite, Arroz)
Esta conversión es válida para aquellos tipos de datos que sean iterables: cadenas de
caracteres, listas, diccionarios, conjuntos, etc. Un ejemplo que no unciona es intentar
convertir un número en una tupla:
>>> tuple(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: int object is not iterable
El uso de la unción tuple() sin argumentos equivale a crear una tupla vacía:
>>> tuple()
()
Truco: Para crear una tupla vacía, se suele recomendar el uso de () rente a tuple(), no
sólo por ser más pitónico sino por tener (en promedio) un mejor rendimiento en tiempos de
ejecución.
Con las tuplas podemos realizar todas las operaciones que vimos con listas salvo las que
conlleven una modicación «in-situ» de la misma:
• reverse()
• append()
• extend()
• remove()
• clear()
• sort()
Truco: Sí es posible aplicar sorted() o reversed() sobre una tupla ya que no estamos
modicando su valor sino creando un nuevo objeto.
Ver también:
La comparación de tuplas unciona exactamente igual que la comparación de listas.
El desempaquetado es una característica de las tuplas que nos permite asignar una tupla
a variables independientes:
>>> king1
Melchor
>>> king2
Gaspar
>>> king3
Baltasar
>>> quotient
2
>>> remainder
1
Intercambio de valores
A través del desempaquetado de tuplas podemos llevar a cabo el intercambio de los valores
de dos variables de manera directa:
>>> value1 = 40
>>> value2 = 20
>>> value1
20
>>> value2
40
Nota: A priori puede parecer que esto es algo «natural», pero en la gran mayoría de lenguajes
de programación no es posible hacer este intercambio de orma «directa» ya que necesitamos
recurrir a una tercera variable «auxiliar» como almacén temporal en el paso intermedio de
traspaso de valores.
Desempaquetado extendido
No tenemos que ceñirnos a realizar desempaquetado uno a uno. También podemos extenderlo
e indicar ciertos «grupos» de elementos mediante el operador *.
Veamos un ejemplo:
>>> head
G
>>> body
[A, R, Y]
>>> tail
W
Podemos aplicar combinaciones del enoque anterior. Por ejemplo usando sólo dos elementos:
Lo que se tiene que cumplir es que el número de elementos de destino debe ser menor
o igual que el número de elementos de origen:
>>> ranking
(G, A, R, Y, W)
>>> len(ranking)
5
Desempaquetado genérico
El desempaquetado de tuplas es extensible a cualquier tipo de datos que sea iterable. Veamos
algunos ejemplos de ello.
Sobre cadenas de texto:
Sobre listas:
>>> writer1, writer2, writer3 = [Virginia Woolf, Jane Austen, Mary Shelley]
>>> writer1, writer2, writer3
(Virginia Woolf, Jane Austen, Mary Shelley)
Sin embargo no hemos conseguido una tupla por comprensión sino un generador:
>>> myrange
<generator object <genexpr> at 0x10b3732e0>
Aunque puedan parecer estructuras de datos muy similares, sabemos que las tuplas carecen de
ciertas operaciones, especialmente las que tienen que ver con la modicación de sus valores,
ya que no son inmutables. Si las listas son más fexibles y potentes, ¿por qué íbamos a
necesitar tuplas? Veamos 4 potenciales ventajas del uso de tuplas rente a las listas:
1. Las tuplas ocupan menos espacio en memoria.
2. En las tuplas existe protección rente a cambios indeseados.
3. Las tuplas se pueden usar como claves de diccionarios (son «hashables»).
4. Las namedtuples son una alternativa sencilla a los objetos.
5.3 Diccionarios
también un objeto indexado por claves (las palabras) que tienen asociados unos valores
(los signicados).1
• Tienen un acceso muy rápido a sus elementos, debido a la orma en la que están
implementados internamente.3
Nota: En otros lenguajes de programación, a los diccionarios se les conoce como arrays
asociativos, «hashes» o «hashmaps».
Para crear un diccionario usamos llaves {} rodeando asignaciones clave: valor que están
separadas por comas. Veamos algunos ejemplos de diccionarios:
>>> empty_dict = {}
>>> rae = {
... bifronte: De dos frentes o dos caras,
... anarcoide: Que tiende al desorden,
... montuvio: Campesino de la costa
... }
>>> population_can = {
... 2015: 2_135_209,
... 2016: 2_154_924,
... 2017: 2_177_048,
... 2018: 2_206_901,
... 2019: 2_220_270
... }
En el código anterior podemos observar la creación de un diccionario vacío, otro donde sus
claves y sus valores son cadenas de texto y otro donde las claves y los valores son valores
enteros.
Ejecución paso a paso a través de Python Tutor:
https://cutt.ly/Sav2Yw
Advertencia: Aunque está permitido, NUNCA llames dict a una variable porque
destruirías la unción que nos permite crear diccionarios. Y tampoco uses nombres
derivados como _dict o dict_ ya que no son nombres representativos que identiquen el
propósito de la variable.
Ejercicio
3
Véase este análisis de complejidad y rendimiento de distintas estructuras de datos en CPython.
Entre en el intérprete interactivo de Python (>>>) y cree un diccionario con los nombres
(como claves) de 5 personas de su amilia y sus edades (como valores).
5.3.2 Conversión
Para convertir otros tipos de datos en un diccionario podemos usar la unción dict():
Nota: Si nos jamos bien, cualquier iterable que tenga una estructura interna de 2 elementos
es susceptible de convertirse en un diccionario a través de la unción dict().
Diccionario vacío
Existe una manera particular de usar dict() y es no pasarle ningún argumento. En este
caso estaremos queriendo convertir el «vacío» en un diccionario, con lo que obtendremos un
diccionario vacío:
>>> dict()
{}
Truco: Para crear un diccionario vacío, se suele recomendar el uso de {} rente a dict(),
no sólo por ser más pitónico sino por tener (en promedio) un mejor rendimiento en tiempos
de ejecución.
También es posible utilizar la unción dict() para crear dicionarios y no tener que utilizar
llaves y comillas:
Supongamos que queremos transormar la siguiente tabla en un diccionario:
Atributo Valor
name Guido
surname Van Rossum
job Python creator
Utilizando la construcción mediante dict podemos pasar clave y valor como argumentos
de la unción:
>>> person
{name: Guido, surname: Van Rossum, job: Python creator}
El inconveniente que tiene esta aproximación es que las claves deben ser identicadores
válidos en Python. Por ejemplo, no se permiten espacios:
Nivel intermedio
Es posible crear un diccionario especicando sus claves y un único valor de «relleno»:
>>> dict.fromkeys(aeiou, 0)
{a: 0, e: 0, i: 0, o: 0, u: 0}
Obtener un elemento
Para obtener un elemento de un diccionario basta con escribir la clave entre corchetes.
Veamos un ejemplo:
>>> rae = {
... bifronte: De dos frentes o dos caras,
... anarcoide: Que tiende al desorden,
... montuvio: Campesino de la costa
... }
>>> rae[anarcoide]
Que tiende al desorden
>>> rae[acceso]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: acceso
Usando get()
Existe una unción muy útil para «superar» los posibles errores de acceso por claves
inexistentes. Se trata de get() y su comportamiento es el siguiente:
1. Si la clave que buscamos existe, nos devuelve su valor.
2. Si la clave que buscamos no existe, nos devuelve None4 salvo que le indiquemos otro
valor por deecto, pero en ninguno de los dos casos obtendremos un error.
1 >>> rae
2 {bifronte: De dos frentes o dos caras,
3 anarcoide: Que tiende al desorden,
4 montuvio: Campesino de la costa}
5
6 >>> rae.get(bifronte)
7 De dos frentes o dos caras
8
9 >>> rae.get(programación)
10
>>> rae = {
... bifronte: De dos frentes o dos caras,
... anarcoide: Que tiende al desorden,
... montuvio: Campesino de la costa
... }
>>> rae
{bifronte: De dos frentes o dos caras,
anarcoide: Que tiende al desorden,
montuvio: Campesino de la costa,
enjuiciar: Someter una cuestión a examen, discusión y juicio}
Supongamos ahora que queremos modicar el signicado de la palabra enjuiciar por otra
acepción:
5
Realmente no estamos viendo nada en la consola de Python porque la representación en cadena de texto
es vacía.
>>> rae
{bifronte: De dos frentes o dos caras,
anarcoide: Que tiende al desorden,
montuvio: Campesino de la costa,
enjuiciar: Instruir, juzgar o sentenciar una causa}
Una orma muy habitual de trabajar con diccionarios es utilizar el patrón creación
partiendo de uno vacío e ir añadiendo elementos poco a poco.
Supongamos un ejemplo en el que queremos construir un diccionario donde las claves son
las letras vocales y los valores son sus posiciones:
>>> enum_vowels = {}
>>> enum_vowels
{a: 1, e: 2, i: 3, o: 4, u: 5}
Nota: Hemos utilizando la unción enumerate() que ya vimos para las listas en el apartado:
Iterar usando enumeración.
Ejercicio
pycheck: cities
Ejercicio
pycheck: count_letters
Longitud de un diccionario
>>> rae
{bifronte: De dos frentes o dos caras,
anarcoide: Que tiende al desorden,
montuvio: Campesino de la costa,
enjuiciar: Instruir, juzgar o sentenciar una causa}
>>> len(rae)
4
Python orece mecanismos para obtener todos los elementos de un diccionario. Partimos del
siguiente diccionario:
>>> rae
{bifronte: De dos frentes o dos caras,
anarcoide: Que tiende al desorden,
montuvio: Campesino de la costa,
enjuiciar: Instruir, juzgar o sentenciar una causa}
>>> rae.keys()
dict_keys([bifronte, anarcoide, montuvio, enjuiciar])
>>> rae.values()
dict_values([
De dos frentes o dos caras,
Que tiende al desorden,
Campesino de la costa,
Instruir, juzgar o sentenciar una causa
])
>>> rae.items()
dict_items([
(bifronte, De dos frentes o dos caras),
(anarcoide, Que tiende al desorden),
(montuvio, Campesino de la costa),
(enjuiciar, Instruir, juzgar o sentenciar una causa)
])
Nota: Para este último caso cabe destacar que los «items» se devuelven como una lista
de tuplas, donde cada tupla tiene dos elementos: el primero representa la clave y el segundo
representa el valor.
En base a los elementos que podemos obtener, Python nos proporciona tres maneras de iterar
sobre un diccionario.
Iterar sobre claves:
Nota: En este último caso, recuerde el uso de los «-strings» para ormatear cadenas de
texto.
Ejercicio
pycheck: avg_population
Borrar elementos
Python nos orece, al menos, tres ormas para borrar elementos en un diccionario:
Por su clave: Mediante la sentencia del:
>>> rae = {
... bifronte: De dos frentes o dos caras,
... anarcoide: Que tiende al desorden,
... montuvio: Campesino de la costa
... }
>>> rae
{anarcoide: Que tiende al desorden, montuvio: Campesino de la costa}
Por su clave (con extracción): Mediante la unción pop() podemos extraer un elemento
del diccionario por su clave. Vendría a ser una combinación de get() + del:
>>> rae = {
... bifronte: De dos frentes o dos caras,
... anarcoide: Que tiende al desorden,
... montuvio: Campesino de la costa
... }
>>> rae.pop(anarcoide)
Que tiende al desorden
>>> rae
{bifronte: De dos frentes o dos caras, montuvio: Campesino de la costa}
>>> rae.pop(bucle)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: bucle
>>> rae.clear()
>>> rae
{}
>>> rae = {
... bifronte: De dos frentes o dos caras,
... anarcoide: Que tiende al desorden,
... montuvio: Campesino de la costa
... }
>>> rae = {}
>>> rae
{}
Ejercicio
pycheck: merge_dicts
Combinar diccionarios
Dados dos (o más) diccionarios, es posible «mezclarlos» para obtener una combinación de
los mismos. Esta combinación se basa en dos premisas:
1. Si la clave no existe, se añade con su valor.
2. Si la clave ya existe, se añade con el valor del «último» diccionario en la mezcla.6
Python orece dos mecanismos para realizar esta combinación. Vamos a partir de los
siguientes diccionarios para ejemplicar su uso:
6
En este caso «último» hace reerencia al diccionario que se encuentra más a la derecha en la expresión.
>>> rae1 = {
... bifronte: De dos frentes o dos caras,
... enjuiciar: Someter una cuestión a examen, discusión y juicio
... }
>>> rae2 = {
... anarcoide: Que tiende al desorden,
... montuvio: Campesino de la costa,
... enjuiciar: Instruir, juzgar o sentenciar una causa
... }
A partir de Python 3.9 podemos utilizar el operador | para combinar dos diccionarios:
>>> rae1.update(rae2)
>>> rae1
{bifronte: De dos frentes o dos caras,
enjuiciar: Instruir, juzgar o sentenciar una causa,
anarcoide: Que tiende al desorden,
montuvio: Campesino de la costa}
Nota: Tener en cuenta que el orden en el que especicamos los diccionarios a la hora de su
combinación (mezcla) es relevante en el resultado nal. En este caso el orden de los actores
sí altera el producto.
Nivel intermedio
Al igual que ocurría con las listas, si hacemos un cambio en un diccionario, se verá refejado
en todas las variables que hagan reerencia al mismo. Esto se deriva de su propiedad de ser
mutable. Veamos un ejemplo concreto:
>>> original_rae = {
... bifronte: De dos frentes o dos caras,
... anarcoide: Que tiende al desorden,
... montuvio: Campesino de la costa
... }
>>> original_rae
{bifronte: bla bla bla,
anarcoide: Que tiende al desorden,
montuvio: Campesino de la costa}
>>> copy_rae
{bifronte: bla bla bla,
anarcoide: Que tiende al desorden,
montuvio: Campesino de la costa}
Una posible solución a este problema es hacer una «copia dura». Para ello Python
proporciona la unción copy():
>>> original_rae = {
... bifronte: De dos frentes o dos caras,
... anarcoide: Que tiende al desorden,
... montuvio: Campesino de la costa
... }
>>> original_rae
{bifronte: bla bla bla,
anarcoide: Que tiende al desorden,
montuvio: Campesino de la costa}
>>> copy_rae
(continué en la próxima página)
Truco: En el caso de que estemos trabajando con diccionarios que contienen elementos
mutables, debemos hacer uso de la unción deepcopy() dentro del módulo copy de la librería
estándar.
Nivel intermedio
De orma análoga a cómo se escriben las listas por comprensión, podemos aplicar este método
a los diccionarios usando llaves { }.
Veamos un ejemplo en el que creamos un diccionario por comprensión donde las claves
son palabras y los valores son sus longitudes:
>>> words_length
{sun: 3, space: 5, rocket: 6, earth: 5}
>>> words_length
{sun: 3, space: 5, rocket: 6}
Nota: Se puede consultar el PEP-274 para ver más ejemplos sobre diccionarios por
comprensión.
Ejercicio
pycheck: split_marks
Nivel avanzado
La única restricción que deben cumplir las claves de un diccionario es ser «hashables»7 .
Un objeto es «hashable» si se le puede asignar un valor «hash» que no cambia en ejecución
durante toda su vida.
Para encontrar el «hash» de un objeto, Python usa la unción hash(), que devuelve un
número entero y es utilizado para indexar la tabla «hash» que se mantiene internamente:
>>> hash(999)
999
>>> hash(3.14)
322818021289917443
>>> hash(hello)
-8103770210014465245
Nota: De lo anterior se deduce que las claves de los diccionarios, al tener que ser
«hasheables», sólo pueden ser objetos inmutables.
La unción «built-in» hash() realmente hace una llamada al método mágico __hash__() del
objeto en cuestión:
>>> hash(spiderman)
-8105710090476541603
EJERCICIOS DE REPASO
1. pycheck: group_words
2. pycheck: same_dict_values
3. pycheck: build_super_dict
4. pycheck: clear_dict_values
5. pycheck: x_keys
6. pycheck: order_stock
7. pycheck: inventory_moves
8. pycheck: sort_dict
9. pycheck: money_back
10. pycheck: money_back_max
AMPLIAR CONOCIMIENTOS
5.4 Conjuntos
Un conjunto en Python representa una serie de valores únicos y sin orden establecido,
con la única restricción de que sus elementos deben ser «hashables». Mantiene muchas
similitudes con el concepto matemático de conjunto1
Para crear un conjunto basta con separar sus valores por comas y rodearlos de llaves {}:
>>> lottery
{10, 21, 29, 31, 46, 94}
>>> wrong_empty_set = {}
>>> type(wrong_empty_set)
dict
1
Foto original de portada por Duy Pham en Unsplash.
>>> empty_set
set()
>>> type(empty_set)
set
Advertencia: Aunque está permitido, NUNCA llames set a una variable porque
destruirías la unción que nos permite crear conjuntos. Y tampoco uses nombres derivados
como _set o set_ ya que no son nombres representativos que identiquen el propósito de
la variable.
5.4.2 Conversión
Para convertir otros tipos de datos en un conjunto podemos usar la unción set() sobre
cualquier iterable:
>>> set(aplatanada)
{a, d, l, n, p, t}
Importante: Como se ha visto en los ejemplos anteriores, set() se suele utilizar en muchas
ocasiones como una orma de extraer los valores únicos de otros tipos de datos. En el
caso de los diccionarios se extraen las claves, que, por denición, son únicas.
Nota: El hecho de que en los ejemplos anteriores los elementos de los conjuntos estén
Obtener un elemento
En un conjunto no existe un orden establecido para sus elementos, por lo tanto no podemos
acceder a un elemento en concreto.
De este hecho se deriva igualmente que no podemos modicar un elemento existente,
ya que ni siquiera tenemos acceso al mismo. Python sí nos permite añadir o borrar elementos
de un conjunto.
Añadir un elemento
Para añadir un elemento a un conjunto debemos utilizar la unción add(). Como ya hemos
indicado, al no importar el orden dentro del conjunto, la inserción no establece a priori la
posición donde se realizará.
A modo de ejemplo, vamos a partir de un conjunto que representa a los cuatro integrantes
originales de The Beatles. Luego añadiremos a un nuevo componente:
>>> beatles
{Best, Harrison, Lennon, McCartney, Starr}
Truco: Una pequeña regla mnemotécnica para dierenciar add() de append() es que
la unción append() signica añadir al nal, y como los conjuntos no mantienen un orden,
esta unción se aplica únicamente a listas. Por descarte, la unción add() se aplica sobre
conjuntos.
Ejercicio
pycheck: tupleset
Borrar elementos
>>> beatles
{Best, Harrison, Lennon, McCartney, Starr}
>>> beatles.remove(Best)
>>> beatles
{Harrison, Lennon, McCartney, Starr}
Longitud de un conjunto
Podemos conocer el número de elementos (cardinalidad) que tiene un conjunto con la unción
len():
>>> beatles
{Harrison, Lennon, McCartney, Starr}
>>> len(beatles)
4
Tal y como hemos visto para otros tipos de datos iterables, la orma de recorrer los elementos
de un conjunto es utilizar la sentencia for:
Consejo: Como en el ejemplo anterior, es muy común utilizar una variable en singular para
recorrer un iterable (en plural). No es una regla ja ni sirve para todos los casos, pero sí
suele ser una buena práctica.
Pertenencia de elemento
Al igual que con otros tipos de datos, Python nos orece el operador in para determinar si
un elemento pertenece a un conjunto:
>>> beatles
{Harrison, Lennon, McCartney, Starr}
Ordenando un conjunto
Ya hemos comentado que los conjuntos no mantienen un orden. ¿Pero qué ocurre si
intentamos ordenarlo?
>>> sorted(marks)
[2, 4, 5, 6, 8, 9]
>>> marks.sort()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: set object has no attribute sort
Vamos a partir de dos conjuntos = 1, 2 y = 2, 3 para ejemplicar las distintas
operaciones que se pueden hacer entre ellos basadas en los Diagramas de Venn y la Teoría
de Conjuntos:
>>> A = {1, 2}
>>> B = {2, 3}
Intersección
>>> A & B
{2}
>>> A.intersection(B)
{2}
Unión
>>> A | B
{1, 2, 3}
>>> A.union(B)
{1, 2, 3}
Diferencia
>>> A - B
{1}
>>> A.difference(B)
{1}
Diferencia simétrica
>>> A ^ B
{1, 3}
>>> A.symmetric_difference(B)
{1, 3}
>>> A ^ B == (A - B) | (B - A)
True
Inclusión
⊆
>>> B <= A
True
⊃
⊇
>>> B >= A
True
Los conjuntos, al igual que las listas y los diccionarios, también se pueden crear por
comprensión.
Veamos un ejemplo en el que construimos un conjunto por comprensión con aquellos números
enteros múltiplos de 3 en el rango [0, 20):
>>> m3
{0, 3, 6, 9, 12, 15, 18}
Ejercicio
pycheck: common_consonants
>>> marks_levels
frozenset({1, 2, 3, 4, 5})
>>> marks_levels.add(50)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: frozenset object has no attribute add
Nota: Los frozenset son a los sets lo que las tuplas a las listas: una orma de «congelar»
los valores para que no se puedan modicar.
AMPLIAR CONOCIMIENTOS
• Sets in Python
5.5 Ficheros
Python orece la unción open() para «abrir» un chero. Esta apertura se puede realizar en
3 modos distintos:
• Lectura del contenido de un chero existente.
• Escritura del contenido en un chero nuevo.
• Añadido al contenido de un chero existente.
Veamos un ejemplo para leer el contenido de un chero en el que se encuentran las
temperaturas mínimas y máximas de cada día de la última semana. El chero está en la
subcarpeta (ruta relativa) files/temps.dat y tiene el siguiente contenido:
23 29
23 31
26 34
23 33
22 29
22 28
22 28
La unción open() recibe como primer argumento la ruta al chero que queremos manejar
(como un «string») y como segundo argumento el modo de apertura (también como un
«string»). Nos devuelve el manejador del chero, que en este caso lo estamos asignando
a una variable llamada f pero le podríamos haber puesto cualquier otro nombre.
Nota: Es importante dominar los conceptos de ruta relativa y ruta absoluta para el
trabajo con cheros. Véase este artículo de DeNovatoANovato.
>>> f
<_io.TextIOWrapper name=files/temps.dat mode=r encoding=UTF-8>
Truco: Existen muchas codicaciones de caracteres para cheros, pero la más utilizada es
UTF-8 ya que es capaz de representar cualquier caracter Unicode al utilizar una longitud
variable de 1 a 4 bytes.
Hay que tener en cuenta que la ruta al chero que abrimos (en modo lectura) debe existir,
ya que de lo contrario obtendremos un error:
Una vez abierto el chero ya podemos proceder a leer su contenido. Para ello Python nos
orece la posibilidad de leer todo el chero de una vez o bien leerlo línea a línea.
Siguiendo con nuestro ejemplo de temperaturas, veamos cómo leer todo el contenido del
chero de una sola vez. Para esta operación, Python nos provee, al menos, de dos unciones:
read() Devuelve todo el contenido del chero como una cadena de texto (str):
>>> f.read()
23 29\n23 31\n26 34\n23 33\n22 29\n22 28\n22 28\n
readlines() Devuelve todo el contenido del chero como una lista (list) donde cada
elemento es una línea:
>>> f = open(files/temps.dat)
>>> f.readlines()
[23 29\n, 23 31\n, 26 34\n, 23 33\n, 22 29\n, 22 28\n, 22 28\n]
Importante: Nótese que, en ambos casos, los saltos de línea \n siguen apareciendo en
los datos leídos, por lo que habría que «limpiar» estos caracteres. Para ello se recomienda
utilizar las unciones ya vistas de cadenas de texto.
Hay situaciones en las que interesa leer el contenido del chero línea a línea. Imaginemos un
chero de tamaño considerable (varios GB). Si intentamos leer completamente este chero
de sola una vez podríamos ocupar demasiada RAM y reducir el rendimiento de nuestra
máquina.
Es por ello que Python nos orece varias aproximaciones a la lectura de cheros línea a
línea. La más usada es iterar sobre el propio manejador del chero, ya que los cheros son
estructuras de datos iterables:
>>> f = open(files/temps.dat)
23 31
26 34
23 33
22 29
22 28
22 28
Truco: Igual que pasaba anteriormente, la lectura línea por línea también incluye el salto
de línea \n lo que provoca un «doble espacio» entre cada una de las salidas. Bastaría con
aplicar line.strip() para eliminarlo.
Hay ocasiones en las que nos interesa leer únicamente una sola línea. Es cierto que esto se
puede conseguir mediante la aproximación anterior. Sería algo como:
>>> f = open(files/temps.dat)
Pero Python también orece la unción readline() que nos devuelve la siguiente línea del
chero:
>>> f = open(files/temps.dat)
>>> f.readline()
23 29\n
>>> f = open(files/temps.dat)
Hay que tener en cuenta que, una vez abierto el chero, la lectura de su contenido se
puede realizar una única vez. O dicho de otra manera, el iterable que lleva implícito «se
agota».
Veamos este escenario con el ejemplo anterior:
>>> f = open(files/temps.dat)
Advertencia: Por este motivo y también por cuestiones de legibilidad del código,
deberíamos abrir un chero una única vez y realizar todas las operaciones de lectura
necesarias, siempre que las circunstancias lo permitan.
Para escribir texto en un chero hay que abrir dicho chero en modo escritura. Para ello
utilizamos el argumento adicional en la unción open() que indica esta operación:
Nota: Si bien el chero en sí mismo se crea al abrirlo en modo escritura, la ruta hasta
ese chero no. Eso quiere decir que debemos asegurarnos que las carpetas hasta llegar a
dicho chero existen. En otro caso obtenemos un error de tipo FileNotFoundError.
Ahora ya podemos hacer uso de la unción write() para enviar contenido al chero abierto.
Supongamos que queremos volcar el contenido de una lista/tupla en dicho chero. En este
caso partimos de los códigos IATA de aeropuertos de las Islas Canarias2 .
1 >>> canary_iata = (TFN, TFS, LPA, GMZ, VDE, SPC, ACE, FUE)
2
7 >>> f.close()
2
Fuente: Smart Drone
Nótese:
Línea 4 Escritura de cada código en el chero. La unción write() no incluye el salto de
línea por deecto, así que lo añadimos de manera explícita.
Línea 7 Cierre del chero con la unción close(). Especialmente en el caso de la escritura
de cheros, se recomienda encarecidamente cerrar los cheros para evitar pérdida de
datos.
Otra orma de escribir la tupla «de una sola vez» podría ser utilizando la unción join()
con el salto de línea como separador:
>>> canary_iata = (TFN, TFS, LPA, GMZ, VDE, SPC, ACE, FUE)
>>> f.write(\n.join(canary_iata))
>>> f.close()
En el caso de que ya tengamos una lista (iterable) cuyos elementos tengan el ormato
de salida que necesitamos (incluyendo salto de línea si así uera necesario) podemos
utilizar la unción writelines() que nos orece Python.
Siguiendo con el ejemplo anterior, imaginemos un escenario en el que la tupla ya contiene
los saltos de línea:
>>> f.writelines(canary_iata)
>>> f.close()
Truco: Esta aproximación puede ser interesante cuando leemos de un chero y escribimos
en otro ya que las líneas «vienen» con el salto de línea ya incorporado.
En este caso el chero more-data.txt se abrirá en modo añadir con lo que las llamadas a
la unción write() hará que aparezcan nueva inormación al nal del contenido ya existente
en dicho chero.
Python orece gestores de contexto como una solución para establecer reglas de entrada y
salida a un determinado bloque de código.
En el caso que nos ocupa, usaremos la sentencia with y el contexto creado se ocupará de
cerrar adecuadamente el chero que hemos abierto, liberando así sus recursos:
Línea 1 Apertura del chero en modo lectura utilizando el gestor de contexto denido por
la palabra reservada with.
Línea 2 Lectura del chero línea a línea utilizando la iteración sobre el manejador del
chero.
Línea 3 Limpieza de saltos de línea con strip() encadenando la unción split() para
separar las dos temperaturas por el carácter espacio. Ver limpiar una cadena y dividir
una cadena.
Línea 4 Imprimir por pantalla la temperatura mínima y la máxima.
Nota: Es una buena práctica usar with cuando se manejan cheros. La ventaja es que el
chero se cierra adecuadamente en cualquier circunstancia, incluso si se produce cualquier
tipo de error.
Hay que prestar atención a la hora de escribir valores numéricos en un chero, ya que el
método write() por deecto espera ver un «string» como argumento:
Importante: Para evitar este tipo de errores, se debe convertir a str aquellos valores que
queramos usar con la unción write() para escribir inormación en un chero de texto. Los
-strings son tu aliado.
EJERCICIOS DE REPASO
1. pycheck: avg_temps
2. pycheck: wc
3. pycheck: read_csv
4. pycheck: txt2md
5. pycheck: nd_words
6. pycheck: sum_matrix
7. pycheck: longest_word
8. pycheck: word_req
9. pycheck: get_line
10. pycheck: replace_chars
11. pycheck: histogram
12. pycheck: submarine
AMPLIAR CONOCIMIENTOS
Modularidad
1
Denición de modularidad en Wikipedia
221
Aprende Python
6.1 Funciones
Para denir una unción utilizamos la palabra reservada def seguida del nombre6 de
la unción. A continuación aparecerán 0 o más parámetros separados por comas (entre
paréntesis), nalizando la línea con dos puntos : En la siguiente línea empezaría el cuerpo
de la unción que puede contener 1 o más sentencias, incluyendo (o no) una sentencia de
retorno con el resultado mediante return.
1
Foto original por Nathan Dumlao en Unsplash.
6
Las reglas aplicadas a nombres de variables también se aplican a nombres de unciones.
Advertencia: Prestar especial atención a los dos puntos : porque suelen olvidarse en
la denición de la unción.
def say_hello():
print(Hello!)
Los nombres de las unciones siguen las mismas reglas que las variables y, como
norma general, se suelen utilizar verbos en innitivo para su denición: load_data,
store_values, reset_cart, filter_results, block_request, …
Para invocar (o «llamar») a una unción sólo tendremos que escribir su nombre seguido de
paréntesis. En el caso de la unción sencilla (vista anteriormente) se haría así:
>>> say_hello()
Hello!
Nota: Como era de esperar, al invocar a esta unción obtenemos un mensaje por pantalla,
ruto de la ejecución del cuerpo de la unción.
Cuando queremos invocar a una unción dentro de un chero *.py lo haremos del
mismo modo que hemos visto en el intérprete interactivo:
1 def say_hello():
2 print(Hello!)
3
Retornar un valor
Las unciones pueden retornar (o «devolver») un valor. Veamos un ejemplo muy sencillo:
>>> one()
1
Importante: No conundir return con print(). El valor de retorno de una unción nos
permite usarlo uera de su contexto. El hecho de añadir print() al cuerpo de una unción
es algo «coyuntural» y no modica el resultado de la lógica interna.
>>> if one() == 1:
... print(It works!)
... else:
... print(Something is broken)
...
It works!
Si una unción no incluye un return de orma explícita, devolverá None de orma implícita:
>>> print(empty())
None
Existe la posibilidad de usar la sentencia return «a secas» (que también devuelve None) y
hace que «salgamos» inmediatamente de la unción:
>>> print(quick())
None
Una unción puede retornar más de un valor. El «secreto» es hacerlo mediante una tupla:
>>> result
(0, 1)
>>> type(result)
tuple
Por lo tanto, podremos aplicar el desempaquetado de tuplas sobre el valor retornado por la
unción:
>>> a, b = multiple()
>>> a
0
>>> b
1
>>> sqrt(4)
2.0
Cuando llamamos a una unción con argumentos, los valores de estos argumentos se copian
en los correspondientes parámetros dentro de la unción:
Truco: La sentencia pass permite «no hacer nada». Es una especie de «placeholder».
Veamos otra unción con dos parámetros y algo más de lógica de negocio:2
>>> _min(7, 9)
7
Nótese que la sentencia return puede escribirse en múltiples ocasiones y puede encontrarse
en cualquier lugar de la unción, no necesariamente al nal del cuerpo. Esta técnica puede
ser beneciosa en múltiples escenarios.
Uno de esos escenarios se relaciona con el concepto de cláusula guarda: una pieza de código
que normalmente está al comienzo de la unción y comprueba una serie de condiciones para
continuar con la ejecución o cortarla10 .
Teniendo en cuenta que la sentencia return naliza la ejecución de una unción, es viable
eliminar la sentencia else del ejemplo visto anteriormente:
>>> _min(7, 9)
7
Ejercicio
pycheck: squared_sum
Argumentos posicionales
Parámetro Argumento
vendor AMD
num_cores 8
freq 2.7
Pero es evidente que una clara desventaja del uso de argumentos posicionales es que se
necesita recordar el orden de los argumentos. Un error en la posición de los argumentos
puede generar resultados indeseados:
Argumentos nominales
En esta aproximación los argumentos no son copiados en un orden especíco sino que se
asignan por nombre a cada parámetro. Ello nos permite evitar el problema de conocer
cuál es el orden de los parámetros en la denición de la unción. Para utilizarlo, basta con
realizar una asignación de cada argumento en la propia llamada a la unción.
Veamos la misma llamada que hemos hecho en el ejemplo de construcción de la «cpu» pero
ahora utilizando paso de argumentos nominales:
Se puede ver claramente que el orden de los argumentos no infuye en el resultado nal:
Pero hay que tener en cuenta que, en este escenario, los argumentos posicionales siempre
deben ir antes que los argumentos nominales. Esto tiene mucho sentido ya que, de
no hacerlo así, Python no tendría orma de discernir a qué parámetro corresponde cada
argumento:
Nivel intermedio
Cuando realizamos modicaciones a los argumentos de una unción es importante tener en
cuenta si son mutables (listas, diccionarios, conjuntos, …) o inmutables (tuplas, enteros,
fotantes, cadenas de texto, …) ya que podríamos obtener eectos colaterales no deseados.
Supongamos que nos piden escribir una unción que reciba una lista y que devuelva sus
valores elevados al cuadrado. Pero lo hacemos «malamente»:
>>> square_it(values)
[4, 9, 16]
Es posible especicar valores por deecto en los parámetros de una unción. En el caso
de que no se proporcione un valor al argumento en la llamada a la unción, el parámetro
correspondiente tomará el valor denido por deecto.
Siguiendo con el ejemplo de la «cpu», podemos asignar 2.0GHz como recuencia por deecto.
La denición de la unción cambiaría ligeramente:
>>> build_cpu(INTEL, 2)
{vendor: INTEL, num_cores: 2, freq: 2.0}
Nivel intermedio
Es importante tener presente que los valores por deecto en los parámetros se calculan cuando
se dene la unción, no cuando se ejecuta. Veamos un ejemplo siguiendo con el caso anterior:
>>> build_cpu(AMD, 4)
{vendor: AMD, num_cores: 4, freq: 2.0}
>>> build_cpu(AMD, 4)
{vendor: AMD, num_cores: 4, freq: 2.0}
Ejercicio
pycheck: actorial
Nivel avanzado
Hay que tener cuidado a la hora de manejar los parámetros que pasamos a una unción ya
que podemos obtener resultados indeseados, especialmente cuando trabajamos con tipos de
datos mutables.
Supongamos una unción que añade elementos a una lista que pasamos por parámetro. La
idea es que si no pasamos la lista, ésta siempre empiece siendo vacía. Hagamos una serie de
pruebas pasando alguna lista como segundo argumento:
Aparentemente todo está uncionando de manera correcta, pero veamos qué ocurre en las
siguientes llamadas:
>>> buggy(a)
[a]
Obviamente algo no ha uncionado correctamente. Se esperaría que result tuviera una lista
vacía en cada ejecución. Sin embargo esto no sucede por estas dos razones:
1. El valor por deecto se establece cuando se dene la unción.
2. La variable result apunta a una zona de memoria en la que se modican sus valores.
Ejecución paso a paso a través de Python Tutor:
https://cutt.ly/sBNpVT2
A riesgo de perder el parámetro por deecto, una posible solución sería la siguiente:
>>> works(a)
[a]
>>> works(b)
[b]
La orma de arreglar el código anterior utilizando un parámetro con valor por deecto sería
utilizar un tipo de dato inmutable y tener en cuenta cuál es la primera llamada:
>>> nonbuggy(a)
[a]
>>> nonbuggy(b)
[b]
Empaquetar/Desempaquetar argumentos
Nivel intermedio
Python nos orece la posibilidad de empaquetar y desempaquetar argumentos cuando estamos
invocando a una unción, tanto para argumentos posicionales como para argumentos
nominales.
Y de esto se deriva el hecho de que podamos utilizar un número variable de argumentos
en una unción, algo que puede ser muy interesante según el caso de uso que tengamos.
>>> sum(4, 3, 2, 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sum() takes at most 2 arguments (4 given)
Para superar esta «limitación» vamos a hacer uso del * para empaquetar los argumentos
posicionales:
>>> _sum(4, 3, 2, 1)
values=(4, 3, 2, 1)
10
>>> _sum(values)
Traceback (most recent call last):
(continué en la próxima página)
>>> best_student(marks)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: best_student() takes 0 positional arguments but 1 was given
Convenciones
En muchas ocasiones se utiliza args como nombre de parámetro para argumentos posicionales
y kwargs como nombre de parámetro para argumentos nominales. Esto son únicamente
convenciones, no hay obligación de utilizar estos nombres. Así, podemos encontrar
unciones denidas de la siguiente manera:
>>> def func(*args, **kwargs):
... # TODO
... pass
...
Si bien Python nos da fexibilidad para pasar argumentos a nuestras unciones en modo
nominal o posicional, existen opciones para orzar que dicho paso sea obligatorio para una
determinada modalidad.
Nivel avanzado
A partir de Python 3.0 se orece la posibilidad de obligar a que determinados parámetros de
la unción sean pasados sólo por nombre.
Para ello, en la denición de los parámetros de la unción, tendremos que incluir un parámetro
especial * que delimitará el tipo de parámetros. Así, todos los parámetros a la derecha del
separador estarán obligados a ser nominales:
Ejemplo:
>>> def sum_power(a, b, *, power=False):
... if power:
... a **= 2
... b **= 2
... return a + b
...
>>> sum_power(3, 4)
(continué en la próxima página)
Nivel avanzado
A partir de Python 3.8 se orece la posibilidad de obligar a que determinados parámetros de
la unción sean pasados sólo por posición.
Para ello, en la denición de los parámetros de la unción, tendremos que incluir un parámetro
especial / que delimitará el tipo de parámetros. Así, todos los parámetros a la izquierda del
delimitador estarán obligados a ser posicionales:
Ejemplo:
>>> sum_power(3, 4)
7
Si mezclamos las dos estrategias anteriores podemos orzar a que una unción reciba
argumentos de un modo concreto.
Continuando con el ejemplo anterior, podríamos hacer lo siguiente:
Ejercicio
pycheck: consecutive_reqs
Nivel avanzado
Las unciones se pueden utilizar en cualquier contexto de nuestro programa. Son objetos que
pueden ser asignados a variables, usados en expresiones, devueltos como valores de retorno
o pasados como argumentos a otras unciones.
Veamos un primer ejemplo en el que pasamos una unción como argumento:
>>> type(success)
function
>>> doit(success)
Yeah!
Veamos un segundo ejemplo en el que pasamos, no sólo una unción como argumento, sino
los valores con los que debe operar:
>>> type(repeat_please)
(continué en la próxima página)
6.1.3 Documentación
Ya hemos visto que en Python podemos incluir comentarios para explicar mejor determinadas
zonas de nuestro código.
Del mismo modo podemos (y en muchos casos debemos) adjuntar documentación a la
denición de una unción incluyendo una cadena de texto (docstring) al comienzo de su
cuerpo:
>>> help(closest_int)
closest_int(value)
Returns the closest integer to the given value.
The operation is:
1. Compute distance to floor.
2. If distance less than a half, return floor.
Otherwise, return ceil.
>>> closest_int?
Signature: closest_int(value)
Docstring:
Returns the closest integer to the given value.
The operation is:
1. Compute distance to floor.
2. If distance less than a half, return floor.
Otherwise, return ceil.
File: ~/aprendepython/<ipython-input-75-5dc166360da1>
Type: function
Importante: Esto no sólo se aplica a unciones propias, sino a cualquier otra unción
denida en el lenguaje.
Nota: Si queremos ver el docstring de una unción en «crudo» (sin ormatear), podemos
usar <function>.__doc__.
Explicación de parámetros
Aunque cada uno tienes sus particularidades, todos comparten una misma estructura:
• Una primera línea de descripción de la unción.
• A continuación especicamos las características de los parámetros (incluyendo sus
tipos).
• Por último, indicamos si la unción retorna un valor y sus características.
Aunque todos los ormatos son válidos, nos centraremos en reStructuredText por ser el
estándar propuesto por Python para la documentación.
Ver también:
Google docstrings y Numpy docstrings también son ampliamente utilizados, lo único es que
necesitan de un módulo externo denominado Napoleon para que se puedan incluir en la
documentación Sphinx.
Sphinx
Dentro del «docstring» podemos escribir con sintaxis reStructuredText – véase por ejemplo
la expresión matemática en el tag :return: – lo que nos proporciona una gran fexibilidad.
Nota: La plataorma Read the Docs aloja la documentación de gran cantidad de proyectos.
En muchos de los casos se han usado «docstrings» con el ormato Sphinx visto anteriormente.
Anotación de tipos
Nivel intermedio
Las anotaciones de tipos o type-hints5 se introdujeron en Python 3.5 y permiten indicar
tipos para los parámetros de una unción y/o para su valor de retorno (aunque también
uncionan en creación de variables).
Veamos un ejemplo en el que creamos una unción para dividir una cadena de texto por la
posición especicada en el parámetro:
Como se puede observar, vamos añadiendo los tipos después de cada parámetro utilizando
: como separador. En el caso del valor de retorno usamos la fecha ->
Quizás la siguiente ejecución pueda sorprender:
Eectivamente como habrás visto, no hemos obtenido ningún error, a pesar de que
estamos pasando como primer argumento una lista en vez de una cadena de texto. Esto ocurre
porque lo que hemos denido es simplemente una anotación de tipo, no una declaración de
tipo. Existen herramientas como mypy que sí se encarga de comprobar este escenario.
Tipos compuestos
Hay escenarios en los que necesitamos más expresividad de cara a la anotación de tipos.
¿Qué ocurre si queremos indicar una lista de cadenas de texto o un conjunto de enteros?
Veamos algunos ejemplos válidos:
Anotación Ejemplo
list[str] [A, B, C]
set[int] {4, 3, 9}
dict[str, float] {x: 3.786, y: 2.198, z: 4.954}
tuple[str, int] (Hello, 10)
tuple[float, ... (1.23, 5.21, 3.62) o (4.31, 6.87) o (7.11,)
]
Múltiples tipos
En el caso de que queramos indicar que un determinado parámetro puede ser de un tipo o
de otro hay que especicarlo utilizando el operador7 |.
Veamos algunos ejemplos válidos:
Anotación Significado
tuple❘dict Tupla o diccionario
list[str❘int] Lista de cadenas de texto y/o enteros
set[int❘float] Conjunto de enteros y/o fotantes
7
Disponible a partir de Python 3.10.
Ver también:
Guía rápida para de anotación de tipos (mypy)
Ejercicio
pycheck: mcount
Número indefinido
Nivel avanzado
>>> type(num_words)
function
>>> num_words
<function __main__.<lambda>(t)>
Veamos otro ejemplo en el que mostramos una tabla con el resultado de aplicar el «and»
lógico mediante una unción «lambda» que ahora recibe dos parámetros:
Las unciones «lambda» son bastante utilizadas como argumentos a otras unciones.
Un ejemplo claro de ello es la unción sorted que recibe un parámetro opcional key donde
se dene la clave de ordenación.
Veamos cómo usar una unción anónima «lambda» para ordenar una tupla de pares
longitud-latitud:
>>> geoloc = (
... (15.623037, 13.258358),
... (55.147488, -2.667338),
... (54.572062, -73.285171),
... (3.152857, 115.327724),
... (-40.454262, 172.318877)
)
Ejercicio
pycheck: sort_ages
Enfoque funcional
map()
Esta unción aplica otra unción sobre cada elemento de un iterable. Supongamos que
queremos aplicar la siguiente unción:
2
() = ∀ ∈ [1, 10]
2
>>> type(map_gen)
map
>>> list(map_gen)
[0.5, 2.0, 4.5, 8.0, 12.5, 18.0, 24.5, 32.0, 40.5, 50.0]
4
Denición de Programación funcional en Wikipedia.
Truco: Hay que tener en cuenta que map() devuelve un generador, no directamente una
lista.
filter()
Esta unción selecciona aquellos elementos de un iterable que cumplan una determinada
condición. Supongamos que queremos seleccionar sólo aquellos números impares dentro de
un rango:
>>> type(filter_gen)
filter
>>> list(filter_gen)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
Truco: Hay que tener en cuenta que filter() devuelve un generador, no directamente una
lista.
reduce()
Para poder usar esta unción debemos usar el módulo functools. Nos permite aplicar una
unción dada sobre todos los elementos de un iterable de manera acumulativa. O dicho en
otras palabras, nos permite reducir una unción sobre un conjunto de valores. Supongamos
que queremos realizar el producto de una serie de valores aplicando este enoque:
Consejo: Por cuestiones de legibilidad del código, se suelen preerir las listas por
comprensión a unciones como map() o filter(), aunque cada problema tiene sus propias
características y sus soluciones más adecuadas. Es un enoque «más pitónico».
Hazlo pitónico
Trey Hunner explica en una de sus «newsletters» lo que él entiende por código pitónico:
«Pitónico es un término extraño que signica dierentes cosas para dierentes personas.
Algunas personas piensan que código pitónico va sobre legibilidad. Otras personas piensan
que va sobre adoptar características particulares de Python. Mucha gente tiene una denición
diusa que no va sobre legibilidad ni sobre características del lenguaje.
Yo normalmente uso el término código pitónico como un sinónimo de código idiomático o
la orma en la que la comunidad de Python tiende a hacer las cosas cuando escribe Python.
Eso deja mucho espacio a la interpretación, ya que lo que hace algo idiomático en Python
no está particularmente bien denido.
Yo argumento que código pitónico implica adoptar el desempaquetado de tuplas, usar listas
por comprensión cuando sea apropiado, usar argumentos nominales cuando tenga sentido,
evitar el uso excesivo de clases, usar las estructuras de iteración adecuadas o evitar recorrer
mediante índices.
Para mí, código pitónico signica intentar ver el código desde la perspectiva de las
herramientas especícas que Python nos proporciona, en oposición a la orma en la que
resolveríamos el mismo problema usando las herramientas que nos proporciona JavaScript,
Java, C, …»
Generadores
Un generador, como su propio nombre indica, se encarga de generar «valores» sobre los que
podemos iterar. Es decir, no construye una secuencia de orma explícita, sino que nos permite
ir «consumiendo» un valor de cada vez. Esta propiedad los hace idóneos para situaciones
en las que el tamaño de las secuencias podría tener un impacto negativo en el consumo de
memoria.
De hecho ya hemos visto algunos generadores y los hemos usado sin ser del todo conscientes.
Algo muy parecido8 a un generador es range() que orece la posibilidad de crear secuencias
de números.
Básicamente existen dos implementaciones de generadores:
• Funciones generadoras.
• Expresiones generadoras.
Funciones generadoras
>>> type(evens)
function
>>> type(evens_gen)
generator
De orma más «directa», podemos iterar sobre la propia llamada a la unción generadora:
>>> list(evens(20))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Un detalle muy importante es que los generadores «se agotan». Es decir, una vez que
ya hemos consumido todos sus elementos, no obtendremos nuevos valores:
Expresiones generadoras
Una expresión generadora es sintácticamente muy similar a una lista por comprensión,
pero utilizamos paréntesis en vez de corchetes. Se podría ver como una versión acortada
de una unción generadora.
Podemos tratar de reproducir el ejemplo visto en unciones generadoras en el que creamos
números pares hasta el 20:
>>> type(evens_gen)
generator
Ver también:
Las expresiones generadoras admiten condiciones y anidamiento de bucles, tal y como se vio
con las listas por comprensión.
Una expresión generadora se puede explicitar, sumar, buscar su máximo o su mínimo, o lo
que queramos, tal y como lo haríamos con un iterable cualquiera:
Ejercicio
pycheck: gen_squared
Funciones interiores
>>> validation_rate(zxyzxxyz)
1.0
>>> validation_rate(abzxyabcdz)
0.4
>>> validation_rate(abc)
0.0
Truco: Estas unciones pueden tener sentido cuando su ámbito de aplicación es muy concreto
y no se pueden reutilizar ácilmente.
Clausuras
Una clausura (del término inglés «closure») establece el uso de una unción interior que se
genera dinámicamente y recuerda los valores de los argumentos con los que ue creada:
>>> m3 = make_multiplier_of(3)
>>> m5 = make_multiplier_of(5)
>>> type(m3)
(continué en la próxima página)
>>> m3(7) # 7 * 3
21
>>> type(m5)
function
>>> m5(8) # 8 * 5
40
Importante: En una clausura retornamos una unción, no una llamada a una unción.
Decoradores
Elemento Descripción
my_decorator Nombre del decorador
wrapper Función interior (convención de nombre)
func Función a decorar (convención de nombre)
*args Argumentos posicionales (convención de nombre)
**kwargs Argumentos nominales (convención de nombre)
Ahora denimos una unción ordinaria (que usaremos más adelante) y que computa :
>>> power(2, 3)
8
>>> power(4, 5)
1024
>>> decorated_power(2, 3) # 8
(continué en la próxima página)
Python nos orece un «syntactic sugar» para simplicar la aplicación de los decoradores a
través del operador @ justo antes de la denición de la unción que queremos decorar:
>>> @res2bin
... def power(x: int, n: int) -> int:
... return x ** n
...
>>> power(2, 3)
0b1000
>>> power(4, 5)
0b10000000000
Ejercicio
pycheck: abs_decorator
Manipulando argumentos
Hemos visto un ejemplo de decorador que trabaja sobre el resultado de la unción decorada,
pero nada impide que trabajemos sobre los argumentos que se le pasa a la unción decorada.
Supongamos un escenario en el que implementamos unciones que trabajan con dos
operandos y queremos asegurarnos de que esos operados son números enteros. Lo
primero será denir el decorador:
Truco: Dado que sabemos positivamente que las unciones a decorar trabajan con dos
Ahora creamos una unción sencilla que suma dos números y le aplicamos el decorador:
>>> @assert_int
... def _sum(a, b):
... return a + b
...
Múltiples decoradores
Podemos aplicar más de un decorador a cada unción. Para ejemplicarlo vamos a crear dos
decoradores muy sencillos:
Ahora aplicaremos ambos decoradores sobre una unción que realiza el producto de dos
números:
>>> @plus5
... @div2
... def prod(a, b):
... return a * b
...
>>> prod(4, 3)
11
>>> ((4 * 3) // 2) + 5
11
Cuando tenemos varios decoradores se aplican desde dentro hacia uera ya que la
ejecución de un decorador depende de otro decorador.
Si anotamos los decoradores podemos ver exactamente cuál es el orden de ejecución:
>>> prod(4, 3)
result=12 # función prod "tal cual" (4*3)
div2 # decorador div2
result=6 # aplicación decorador div2 (12/2)
plus5 # decorador plus5
11 # aplicación decorador plus5 (6+5)
Una orma sencilla de entender el orden de ejecución de múltiples decoradores es aplicar las
unciones decoradoras directamente sobre la unción decorada.
Esto:
>>> @plus5
... @div2
... def prod(a, b):
... return a * b
...
equivale a:
El último «salto mortal» sería denir decoradores con parámetros. El esqueleto básico de un
decorador con parámetros es el siguiente:
Lo más sencillo es verlo con un ejemplo. Supongamos que queremos orzar a que los
parámetros de entrada a la unción sean de un tipo concreto (pero parametrizable).
Podríamos denir el decorador de la siguiente manera:
Ahora creamos una unción sencilla que suma dos números y le aplicamos el decorador:
>>> @assert_type(float)
... def _sum(a, b):
... return a + b
...
La ventaja que tiene este enoque es que podemos aplicar «distintos» decoradores
modicando sus parámetros. Por ejemplo, supongamos que ahora queremos asegurar que
una unción trabaja únicamente con cadenas de texto:
>>> @assert_type(str)
... def split(text):
... half_size = len(text) // 2
... return text[:half_size], text[half_size:]
...
Ejercicio
Funciones recursivas
>>> call_me()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in call_me
File "<stdin>", line 2, in call_me
File "<stdin>", line 2, in call_me
[Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded
Veamos ahora un ejemplo más real en el que computar el enésimo término de la Sucesión de
Fibonacci utilizando una unción recursiva:
>>> fibonacci(10)
55
>>> fibonacci(20)
6765
Ejercicio
pycheck: actorial_recursive
Funcionitis
Cuando una variable se dene en el espacio de nombres global podemos hacer uso de ella con
total transparencia dentro del ámbito de las unciones del programa:
>>> language
castellano
>>> catalonia()
language=castellano
En el caso de que asignemos un valor a una variable global dentro de una unción, no
estaremos modicando ese valor. Por el contrario, estaremos creando una variable en el
espacio de nombres local:
>>> language
castellano
>>> catalonia()
language=catalan
>>> language
castellano
Python nos permite modicar una variable denida en un espacio de nombres global dentro
de una unción. Para ello debemos usar el modicador global:
>>> language
castellano
>>> catalonia()
language=catalan
>>> language
catalan
Advertencia: El uso de global no se considera una buena práctica ya que puede inducir
a conusión y tener eectos colaterales indeseados.
Python proporciona dos unciones para acceder al contenido de los espacios de nombres:
locals() Devuelve un diccionario con los contenidos del espacio de nombres local:
>>> catalonia()
locals()={language: catalan}
globals() Devuelve un diccionario con los contenidos del espacio de nombres global:
>>> globals()
{__name__: __main__,
__doc__: Automatically created module for IPython interactive environment,
__package__: None,
__loader__: None,
__spec__: None,
__builtin__: <module builtins (built-in)>,
__builtins__: <module builtins (built-in)>,
_ih: [,
"language = castellano",
"def catalonia():\n language = catalan\n print(f{locals()=})\n ",
language,
catalonia(),
globals()],
_oh: {3: castellano},
_dh: [/Users/sdelquin],
In: [,
"language = castellano",
"def catalonia():\n language = catalan\n print(f{locals()=})\n ",
language,
catalonia(),
globals()],
Out: {3: castellano},
get_ipython: <bound method InteractiveShell.get_ipython of <IPython.terminal.
˓→interactiveshell.TerminalInteractiveShell object at 0x10e70c2e0>>,
_i: catalonia(),
_ii: language,
_iii: "def catalonia():\n language = catalan\n print(f{locals()=})\
˓→n ",
_i1: "language = castellano",
language: castellano,
_i2: "def catalonia():\n language = catalan\n print(f{locals()=})\
˓→n ",
catalonia: <function __main__.catalonia()>,
_i3: language,
_3: castellano,
(continué en la próxima página)
EJERCICIOS DE REPASO
1. pycheck: num_in_interval
2. pycheck: extract_evens
3. pycheck: split_case
4. pycheck: perect
5. pycheck: palindrome
6. pycheck: count_vowels_rec
7. pycheck: pangram
8. pycheck: cycle_alphabet
9. pycheck: bubble_sort
10. pycheck: consecutive_seq
11. pycheck: magic_square
12. pycheck: sum_nested
13. pycheck: power_recursive
14. pycheck: hyperactorial
15. pycheck: bonacci_generator
AMPLIAR CONOCIMIENTOS
Hasta ahora hemos estado usando objetos de orma totalmente transparente, casi sin ser
conscientes de ello. Pero, en realidad, todo en Python es un objeto, desde números
a unciones. El lenguaje provee ciertos mecanismos para no tener que usar explícitamente
técnicas de orientación a objetos.
Llegados a este punto, investigaremos en proundidad la creación y manipulación de clases
y objetos, así como todas las técnicas y procedimientos que engloban este paradigma.1
1
Foto original por Rabie Madaci en Unsplash.
La programación orientada a objetos (POO) o en sus siglas inglesas OOP es una manera de
programar (paradigma) que permite llevar al código mecanismos usados con entidades de la
vida real.
Sus benecios son los siguientes:
Encapsulamiento Permite empaquetar el código dentro de una unidad (objeto) donde
se puede determinar el ámbito de actuación.
Abstracción Permite generalizar los tipos de objetos a través de las clases y simplicar
el programa.
Herencia Permite reutilizar código al poder heredar atributos y comportamientos de una
clase a otra.
Polimorsmo Permite crear múltiples objetos a partir de una misma pieza fexible de
código.
¿Qué es un objeto?
Un objeto representa una instancia única de alguna entidad (a través de los valores de sus
atributos) e interactúa con otros objetos (o consigo mismo) a través de sus métodos.
Para crear un objeto primero debemos denir la clase que lo contiene. Podemos pensar en
la clase como el molde con el que se crean nuevos objetos de ese tipo.
En el proceso de diseño de una clase hay que tener en cuenta – entre otros – el principio
de responsabilidad única7 , intentando que los atributos y los métodos que contenga esa
clase estén enocados a un objetivo único y bien denido.
7
Principios SOLID
Empecemos por crear nuestra primera clase. En este caso vamos a modelar algunos de los
droides de la saga StarWars:
Para ello usaremos la palabra reservada class seguida del nombre de la clase:
Existen multitud de droides en el universo StarWars. Una vez que hemos denido la clase
genérica podemos crear instancias/objetos (droides) concretos:
Añadiendo métodos
Un método es una unción que orma parte de una clase o de un objeto. En su ámbito tiene
acceso a otros métodos y atributos de la clase o del objeto al que pertenece.
La denición de un método (de instancia) es análoga a la de una unción ordinaria, pero
incorporando un primer parámetro self que hace reerencia a la instancia actual del objeto.
Una de las acciones más sencillas que se pueden hacer sobre un droide es encenderlo o
apagarlo. Vamos a implementar estos dos métodos en nuestra clase:
>>> k2so.switch_on()
Hi! Im a droid. Can I help you?
>>> k2so.switch_off()
Bye! Im going to sleep
Consejo: El nombre self es sólo una convención. Este parámetro puede llamarse de otra
manera, pero seguir el estándar ayuda a la legibilidad.
Añadiendo atributos
Un atributo no es más que una variable, un nombre al que asignamos un valor, con la
particularidad de vivir dentro de una clase o de un objeto.
Supongamos que, siguiendo con el ejemplo anterior, queremos guardar en un atributo el
estado del droide (encendido/apagado):
>>> k2so.switch_on()
Hi! Im a droid. Can I help you?
>>> k2so.power_on
True
>>> k2so.switch_off()
Bye! Im going to sleep
>>> k2so.power_on
False
Importante: Siempre que queramos acceder a cualquier método o atributo del objeto habrá
Inicialización
Existe un método especial que se ejecuta cuando creamos una instancia de un objeto. Este
método es __init__ y nos permite asignar atributos y realizar operaciones con el objeto en
el momento de su creación. También es ampliamente conocido como el constructor.
Veamos un ejemplo de este método con nuestros droides en el que únicamente guardaremos
el nombre del droide como un atributo del objeto:
8 >>> droid.name
9 BB-8
>>> droid.name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Droid object has no attribute name
Ejercicio
Escriba una clase MobilePhone que represente un teléono móvil.
Atributos:
6.2.3 Atributos
Acceso directo
>>> droid.name
C-3PO
Nota: Nótese el acceso a los atributos con obj.attribute en vez de lo que veníamos usando
en diccionarios donde hay que escribir «un poco más» obj[attribute].
Propiedades
Como hemos visto previamente, los atributos denidos en un objeto son accesibles
públicamente. Esto puede parecer extraño a personas que vengan de otros lenguajes de
programación (véase Java). En Python existe un cierto «sentido de la responsabilidad» a la
hora de programar y manejar este tipo de situaciones: Casi todo es posible a priori pero se
debe controlar explícitamente.
Una primera solución «pitónica» para la privacidad de los atributos es el uso de
propiedades. La orma más común de aplicar propiedades es mediante el uso de decoradores:
• @property para leer el valor de un atributo («getter»).
• @name.setter para escribir el valor de un atributo.
Veamos un ejemplo en el que estamos ouscando el nombre del droide a través de propiedades:
>>> droid.name
inside the getter
N1-G3L
>>> droid.name
(continué en la próxima página)
>>> droid.hidden_name
Nigel
>>> droid.name
inside the getter
waka-waka
Valores calculados
Una propiedad también se puede usar para devolver un valor calculado (o computado).
A modo de ejemplo, supongamos que la altura del periscopio de los droides astromecánicos
se calcula siempre como un porcentaje de su altura. Veamos cómo implementarlo:
>>> droid.periscope_height
0.315
>>> droid.periscope_height(from_ground=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: float object is not callable
En este caso tendríamos que implementar un método para resolver el escenario planteado.
Consejo: La ventaja de usar valores calculados sobre simples atributos es que el cambio de
valor en un atributo no asegura que actualicemos otro atributo, y además siempre podremos
modicar directamente el valor del atributo, con lo que podríamos obtener eectos colaterales
indeseados.
Cacheando propiedades
En los ejemplos anteriores hemos creado una propiedad que calcula el alto del periscopio
de un droide astromecánico a partir de su altura. El «coste» de este cálculo es bajo, pero
imaginemos por un momento que uera muy alto.
Si cada vez que accedemos a dicha propiedad tenemos que realizar ese cálculo, estaríamos
siendo muy inecientes (en el caso de que la altura del droide no cambiara). Veamos una
aproximación a este escenario usando el cacheado de propiedades:
>>> droid.periscope_height
Calculating periscope height...
0.315
>>> droid.periscope_height # Cacheado!
0.315
>>> droid.periscope_height
Calculating periscope height...
0.345
>>> droid.periscope_height # Cacheado!
0.345
Ocultando atributos
Python tiene una convención sobre aquellos atributos que queremos hacer «privados» (u
ocultos): comenzar el nombre con doble subguión __
Lo que realmente ocurre tras el telón se conoce como «name mangling» y consiste en
modicar el nombre del atributo incorporado la clase como un prejo. Sabiendo esto podemos
acceder al valor del atributo supuestamente privado:
>>> droid._Droid__name
BC-44
Nota: La losoía de Python permite hacer casi cualquier cosa con los objetos que se
manejan, eso sí, el sentido de la responsabilidad se traslada a la persona que desarrolla e
incluso a la persona que hace uso del objeto.
Atributos de clase
Podemos asignar atributos a una clase y serán asumidos por todos los objetos instanciados
de esa clase.
A modo de ejemplo, en un principio, todos los droides están diseñados para que obedezcan
a su dueño. Esto lo conseguiremos a nivel de clase, salvo que ese comportamiento se
sobreescriba:
Truco: Los atributos de clase son accesibles tanto desde la clase como desde las instancias
creadas.
>>> droid1.obeys_owner
False
>>> droid2.obeys_owner
False
6.2.4 Métodos
Métodos de instancia
Un método de instancia es un método que modica o accede al estado del objeto al que
hace reerencia. Recibe self como primer parámetro, el cual se convierte en el propio objeto
sobre el que estamos trabajando. Python envía este argumento de orma transparente: no
hay que pasarlo como argumento.
Veamos un ejemplo en el que, además del constructor, creamos un método de instancia para
hacer que un droide se mueva:
>>> droid.move_up(10)
Moving 10 steps
Propiedades vs Métodos
Métodos de clase
>>> Droid.total_droids()
3 droids built so far!
Consejo: El nombre cls es sólo una convención. Este parámetro puede llamarse de otra
manera, pero seguir el estándar ayuda a la legibilidad.
Métodos estáticos
>>> Droid.get_droids_categories()
(Messeger, Astromech, Power, Protocol)
Métodos decorados
Es posible que, según el escenario, queramos decorar ciertos métodos de nuestra clase.
Esto lo conseguiremos siguiendo la misma estructura de decoradores que ya hemos visto,
pero con ciertos matices.
A continuación veremos un ejemplo en el que creamos un decorador para auditar las acciones
de un droide y saber quién ha hecho qué:
>>> droid.move(1, 1)
Droid B1 running move
>>> droid.reset()
Droid B1 running reset
El decorador se puede poner dentro o uera de la clase. Por una cuestión de encapsulamiento
podría tener sentido dejarlo dentro de la clase como método estático.
Ver también:
También es posible aplicar esta misma técnica usando decoradores con parámetros.
Métodos mágicos
Nivel avanzado
Cuando escribimos hello world * 3 ¿cómo sabe el objeto hello world lo que
debe hacer para multiplicarse con el objeto entero 3? O dicho de otra orma, ¿cuál es la
implementación del operador * para «strings» e «int»? En valores numéricos puede parecer
evidente (siguiendo los operadores matemáticos), pero no es así para otros objetos. La
solución que proporciona Python para estas (y otras) situaciones son los métodos mágicos.
Los métodos mágicos empiezan y terminan por doble subguión __ (es por ello que también
se les conoce como «dunder-methods»). Uno de los «dunder-methods» más amosos es el
constructor de una clase: __init__().
Para el caso de los operadores, existe un método mágico asociado (que podemos personalizar).
Por ejemplo la comparación de dos objetos se realiza con el método __eq__():
Extrapolando esta idea a nuestro universo StarWars, podríamos establecer que dos droides
son iguales si su nombre es igual, independientemente de que tengan distintos números de
serie:
>>> droid1.__eq__(droid2)
True
Truco:
Para poder utilizar la anotación de tipo Droid necesitamos añadir la siguiente línea al
principio de nuestro código:
from __future__ import annotations
Veamos un ejemplo en el que «sumamos» dos droides (esto se podría ver como una usión).
Supongamos que la suma de dos droides implica: a) que el nombre del droide resultante es
la concatenación de los nombres de los droides de entrada; b) que la energía del droide
resultante es la suma de la energía de los droides de entrada:
>>> class Droid:
... def __init__(self, name: str, power: int):
... self.name = name
... self.power = power
...
... def __add__(self, other: Droid) -> Droid:
... new_name = self.name + - + other.name
... new_power = self.power + other.power
... return Droid(new_name, new_power) # Hay que devolver un objeto de tipo␣
˓→Droid
...
(continué en la próxima página)
Importante: Este tipo de operaciones debe devolver un objeto de la clase con la que
estamos trabajando.
Truco: En este tipo de métodos mágicos el parámetro suele llamarse other haciendo
reerencia al «otro» objeto que entra en la operación. Es una convención.
Sobrecarga de operadores
>>> powerful_droid.power
100
Esta misma estrategia se puede aplicar al operador de igualdad ya que es muy habitual
encontrar comparaciones de objetos en nuestro código. Por ello, deberíamos tener en cuenta
si se van a comparar dos objetos de distinta naturaleza.
Retomando el caso ya visto… ¿qué pasaría si comparamos un droide con una cadena
de texto?
__str__
Uno de los métodos mágicos más utilizados es __str__ y permite establecer la orma en la
que un objeto es representado como cadena de texto:
>>> str(droid)
Droid "K-2SO" serial-no 8403898409432
Ejercicio
Dena una clase Fraction que represente una racción con numerador y denominador enteros
y utilice los métodos mágicos para poder sumar, restar, multiplicar y dividir estas racciones.
Además de esto, necesitaremos:
• gcd(a, b) como método estático siguiendo el algoritmo de Euclides para calcular el
máximo común divisor entre a y b.
• __init__(self, num, den) para construir una racción (incluyendo simplicación de
sus términos mediante el método gcd().
• __str__(self) para representar una racción.
Algoritmo de Euclides:
__repr__
En ausencia del método __str__() se usará por deecto el método __repr__(). La dierencia
entre ambos métodos es que el primero está más pensado para una representación del objeto
de cara al usuario mientras que el segundo está más orientado al desarrollador.
El método __repr()__ se invoca automáticamente en los dos siguientes escenarios:
1. Cuando no existe el método __str__() en el objeto y tratamos de encontrar su
representación en cadena de texto con str() o print().
2. Cuando utilizamos el intérprete interactivo de Python y pedimos el «valor» del objeto.
Veamos un ejemplo. En primer lugar un droide que sólo implementa el método __str__():
>>> print(c14)
Hi there! Im C-14
Gestores de contexto
Aunque en este caso no estamos haciendo uso de los parámetros en la unción __exit__(),
hacen reerencia a una posible excepción (error) que se produzca en la ejecución del bloque
de código que engloba el contexto. Los tres parámetros son:
1. exc_type indicando el tipo de la excepción.
2. exc_value indicando el valor (mensaje) de la excepción.
3. exc_traceback indicando la «traza» (pila) de llamadas que llevaron hasta la excepción.
Ahora podemos probar nuestro gestor de contexto con un ejemplo concreto. La orma de
«activar» el contexto es usar la sentencia with seguida del símbolo que lo gestiona:
>>> with Timer():
... for _ in range(1_000_000):
... x = 2 ** 20
...
Execution time (seconds): 0.05283
Volviendo a nuestro ejemplo de los droides de StarWars, vamos a crear un gestor de contexto
que «congele» un droide para resetear su distancia recorrida:
>>> class Droid:
... def __init__(self, name: str):
... self.name = name
... self.covered_distance = 0
...
... def move_up(self, steps: int) -> None:
... self.covered_distance += steps
... print(fMoving {steps} steps)
...