Full Code of sdelquin/aprendepython for AI

main 8e7c4af4ee78 cached
140 files
4.8 MB
1.3M tokens
4 symbols
1 requests
Download .txt
Showing preview only (5,046K chars total). Download the full file or copy to clipboard to get everything.
Repository: sdelquin/aprendepython
Branch: main
Commit: 8e7c4af4ee78
Files: 140
Total size: 4.8 MB

Directory structure:
gitextract_sauf215x/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .prettierignore
├── CHANGELOG.md
├── Dockerfile
├── README.md
├── docs/
│   ├── assets/
│   │   ├── css/
│   │   │   └── custom.css
│   │   └── js/
│   │       ├── clipboard.js
│   │       └── mathjax.js
│   ├── core/
│   │   ├── controlflow/
│   │   │   ├── conditionals.md
│   │   │   ├── index.md
│   │   │   └── loops.md
│   │   ├── datastructures/
│   │   │   ├── dicts.md
│   │   │   ├── files.md
│   │   │   ├── index.md
│   │   │   ├── lists.md
│   │   │   ├── sets.md
│   │   │   └── tuples.md
│   │   ├── datatypes/
│   │   │   ├── data.md
│   │   │   ├── index.md
│   │   │   ├── numbers.md
│   │   │   └── strings.md
│   │   ├── devenv/
│   │   │   ├── index.md
│   │   │   ├── real-context.md
│   │   │   ├── thonny.md
│   │   │   └── vscode.md
│   │   ├── index.md
│   │   ├── introduction/
│   │   │   ├── history.md
│   │   │   ├── index.md
│   │   │   ├── machine.md
│   │   │   └── python.md
│   │   └── modularity/
│   │       ├── exceptions.md
│   │       ├── functions.md
│   │       ├── index.md
│   │       ├── modules.md
│   │       └── oop.md
│   ├── index.md
│   ├── stdlib/
│   │   ├── data-access/
│   │   │   ├── index.md
│   │   │   └── sqlite.md
│   │   ├── index.md
│   │   └── text-processing/
│   │       ├── index.md
│   │       ├── re.md
│   │       └── string.md
│   └── third-party/
│       ├── config/
│       │   ├── index.md
│       │   └── prettyconf.md
│       ├── data-science/
│       │   ├── files/
│       │   │   ├── jupyter/
│       │   │   │   ├── equations.tex
│       │   │   │   └── timeit.py
│       │   │   ├── matplotlib/
│       │   │   │   ├── avengers.csv
│       │   │   │   ├── bmw-clean.csv
│       │   │   │   ├── bmw_plot.py
│       │   │   │   ├── eth-usd.csv
│       │   │   │   ├── euro-dollar-clean.csv
│       │   │   │   ├── euro_dollar.py
│       │   │   │   ├── global-temperatures.csv
│       │   │   │   ├── imdb-top-1000.csv
│       │   │   │   ├── medals.xlsx
│       │   │   │   ├── mwh-spain-2021-clean.csv
│       │   │   │   ├── mwh_spain.py
│       │   │   │   ├── nba-data.csv
│       │   │   │   ├── pokemon.csv
│       │   │   │   ├── pokemon_speed.py
│       │   │   │   ├── soften_wave.py
│       │   │   │   ├── tiobe-2020-clean.csv
│       │   │   │   └── tiobe_2020.py
│       │   │   ├── numpy/
│       │   │   │   ├── diag.py
│       │   │   │   ├── diag_transform.py
│       │   │   │   ├── euler_product.py
│       │   │   │   ├── flip_powers.py
│       │   │   │   ├── identity_equation.py
│       │   │   │   ├── lineq.py
│       │   │   │   ├── np_matrix.py
│       │   │   │   ├── np_odds.py
│       │   │   │   ├── np_random.py
│       │   │   │   ├── np_transform.py
│       │   │   │   ├── transpose.py
│       │   │   │   └── vectorize.py
│       │   │   └── pandas/
│       │   │       ├── above_mean.py
│       │   │       ├── comunidades.py
│       │   │       ├── create_dataframe.py
│       │   │       ├── create_series.py
│       │   │       ├── democan.csv
│       │   │       ├── df_access.py
│       │   │       ├── df_oasis.py
│       │   │       ├── grants.py
│       │   │       ├── index_dataframe.py
│       │   │       ├── load_dataframe.py
│       │   │       ├── oasis.csv
│       │   │       ├── pop_density.py
│       │   │       ├── pop_percentage.py
│       │   │       ├── recoding.py
│       │   │       ├── smallest_density.py
│       │   │       └── tech.csv
│       │   ├── index.md
│       │   ├── jupyter.md
│       │   ├── matplotlib.md
│       │   ├── numpy.md
│       │   └── pandas/
│       │       ├── dataframes.md
│       │       ├── index.md
│       │       └── series.md
│       ├── index.md
│       ├── learning/
│       │   ├── index.md
│       │   └── pypas.md
│       ├── networking/
│       │   ├── files/
│       │   │   └── requests/
│       │   │       └── req.py
│       │   ├── index.md
│       │   └── requests.md
│       ├── pdf/
│       │   ├── index.md
│       │   └── weasyprint.md
│       ├── scraping/
│       │   ├── beautifulsoup.md
│       │   ├── files/
│       │   │   ├── beautifulsoup/
│       │   │   │   └── pypi-trend.py
│       │   │   └── selenium/
│       │   │       ├── mercadona.py
│       │   │       ├── wordle_play.py
│       │   │       └── wordle_try.py
│       │   ├── index.md
│       │   └── selenium.md
│       └── webdev/
│           ├── django/
│           │   ├── admin.md
│           │   ├── api.md
│           │   ├── apps.md
│           │   ├── auth.md
│           │   ├── extras.md
│           │   ├── files/
│           │   │   └── api/
│           │   │       ├── categories.json
│           │   │       ├── posts.json
│           │   │       └── users.json
│           │   ├── forms.md
│           │   ├── i18n.md
│           │   ├── index.md
│           │   ├── justfile.md
│           │   ├── middleware.md
│           │   ├── models.md
│           │   ├── production.md
│           │   ├── setup.md
│           │   ├── static.md
│           │   ├── templates.md
│           │   ├── urls.md
│           │   ├── views.md
│           │   └── webdev.md
│           └── index.md
├── includes/
│   └── abbreviations.md
├── justfile
├── pyproject.toml
└── zensical.toml

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
  push:
    branches:
      - main
  workflow_dispatch:
jobs:
  deploy-documentation:
    name: Deploy documentation
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Build docker image
        run: docker build . -t aprendepython
      - name: Build documentation
        run: docker run -v ${PWD}:/docs zensical/zensical build
      - name: Sync files with production server
        uses: burnett01/rsync-deployments@4.1
        with:
          switches: -avzr --delete
          path: site/
          remote_host: aprendepython.es
          remote_path: ${{ secrets.REMOTE_BUILD_PATH }}
          remote_user: ${{ secrets.REMOTE_USER }}
          remote_key: ${{ secrets.REMOTE_KEY }}


================================================
FILE: .gitignore
================================================
.venv
.artwork
*.pyc
TODO
site
.DS_Store
__pycache__
.mypy_cache
.pytest_cache
*.egg-info
*.egg
.cache


================================================
FILE: .prettierignore
================================================
*.md
pyproject.toml


================================================
FILE: CHANGELOG.md
================================================
# Changelog

Las versiones siguen [versionado semántico](https://semver.org/) (`<major>.<minor>.<patch>`).

## Version X.X.X

Publicada el DD-MM-YYYY

## Version 3.2.5

Publicada el 10-04-2026

- Estructuras de datos: Tuplas
	- Se traslada ejercicio `dec2bin` desde listas.
- Django: Puesta en marcha
	- Añade diagrama sobre contenido de carpeta `.venv`
- Django: Modelos
	- Añade nota final al revertir migraciones.

## Version 3.2.4

Publicada el 08-04-2026

- Actualiza la versión de Zensical a [0.0.32](https://github.com/zensical/zensical/releases/tag/v0.0.32).
- Corrige render de fórmulas matemáticas en anotaciones de código: [zensical/ui](https://github.com/zensical/ui/issues/93)
- Django: Interfaz administrativa
	- Corrige typo en registro de modelo.
- Django: Producción
	- Añade documentación sobre seguridad.
- Acceso a datos: sqlite
	- Mejora documentación sobre tipos de datos en SQLite.
- Modularidad: Objetos y clases
	- Aclara uso de anotaciones dentro de clase.
- Tipos de datos: Cadenas de texto
	- Añade funciones para identificar espacios.
- Modularidad: Excepciones
	- Mejora documentación.
- Django: Justfile
	- Corrige algunas recetas para Django.
- Entornos de desarrollo: Contexto real
	- Añade notas sobre instalación de uv.

## Version 3.2.3

Publicada el 22-03-2026

- Actualiza la versión de Zensical a [0.0.28](https://github.com/zensical/zensical/releases/tag/v0.0.28).
- Modularidad: Programación orientada a objetos
	- Cambia su nombre a "Objetos y clases".
- Django: API
	- Arregla acceso por `pk` en vez de `id` (donde aplique).
	- Mejora apuntes en toda la sección.
- Django: Plantillas
	- Añade pequeña sobre sobre localización de plantillas.
- Django: Puesta en marcha
	- Añade diagrama de flujo de procesos.
- Django: Desarrollo web
	- Actualiza tabla de versiones de Django.

## Version 3.2.2

Publicada el 16-03-2026

- Configuración Zensical
	- Eliminar el resaltado de búsqueda.
- Django: API
	- Corrige errores menores.
	- Añade documentación sobre error 409.
- Modularidad: Funciones
	- Corrige errores menores.
- Tipos de datos: Cadenas de texto.
	- Añade opciones de `count()`
- Django: Justfile
	- Añade instrucciones de instalación.
	- Añade receta para `dbcmd`.
- Django: Aplicaciones
	- Añade configuración de app `shared`.
- Django: Modelos
	- Añade notas sobre instalación de cliente SQLite.
	- Añade receta `just` para lanzar comando de base de datos.
	- Añade documentación sobre revertir migraciones.
- Modularidad: Programación orientada a objetos.
	- Corrige ejemplos de métodos mágicos en operadores.
	- Corrige ejemplos de gestores de contexto.
- Django: URLs
	- Corrige error ortográfico.
- Django: Formularios
	- Mejora ejemplos de formularios.
	- Añade nota sobre campos opcionales.
- Estructuras de datos: Ficheros
	- Añade notas sobre los modos de apertura extendidos.
- Django: Puesta en marcha
	- Mejora contenido del `.gitignore` para proyectos Django.

## Version 3.2.1

Publicada el 15-03-2026

- Actualiza la versión de Zensical a [0.0.27](https://github.com/zensical/zensical/releases/tag/v0.0.27).

## Version 3.2.0

Publicada el 11-03-2026

- Django: API
  - Migración de apuntes a [Django Ninja](https://django-ninja.dev/).

## Version 3.1.6

Publicada el 03-03-2026

- Introducción: Python
	- Actualiza rankings.
	- Añade fondo de pantalla personalizado sobre Zen de Python.
- Entornos de desarrollo: Contexto real
	- Corrige errores menores.
- Entornos de desarrollo: Visual Studio Code
	- Añade configuraciones vscode, ruff y ty.
- Tipos de datos: Datos
	- Añade documentación sobre precarga de datos.
- Tipos de datos: Números
	- Corrige notas sobre precedencia de operadores.

## Version 3.1.5

Publicada el 01-03-2026

Corrige ruta de _assets_ JavaScript para MathJax.

## Version 3.1.4

Publicada el 26-02-2026

- Modularidad: Funciones
  - Arregla código de algunas funciones.
- Actualiza la versión de Zensical a [0.0.24](https://github.com/zensical/zensical/releases/tag/v0.0.24).

## Version 3.1.3

Publicada el 04-02-2026

- Estructuras de datos: Ficheros
  - Añade documentación sobre `writelines()`.
  - Añade documentación sobre apertura de múltiples ficheros con contextos.
  - Añade nuevo ejercicio de ficheros `split-file`.

## Version 3.1.2

Publicada el 02-02-2026

- Actualiza la versión de Zensical a [0.0.20](https://github.com/zensical/zensical/releases/tag/v0.0.20).
- Django: Modelos
  - Añade documentación sobre `editable`
  - Añade forma correcta de comprobar la pertenencia de un valor enumerado.
- Django: API
  - Añade aclaración sobre serialización de campos `Decimal`.

## Version 3.1.1

Publicada el 26-01-2026

- Django: Modelos
  - Añade notas sobre _fixtures_.

## Version 3.1.0

Publicada el 26-01-2026

- Desechar el viejo fichero de configuración `mkdocs.yml`.
- Django: Producción
  - Añade notas sobre Django en producción.

## Version 3.0.7

Publicada el 23-01-2026

- Estructuras de datos: Diccionarios
  - Añade función `setdefault()`
- Django: Extras
  - Añade gestión de miniaturas en Sorl Thumbnail.
- Django: Estáticos
  - Añade creación de modales con Bootstrap.
- Django: Formularios
  - Nota informativa sobre accesos a campos desde constructor.
- Django: Modelos
  - Añade validación cruzada de campos de modelo.

## Version 3.0.6

Publicada el 22-01-2026

- Django: API
  - Mejora implementación de comprobación de métodos HTTP.

## Version 3.0.5

Publicada el 19-01-2026

Actualiza la versión de Zensical a [0.0.17](https://github.com/zensical/zensical/releases/tag/v0.0.17).

## Version 3.0.4

Publicada el 15-01-2026

Actualiza la versión de Zensical a [0.0.16](https://github.com/zensical/zensical/releases/tag/v0.0.16).

## Version 3.0.3

Publicada el 14-01-2026

- Django: Modelos
  - Añade otra manera de añadir objetos en muchos a muchos.
- Django: API
  - Mejora de documentación con más código y explicaciones.

## Version 3.0.2

Publicada el 12-01-2026

- Estructuras de datos: Listas
  - Añade ejercicios al principio del capítulo.
- Django: API
  - Añade nuevo contenido y corrige errores.

## Version 3.0.1

Publicada el 08-01-2026

- pypas
  - Añade documentación de nuevos comandos.

## Version 3.0.0

Publicada el 06-01-2026

- Migración de todo el sitio web a Zensical.

## Version 2.2.4

Publicada el 03-12-2025

- Django: Extras
  - Corrige configuración de Brevo.
- Configuraciones: Pretty Conf
  - Añade documentación sobre valores por defecto.
- PDF: WeasyPrint
  - Añade documentación sobre CSS.
- Django: Middleware
  - Corrige errores menores.

## Version 2.2.3

Publicada el 02-12-2025

- Django: Internacionalización
  - Añade documentación de poedit.
  - Añade receta justfile para poedit.
- Django: Extras
  - Completa documentación y corrige errores menores.
- Django: Middleware
  - Cambia ejemplo de middleware personalizado.

## Version 2.2.2

Publicada el 01-12-2025

Completa documentación de:
  - Django: Estáticos
    - Corrige errores menores.
  - Django: Plantillas
    - Corrige errores menores.
  - Django: Internacionalización
    - Traducción en URLs.
    - Etiqueta personalizada para selección de idioma.

## Version 2.2.1

Publicada el 27-11-2025

Completa documentación de:
  - Django: Modelos
    - Corrige typos.
  - Django: Extras
    - Corrige pequeños detalles en documentación.
    - Elimina indicación recurrente de activación de entorno virtual.
  - Django: Justfile
    - Añade receta para Django-RQ.
  - Estructuras de datos: Listas
    - Corrige typos.
  - Django: Interfaz administrativa
    - Corrige typos.
  - Django: Plantillas
    - Corrige typos.
  - Django: URLs
    - Corrige typos.
  - Configuraciones: Pretty Conf
    - Corrige pequeños errores.
  - PDF: Weasyprint
    - Corrige errores.
    - Mejora documentación.

## Version 2.2.0

Publicada el 25-11-2025

Completa documentación de:
  - Modularidad: Programación orientada a objetos
    - Corrige typos.
  - Modularidad: Excepciones
    - Corrige excepciones predefinidas.
  - Estructuras de datos: Listas
    - Corrige ubicación de sección de extender listas.
  - Django: Modelos
    - Corrige relaciones muchos a muchos.
  - Django: Interfaz administrativa
    - Amplía claves ajenas para admin.
  - Django: URLs
    - Completa URLs con expresiones regulares.
  - Django: Plantillas
    - Corrige etiquetas y filtros personalizados.
  - Django: Formularios
    - Corrigue validación.
  - Django: Estáticos
    - Corrige fallos menores en Bootstrap.
  - Django: Internacionalización
    - Amplía y corrige ficheros de idioma.

## Version 2.1.3

Publicada el 13-11-2025

Completa documentación de:
  - Entornos de desarrollo: Contexto real
    - Corrección sobre uv.
  - Estructuras de datos: Tuplas
    - Aclaración sobre funciones.
  - Django: Plantillas
    - Etiquetas personalizadas.
    - Filtros personalizados.
  - Django: Justfile
    - Receta para generar "secret key".

## Version 2.1.2

Publicada el 31-10-2025

Crea documentación de:
  - Estructuras de datos: Listas
    - splitlines()
  - Django: Vistas
    - Vistas de error personalizadas.
    - Decorando vistas.
  - Django: Índice de contenidos
  - Django: Interfaz administrativa
    - Campos de búsqueda.
    - Filtros de lista.
    - Campos autocompletados.
    - Acciones de administración.

Completa documentación de:
  - Estructuras de datos: Conjuntos
    - Teoría de conjuntos.
  - Django: URLs
    - Aclaración de conversores personalizados.
  - Django: Autenticación
    - Atributos del modelo User.

## Version 2.1.1

Publicada el 29-10-2025

Completa documentación de:
  - Fundamentos del lenguaje: Bucles.
  - Django: Autenticación.
  - Django: Modelos.
  - Django: Plantillas.
  - Django: Formularios.
  - Django: URLs.
  - Django: Middleware.
  - Django: Justfile.

## Version 2.1.0

Publicada el 24-10-2025

Crea documentación de:
  - API: Middleware.

Completa documentación de:
  - Fundamentos del lenguaje: Cadenas de texto.
  - Django: API.
  - Django: Autenticación.
  - Django: Estáticos.
  - Django: Extras.
  - Django: Formularios.
  - Django: Justfile.
  - Django: Modelos.
  - Django: Plantillas.
  - Django: Puesta en marcha.
  - Django: URLs.

## Version 2.0.10

Publicada el 08-10-2025

- Añade documentación específica de `justfile` para Django.
- Completa documentación de:
  - Fundamentos del lenguaje: Algo de historia.
  - Fundamentos del lenguaje: Números.
  - Fundamentos del lenguaje: Cadenas de texto.
  - Django: Aplicaciones.
  - Django: Formularios.
  - Django: Modelos.
  - Django: Puesta en marcha.
  - Django: Estáticos.

## Version 2.0.9

Publicada el 07-10-2025

- Completa documentación de:
  - Django: Modelos.
  - Django: URLs.
  - Django: Plantillas.
  - Django: Formularios.
  - Django: Estáticos.

## Version 2.0.8

Publicada el 06-10-2025

- Completa documentación de:
  - Django: Formularios.
  - Django: Plantillas.

## Version 2.0.7

Publicada el 03-10-2025

- Completa documentación de:
  - Django: Formularios.
  - Django: Plantillas.
  - Django: URLs.
  - Django: Vistas.
- Añade abreviaturas.

## Version 2.0.6

Publicada el 01-10-2025

- Completa documentación de:
  - Django: Puesta en marcha.
  - Django: Aplicaciones.
  - Django: Modelos.
  - Django: URLs.
  - Django: Vistas.

## Version 2.0.5

Publicada el 30-09-2025

- Completa documentación de:
  - Django: Puesta en marcha.
  - Django: Aplicaciones.
  - Django: Modelos.

## Version 2.0.4

Publicada el 29-09-2025

- Completa documentación de:
  - Django: Puesta en marcha.
  - Django: Aplicaciones.
  - Django: Modelos.

## Version 2.0.3

Publicada el 26-09-2025

- Completa documentación de:
  - Historia de la computación.
  - Hablando con la máquina.
  - Introducción a Python.
  - Puesta en marcha.

## Version 2.0.2

Publicada el 22-09-2025

- Completa documentación de:
    - Fundamentos del lenguaje.
    - Expresiones regulares.
    - sqlite.
    - Desarrollo web.

## Version 2.0.1

Publicada el 19-05-2025

- Añade bloques de contenido en la página principal.
- Añade `README.md` en el repositorio de código.


================================================
FILE: Dockerfile
================================================
FROM debian

RUN apt-get update && \
    apt-get install -y curl && \
    curl -LsSf https://astral.sh/uv/install.sh | sh

ENV UV_LINK_MODE=copy \
    PATH="/root/.local/bin:$PATH"

WORKDIR /docs
COPY . /docs

CMD ["uv", "run", "mkdocs", "build"]


================================================
FILE: README.md
================================================
# Aprende Python

Aprende el lenguaje de programación Python con este manual que cubre los fundamentos del lenguaje, módulos de la librería estándar y paquetes de terceros. Contiene multitud de ejercicios a través de pypas.es y está ajustado a distintos niveles de aprendizaje. Todos los recursos están disponibles vía web utilizando un diseño moderno y completamente gratuito.

![Promo](./promo.jpg)

**La guía definitiva en español para aprender tu lenguaje de programación favorito.**

© Sergio Delgado Quintero.


================================================
FILE: docs/assets/css/custom.css
================================================
/* 
  INSPIRATION: https://github.com/astral-sh/uv/blob/main/mkdocs.template.yml
  MATERIAL COLORS: https://github.com/squidfunk/mkdocs-material/blob/master/src/templates/assets/stylesheets/main/_colors.scss
  DARK & LIGHT STYLES: https://uxplanet.org/create-an-easily-switchable-light-dark-styles-in-figma-ffee3cd542a7
  PATTERNS BASED ON PRIMARY COLOR: https://mycolor.space/?hex=%23009485&sub=1
*/

/* LIGHT MODE */
[data-md-color-scheme="default"] {
  /* Modification of theme colors */
  /* --md-code-hl-comment-color: var(--md-primary-fg-color); */

  /* Custom colors */
  --blue-color: rgb(23, 143, 255);
  --green-color: rgb(29, 187, 53);
  --orange-color: rgb(254, 160, 47);
  --red-color: rgb(248, 65, 65);
  --yellow-color: rgb(216, 210, 25);
  --pink-color: rgb(177, 36, 137);
  /* Transparent custom colors */
  --blue-transparent-color: rgba(23, 143, 255, 0.5);
  --green-transparent-color: rgba(29, 187, 53, 0.5);
  --orange-transparent-color: rgba(254, 160, 47, 0.5);
  --red-transparent-color: rgba(248, 65, 65, 0.5);
  --yellow-transparent-color: rgba(216, 210, 25, 0.5);
  --pink-transparent-color: rgba(177, 36, 137, 0.5);

  .blue {
    color: var(--blue-color);
  }
  .green {
    color: var(--green-color);
  }
  .orange {
    color: var(--orange-color);
  }
  .red {
    color: var(--red-color);
  }
  .yellow {
    color: var(--yellow-color);
  }
  .pink {
    color: var(--pink-color);
  }
}

/* DARK MODE */
[data-md-color-scheme="slate"] {
  /* Modification of theme colors */
  /* --md-code-hl-comment-color: var(--md-accent-fg-color); */

  /* Custom colors */
  --blue-color: rgb(0, 133, 255);
  --green-color: rgb(32, 203, 73);
  --orange-color: rgb(249, 135, 0);
  --red-color: rgb(233, 44, 44);
  --yellow-color: rgb(231, 231, 21);
  --pink-color: rgb(196, 44, 153);

  /* Transparent custom colors */
  --blue-transparent-color: rgba(0, 133, 255, 0.5);
  --green-transparent-color: rgba(32, 203, 73, 0.5);
  --orange-transparent-color: rgba(249, 135, 0, 0.5);
  --red-transparent-color: rgba(233, 44, 44, 0.5);
  --yellow-transparent-color: rgba(231, 231, 21, 0.5);
  --pink-transparent-color: rgba(196, 44, 153, 0.5);

  .blue {
    color: var(--blue-color);
  }
  .green {
    color: var(--green-color);
  }
  .orange {
    color: var(--orange-color);
  }
  .red {
    color: var(--red-color);
  }
  .yellow {
    color: var(--yellow-color);
  }
  .pink {
    color: var(--pink-color);
  }
}

.hl {
  color: var(--md-primary-fg-color);
}

.acc {
  color: var(--md-accent-fg-color);
}

.mono {
  font-family: monospace;
}

.bold {
  font-weight: bold;
}

.md-nav__link--active {
  font-weight: bold;
}

@keyframes beat {
  0%,
  40%,
  80%,
  100% {
    transform: scale(1);
  }
  20%,
  60% {
    transform: scale(1.15);
  }
}
.beat {
  animation: beat 1000ms infinite;
}

@keyframes flip {
  /* https://animista.net/play/basic/flip */
  50% {
    transform: rotateY(-180deg);
  }
}
.flip {
  animation: flip 1500ms infinite both;
}

@keyframes slide {
  /* https://animista.net/play/basic/slide */
  0% {
    transform: translateX(-1px);
  }
  50% {
    transform: translateX(1px);
  }
  100% {
    transform: translateX(-1px);
  }
}
.slide {
  animation: slide 1000ms infinite both;
}

/* Custom exercise admonition */
:root {
  --md-admonition-icon--brain: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21.33 12.91c.09 1.55-.62 3.04-1.89 3.95l.77 1.49c.23.45.26.98.06 1.45-.19.47-.58.84-1.06 1l-.79.25a1.687 1.687 0 0 1-1.86-.55L14.44 18c-.89-.15-1.73-.53-2.44-1.1-.5.15-1 .23-1.5.23-.88 0-1.76-.27-2.5-.79-.53.16-1.07.23-1.62.22-.79.01-1.57-.15-2.3-.45a4.1 4.1 0 0 1-2.43-3.61c-.08-.72.04-1.45.35-2.11-.29-.75-.32-1.57-.07-2.33C2.3 7.11 3 6.32 3.87 5.82c.58-1.69 2.21-2.82 4-2.7 1.6-1.5 4.05-1.66 5.83-.37.42-.11.86-.17 1.3-.17 1.36-.03 2.65.57 3.5 1.64 2.04.53 3.5 2.35 3.58 4.47.05 1.11-.25 2.2-.86 3.13.07.36.11.72.11 1.09m-5-1.41c.57.07 1.02.5 1.02 1.07a1 1 0 0 1-1 1h-.63c-.32.9-.88 1.69-1.62 2.29.25.09.51.14.77.21 5.13-.07 4.53-3.2 4.53-3.25a2.59 2.59 0 0 0-2.69-2.49 1 1 0 0 1-1-1 1 1 0 0 1 1-1c1.23.03 2.41.49 3.33 1.3.05-.29.08-.59.08-.89-.06-1.24-.62-2.32-2.87-2.53-1.25-2.96-4.4-1.32-4.4-.4-.03.23.21.72.25.75a1 1 0 0 1 1 1c0 .55-.45 1-1 1-.53-.02-1.03-.22-1.43-.56-.48.31-1.03.5-1.6.56-.57.05-1.04-.35-1.07-.9a.97.97 0 0 1 .88-1.1c.16-.02.94-.14.94-.77 0-.66.25-1.29.68-1.79-.92-.25-1.91.08-2.91 1.29C6.75 5 6 5.25 5.45 7.2 4.5 7.67 4 8 3.78 9c1.08-.22 2.19-.13 3.22.25.5.19.78.75.59 1.29-.19.52-.77.78-1.29.59-.73-.32-1.55-.34-2.3-.06-.32.27-.32.83-.32 1.27 0 .74.37 1.43 1 1.83.53.27 1.12.41 1.71.4q-.225-.39-.39-.81a1.038 1.038 0 0 1 1.96-.68c.4 1.14 1.42 1.92 2.62 2.05 1.37-.07 2.59-.88 3.19-2.13.23-1.38 1.34-1.5 2.56-1.5m2 7.47-.62-1.3-.71.16 1 1.25zm-4.65-8.61a1 1 0 0 0-.91-1.03c-.71-.04-1.4.2-1.93.67-.57.58-.87 1.38-.84 2.19a1 1 0 0 0 1 1c.57 0 1-.45 1-1 0-.27.07-.54.23-.76.12-.1.27-.15.43-.15.55.03 1.02-.38 1.02-.92"/></svg>');
}
.md-typeset .admonition.exercise,
.md-typeset details.exercise {
  border-color: rgb(156, 18, 158);
}
.md-typeset .admonition.exercise,
.md-typeset details.exercise {
  background-color: rgba(156, 18, 158, 0.15);
}
.md-typeset .exercise > .admonition-title::before,
.md-typeset .exercise > summary::before {
  background-color: rgb(156, 18, 158);
  -webkit-mask-image: var(--md-admonition-icon--brain);
  mask-image: var(--md-admonition-icon--brain);
}

span.example {
  font-style: italic;
  font-weight: bold;
  color: var(--orange-color);
  span.twemoji {
    margin-left: -0.15rem;
    margin-right: -0.2rem;
    margin-top: 0.1rem;
  }
}

span.pyversion {
  span.version {
    font-size: smaller;
  }
  span.twemoji {
    margin-right: -0.1rem;
  }
}

span.dj-level {
  width: fit-content;
  display: block;
  font-size: smaller;
  color: rgb(230, 230, 230);
  padding: 0.03rem 0.3rem 0.03rem 0.3rem;
  border-radius: 0.2rem;
  background: gray;
}

/* https://github.com/squidfunk/mkdocs-material/issues/3453#issuecomment-2407192662 */
.md-tooltip {
  top: inherit;
  left: inherit;
  margin-left: 12px;
  margin-top: 7px;
}

/* https://mkdocstrings.github.io/recipes/#prevent-selection-of-prompts-and-output-in-python-code-blocks */
.highlight .gp,
.highlight .gt,
.highlight .gr,
.highlight .go {
  /* Generic.Prompt, Generic.Output */
  user-select: none;
}

.white {
  background-color: white;
  display: inline-block;
}

.uv {
  color: #de5fe9;
}

/* Fix icon alignment inside links */

a span.twemoji {
  vertical-align: middle !important;
}


================================================
FILE: docs/assets/js/clipboard.js
================================================
document.addEventListener("DOMContentLoaded", function () {
  document.body.addEventListener("click", function (event) {
    // Verifica si se hizo clic en un botón de copiar
    const copyButton = event.target.closest("button.md-code__button");
    if (!copyButton) return;

    // Encuentra el bloque de código asociado
    const preBlock = copyButton.closest("pre");
    const codeBlock = preBlock ? preBlock.querySelector("code") : null;
    if (!codeBlock) return;

    // Obtiene el texto del bloque de código
    const selectedLines = codeBlock.querySelectorAll("span.hll");
    if (selectedLines.length > 0) {
      // Si hay líneas resaltadas, copia solo esas líneas
      var codeText = Array.from(selectedLines)
        .map((line) => line.textContent || line.innerText)
        .join("\n");
    } else {
      // Si no hay líneas resaltadas, copia todo el bloque de código
      var codeText = codeBlock.textContent || codeBlock.innerText;
    }

    // Filtra y elimina los prompts ">>>", "...", y "$" al inicio de las líneas
    codeText = codeText
      .split("\n")
      .filter(
        (line) =>
          line.startsWith(">") || // Contempla ">>>" y ">"
          line.startsWith("...") ||
          line.startsWith("$"),
      ) // Solo instrucciones
      .map((line) => line.replace(/^(>+|\.\.\.|\$)\s?/, "")) // Elimina ">", ">>>", "..." o "$"
      .join("\n");

    // Si el bloque no es de sesión interactiva o Bash, copia todo el contenido
    if (codeText.trim() === "") {
      // Limpia las líneas resaltadas (hl_lines)
      codeText = codeBlock.textContent || codeBlock.innerText; // Usamos textContent para evitar problemas de espaciado
    }

    // Copia el código limpio al portapapeles
    navigator.clipboard.writeText(codeText).then(() => {
      // Opcional: Mostrar feedback visual en el botón
      copyButton.classList.add("copied");
      setTimeout(() => copyButton.classList.remove("copied"), 1000);
    });

    // Evita que el evento siga propagándose y se copie el código original
    event.stopPropagation();
    event.preventDefault();
  });
});


================================================
FILE: docs/assets/js/mathjax.js
================================================
window.MathJax = {
  tex: {
    inlineMath: [["\\(", "\\)"]],
    displayMath: [["\\[", "\\]"]],
    processEscapes: true,
    processEnvironments: true,
  },
  options: {
    ignoreHtmlClass: ".*|",
    processHtmlClass: "arithmatex",
  },
};

document$.subscribe(() => {
  MathJax.startup.output.clearCache();
  MathJax.typesetClear();
  MathJax.texReset();
  MathJax.typesetPromise();
});

component$.subscribe(({ ref }) => {
  if (ref.classList.contains("md-annotation")) MathJax.typesetPromise([ref]);
});


================================================
FILE: docs/core/controlflow/conditionals.md
================================================
---
icon: material/call-split
tags:
  - Fundamentos del lenguaje
  - Control de flujo
  - Condicionales
---

# Condicionales { #conditionals }

![Banner](images/conditionals/banner.jpg)
/// caption
Imagen generada con Inteligencia Artificial
///

En esta sección estudiaremos las sentencias `if` y `match-case` de _Python_ junto a las distintas variantes que pueden asumir, pero antes de eso introduciremos algunas cuestiones generales de _escritura de código_.

## Definición de bloques { #blocks }

A diferencia de otros lenguajes que utilizan _llaves_ para definir los bloques de código (véase C o Java), cuando Guido Van Rossum [diseñó Python](../introduction/python.md#python) quiso evitar estos caracteres por considerarlos innecesarios.

Es por ello que en Python los bloques de código se definen a través de **espacios en blanco**, preferiblemente :four: espacios en blanco.[^1]

![Dark image](images/conditionals/four-spaces-dark.svg#only-dark)
![Light image](images/conditionals/four-spaces-light.svg#only-light)

!!! tip "Adaptación"

    Esto puede resultar extraño e (incluso) 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.

## Comentarios { #comments }

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 `#!python #` y comprenden desde ahí hasta el final de la línea.

```python
# Universe age expressed in days
universe_age = 13800 * (10 ** 6) * 365
```

Los comentarios también pueden aparecer en la misma línea de código, aunque la [guía de estilo de Python](https://www.python.org/dev/peps/pep-0008/#inline-comments) no aconseja usarlos en demasía:

```python
stock = 0   # Release additional articles
```

Reglas para escribir buenos comentarios[^2]:

1. Los comentarios no deberían duplicar el código.
2. Los buenos comentarios no arreglan un código poco claro.
3. Si no puedes escribir un comentario claro, puede haber un problema en el código.
4. Los comentarios deberían evitar la confusión, no crearla.
5. Usa comentarios para explicar código no idiomático.
6. Proporciona enlaces a la fuente original del código copiado.
7. Incluye enlaces a referencias externas que sean de ayuda.
8. Añade comentarios cuando arregles errores.
9. Usa comentarios para destacar implementaciones incompletas.

## Ancho del código { #code-width }

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](https://www.python.org/dev/peps/pep-0008/#maximum-line-length) es de **80 caracteres**.

Sin embargo, esto genera una [cierta controversia](https://richarddingwall.name/2008/05/31/is-the-80-character-line-limit-still-relevant/) 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 con este límite y otras más flexibles.

En caso de que queramos **romper una línea de código** demasiado larga, tenemos dos opciones:

=== "Usar la _barra invertida_ "

    ```pycon
    >>> factorial = factorial(n) * factorial(n - 1) * factorial(n - 2) * factorial(n -3) * factorial(n - 4) * factorial(n - 5)
    
    >>> factorial = factorial(n) * \
    ...             factorial(n - 1) * \
    ...             factorial(n - 2) * \
    ...             factorial(n - 3) * \
    ...             factorial(n - 4) * \
    ...             factorial(n - 5)
    ```

=== "Usar los _paréntesis_"

    ```pycon
    >>> factorial = factorial(n) * factorial(n - 1) * factorial(n - 2) * factorial(n -3) * factorial(n - 4) * factorial(n - 5)
    
    >>> factorial = (factorial(n - 1) *
    ...              factorial(n - 2) *
    ...              factorial(n - 3) *
    ...              factorial(n - 4) *
    ...              factorial(n - 5))
    ```

## La sentencia `if` { #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 final de la línea.

Veamos un <span class="example">ejemplo:material-flash:</span>:

```pycon
>>> temperature = 40

>>> if temperature > 35:
...     print('Aviso por alta temperatura')
...
Aviso por alta temperatura
```

!!! note "Paréntesis"

    Nótese que en Python no es necesario incluir paréntesis `(` y `)` al escribir condiciones. Hay ocasiones que es recomendable por claridad o por establecer prioridades.

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 <span class="example">ejemplo:material-flash:</span> anterior pero añadiendo esta variante:

```pycon
>>> temperature = 20

>>> if temperature > 35:
...     print('Aviso por alta temperatura')
... else:
...     print('Parámetros normales')
...
Parámetros normales
```

Podríamos tener incluso condiciones dentro de condiciones, lo que se viene a llamar técnicamente **condiciones anidadas**[^3].

Veamos un <span class="example">ejemplo:material-flash:</span> ampliando el caso anterior:

```pycon
>>> temperature = 28

>>> if temperature < 20:
...     if temperature < 10:
...         print('Nivel azul')
...     else:
...         print('Nivel verde')
... else:
...     if temperature < 30:
...         print('Nivel naranja')
...     else:
...         print('Nivel rojo')
...
Nivel naranja
```

Python nos ofrece una mejora en la escritura de condiciones anidadas cuando aparecen consecutivamente un `else` y un `if`. Podemos sustituirlos por la sentencia `elif`:

![Dark image](images/conditionals/elif-dark.svg#only-dark)
![Light image](images/conditionals/elif-light.svg#only-light)

Apliquemos esta mejora al código del <span class="example">ejemplo:material-flash:</span> anterior:

```pycon
>>> temperature = 28

>>> if temperature < 20:
...     if temperature < 10:
...         print('Nivel azul')
...     else:
...         print('Nivel verde')
... elif temperature < 30:
...     print('Nivel naranja')
... else:
...     print('Nivel rojo')
...
Nivel naranja
```

## Asignaciones condicionales { #if-assignments }

Supongamos que queremos asignar un nivel de riesgo de incendio en función de la temperatura.

En su ^^versión clásica^^ escribiríamos algo como:

```pycon
>>> temperature = 35

>>> if temperature < 30:
...     fire_risk = 'LOW'
... else:
...     fire_risk = 'HIGH'
...

>>> fire_risk
'HIGH'
```

Sin embargo, esto lo podríamos abreviar con una **asignación condicional de una única línea**:

```pycon
>>> fire_risk = 'LOW' if temperature < 30 else 'HIGH'

>>> fire_risk
'HIGH'
```

:material-check-all:{ .blue } Con la experiencia, este tipo de construcciones cada vez son más utilizadas ya que condensan información pero mantienen legibilidad.

## Operadores de comparación { #comparation-operators }

Cuando escribimos condiciones debemos incluir alguna expresión de comparación. Para usar estas expresiones es fundamental conocer los **operadores** que nos ofrece Python:

|     Operador      |    Símbolo    |
| ----------------- | ------------- |
| Igualdad          | `#!python ==` |
| Desigualdad       | `#!python !=` |
| Menor que         | `#!python <`  |
| Menor o igual que | `#!python <=` |
| Mayor que         | `#!python >`  |
| Mayor o igual que | `#!python >=` |

A continuación veremos una serie de **_ejemplos_**{ .orange }:material-flash:{ .orange } 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:

```pycon
>>> value = 8

>>> value == 8
True

>>> value != 8
False

>>> value < 12
True

>>> value <= 7
False

>>> value > 4
True

>>> value >= 9
False
```

Python ofrece la posibilidad de ver si un valor está entre dos límites de una manera muy sencilla.

Así, por <span class="example">ejemplo:material-flash:</span>, para descubrir si $x \in [4, 12]$ haríamos:

```pycon
>>> 4 <= x <= 12
True
```

!!! note "Notas"

    1. Una expresión de comparación siempre devuelve un valor «booleano», es decir `#!python True` o `#!python False`.
    2. El uso de paréntesis, en función del caso, puede aclarar la expresión de comparación.

## Operadores lógicos { #logical-operators }

Podemos escribir condiciones más complejas usando los **operadores lógicos**:

| Operador  |    Símbolo     |
| --------- | -------------- |
| «Y» lógico  | `#!python and` |
| «O» lógico  | `#!python or`  |
| «No» lógico | `#!python not` |

A continuación veremos una serie de **_ejemplos_**{ .orange }:material-flash:{ .orange } con expresiones lógicas. 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:

```pycon
>>> x = 8

>>> x > 4 or x > 12  # True or False
True

>>> x < 4 or x > 12  # False or False
False

>>> x > 4 and x > 12  # True and False
False

>>> x > 4 and x < 12  # True and True
True

>>> not(x != 8)  # not False
True
```

Véanse las **tablas de la verdad** para cada operador lógico:

![Tablas de la verdad](images/conditionals/truth-tables.svg)

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `leap-year`

### Cortocircuito lógico { #short-circuit }

Es interesante comprender que las expresiones lógicas **no se evalúan por completo si se dan una serie de circunstancias**. Aquí es donde surge el concepto de ^^cortocircuito^^ (lógico) que no es más que una forma de identificar este escenario.

Supongamos un <span class="example">ejemplo:material-flash:</span> en el que utilizamos un **teléfono móvil** que mide su nivel de batería mediante la variable `power` con valores que van desde 0% a 100% y su cobertura 4G mediante la variable `signal_4g` que va desde 0% a 100%.

=== "Cortocircuito AND :material-gate-and:"

    Para poder ^^enviar un mensaje^^ por Telegram el teléfono necesita tener al menos un 25% de batería y al menos un 10% de cobertura:

    ```pycon
    >>> power = 10
    >>> signal_4g = 60

    >>> power > 25 and signal_4g > 10
    False
    ```

    ``` mermaid
    graph LR
        and{<tt>and</tt>}
        power(<tt>power > 25</tt>)
        signal(<tt>signal_4g > 10</tt>)
        result(((<tt>False</tt>)))
        power -- <tt>False</tt> --> and
        and -.-> signal
        and ==> result
    ```

    :material-check-all:{ .blue } Dado que estamos en un `#!python and` y la primera condición `#!python 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 `#!python False`.

=== "Cortocircuito OR :material-gate-or:"

    Para poder ^^hacer una llamada VoIP^^ necesitamos tener al menos un 40% de batería o al menos un 30% de cobertura:

    ```pycon
    >>> power = 50
    >>> signal_4g = 20
    
    >>> power > 40 or signal_4g > 30
    True
    ```

    ``` mermaid
    graph LR
        or{<tt>or</tt>}
        power(<tt>power > 40</tt>)
        signal(<tt>signal_4g > 30</tt>)
        result(((<tt>True</tt>)))
        power -- <tt>True</tt> --> or
        or -.-> signal
        or ==> result
    ```

    :material-check-all:{ .blue } Dado que estamos en un `#!python or` y la primera condición `#!python power > 40` 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 `#!python True`.

!!! note "Evaluación"

    Si no se produjera un cortocircuito en la evaluación de la expresión, se seguiría comprobando todas las condiciones posteriores hasta llegar al final de la misma.

### «Booleanos» en condiciones { #boolean }

Cuando queremos preguntar por la **veracidad** de una determinada variable «booleana» en una condición, la primera aproximación que parece razonable es usar lo que ya conocemos.

Veamos un <span class="example">ejemplo:material-flash:</span>:

```pycon hl_lines="3"
>>> is_cold = True

>>> if is_cold == True:#(1)!
...     print('Coge chaqueta')
... else:
...     print('Usa camiseta')
...
Coge chaqueta
```
{ .annotate }

1. :fontawesome-solid-triangle-exclamation:{ .red } No es la manera ~~correcta~~ pitónica.

Pero la manera «obvia» de hacerlo en Python es la siguiente:

```pycon hl_lines="1"
>>> if is_cold:
...     print('Coge chaqueta')
... else:
...     print('Usa camiseta')
...
Coge chaqueta
```

Hemos visto una comparación para un valor «booleano» verdadero (`#!python True`). En el caso de que la comparación fuera para un valor falso lo haríamos así:

```pycon
>>> is_cold = False

>>> if not is_cold:#(1)!
...     print('Usa camiseta')
... else:
...     print('Coge chaqueta')
...
Usa camiseta
```
{ .annotate }

1. :material-approximately-equal: `#!python if is_cold == False:`

De hecho, si lo pensamos, estamos reproduciendo bastante bien el _lenguaje natural_:

- Si hace frío :material-arrow-right-box: coge chaqueta.
- Si no hace frío :material-arrow-right-box: usa camiseta.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `marvel-akinator`

### Valor nulo { #none }

`#!python None` es un valor especial de Python que almacena el **valor nulo**[^4]. Veamos cómo se comporta al incorporarlo en condiciones de veracidad.

Veamos un sencillo <span class="example">ejemplo:material-flash:</span> para ilustrar su comportamiento:

```pycon
>>> value = None

>>> if value:
...     print('Value has some useful value')
... else:
...     print('Value seems to be void')#(1)!
...
Value seems to be void
```
{ .annotate }

1. `value` podría contener `#!python None`, `#!python False` o cualquier otra expresión cuya veracidad fuera falsa.

Para distinguir `#!python None` de los valores propiamente booleanos, se recomienda el uso del operador `#!python is`:

```pycon hl_lines="3"
>>> value = None

>>> if value is None:
...     print('Value is clearly None')
... else:
...     print('Value has some useful value')
...
Value is clearly None
```

De igual forma, podemos usar esta construcción para el caso contrario. La forma «pitónica» de preguntar **si algo no es nulo** es la siguiente:

```pycon
>>> value = 99

>>> if value is not None:
...     print(f'{value=}')
...
value=99
```

#### ¿Por qué usar `is`? { #why-is }

Cabe preguntarse por qué utilizamos `#!python is` en vez del operador `#!python ==` al comprobar si un valor es nulo, ya que ambas aproximaciones nos dan el mismo resultado[^5]:

```pycon
>>> value = None

>>> value is None
True

>>> value == None
True
```

La respuesta es que el operador `#!python is` comprueba únicamente si los identificadores (posiciones en memoria) de dos objetos son iguales, mientras que la comparación `#!python ==` puede englobar [muchas otras acciones](../modularity/oop.md#magic-methods). De este hecho se deriva que su ejecución sea mucho más rápida y que se eviten «falsos positivos».

Cuando ejecutamos un programa Python existe una serie de ^^objetos precargados en memoria^^. Uno de ellos es `#!python None`.

Lo podemos comprobar con el siguiente <span class="example">ejemplo:material-flash:</span>:

```pycon
>>> id(None)
4314501456
```

Cualquier variable que igualemos al valor nulo, únicamente será una referencia al mismo objeto `#!python None` en memoria:

```pycon
>>> value = None

>>> id(value)
4314501456
```

Por lo tanto, ver si un objeto es `#!python None` es simplemente comprobar que su «id» coincida con el de `#!python None`, que es exactamente el cometido del operador `#!python is`:

```pycon
>>> id(value) == id(None)
True

>>> value is None
True
```

## Veracidad { #truthiness }

Cuando trabajamos con expresiones que incorporan valores «booleanos», se produce una [conversión implícita](../datatypes/numbers.md#implicit-typecast) que transforma los tipos de datos involucrados a valores `#!python True` o `#!python False`.

Lo primero que debemos entender de cara a comprobar la **veracidad** son los valores que evalúan a falso o evalúan a verdadero.

A continuación se muestra un listado de los **únicos items** que evalúan a `#!python False` en Python:

```pycon
>>> bool(False)
False

>>> bool(None)
False

>>> bool(0)
False

>>> bool(0.0)
False

>>> bool('')#(1)!
False

>>> bool([])#(2)!
False

>>> bool(())#(3)!
False

>>> bool({})#(4)!
False

>>> bool(set())#(5)!
False
```
{ .annotate }

1. La cadena vacía.
2. La lista vacía.
3. La tupla vacía.
4. El diccionario vacío.
5. El conjunto vacío.

:material-check-all:{ .blue } El resto de objetos en Python evalúan a `#!python True`.

Veamos algunos <span class="example">ejemplos:material-flash:</span> de objetos que evalúan a `#!python True` en Python:

```pycon
>>> bool('False')
True

>>> bool(' ')
True

>>> bool(1e-10)
True

>>> bool([0])
True

>>> bool('🦆')
True
```

### Asignación lógica { #logical-assignment }

Es posible utilizar [operadores lógicos](#logical-operators) en sentencias de asignación sacando partido de las tablas de la verdad que funcionan para estos casos.

=== "Asignación mediante AND :material-gate-and:"

    Veamos un <span class="example">ejemplo:material-flash:</span> de asignación lógica utilizando el operador `#!python and`:

    ```pycon
    >>> b = 0
    >>> c = 5
    
    >>> a = b and c#(1)!
    
    >>> a
    0
    ```
    { .annotate }
    
    1. Se trata de una expresión lógica en la que se aplica conversión implícita de los valores enteros a valores «booleanos». En este sentido el valor de `b` evalúa a `#!python False` ya que es 0. Al estar usando un operador `#!python and` se produce un [cortocircuito lógico:material-flash-outline:](#short-circuit) y se asigna el valor de la `b` a la variable `a`.

=== "Asignación mediante OR :material-gate-or:"

    Veamos un <span class="example">ejemplo:material-flash:</span> de asignación lógica utilizando el operador `#!python or`:

    ```pycon hl_lines="4"
    >>> b = 0
    >>> c = 5

    >>> a = b or c#(1)!

    >>> a
    5
    ```
    { .annotate }

    1. Se trata de una expresión lógica en la que se aplica conversión implícita de los valores enteros a valores «booleanos». En este sentido el valor de `b` evalúa a `#!python False` ya que es 0. Al estar usando un operador `#!python or` se continúa a la segunda parte donde el valor de la variable `c` evalúa a `#!python True` ya que es 5. Por tanto se asigna dicho valor a la variable `a`.
        
## Sentencia `match-case` { #match-case }

Una de las novedades más esperadas de <span class="pyversion"><a href="https://docs.python.org/3.10/">Python <span class="version">:octicons-tag-24: 3.10</span></a></span> fue el llamado [Structural Pattern Matching](https://peps.python.org/pep-0636/) 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 { #comparing-values }

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 «if» encadenadas.

Veamos esta primera aproximación mediante un <span class="example">ejemplo:material-flash:</span> donde mostramos un color a partir de su [codificación RGB](https://es.wikipedia.org/wiki/RGB#Codificaci%C3%B3n_hexadecimal_del_color):

```pycon
>>> color = '#FF0000'

>>> match color:
...     case '#FF0000':
...         print('🔴')
...     case '#00FF00':
...         print('🟢')
...     case '#0000FF':
...         print('🔵')
...
🔴
```

¿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:

```pycon
>>> color = '#AF549B'

>>> match color:
...     case '#FF0000':
...         print('🔴')
...     case '#00FF00':
...         print('🟢')
...     case '#0000FF':
...         print('🔵')
...     case _:
...         print('Unknown color!')
...
Unknown color!
```

Hay que tener cuidado con un detalle. Si estás pensando en usar constantes para definir los valores que puede tomar el color, que sepas que esto te va a fallar:

```pycon
>>> RED_HEXA = '#FF0000'
>>> GREEN_HEXA = '#00FF00'
>>> BLUE_HEXA = '#0000FF'

>>> match color:
...     case RED_HEXA:
...         print('🔴')
...     case GREEN_HEXA:
...         print('🟢')
...     case BLUE_HEXA:
...         print('🔵')
...     case _:
...         print('Unknown color!')
  Cell In[4], line 2
    case RED_HEXA:
         ^
SyntaxError: name capture 'RED_HEXA' makes remaining patterns unreachable
```

Esto se debe a que Python trata a las constantes `#!python RED_HEXA GREEN_HEXA BLUE_HEXA` como nombres de variables y trata de aplicar el [patrón de captura](https://peps.python.org/pep-0634/#capture-patterns) sobre `match-case`[^6].

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `simple-op`

### Patrones avanzados { #advanced-patterns }

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.

Vamos a plantear un <span class="example">ejemplo:material-flash:</span> donde partimos de una [tupla](../datastructures/tuples.md) 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:

```pycon
>>> point = (2, 5)

>>> match point:
...     case (x, y):
...         print(f'({x},{y}) is in plane')
...     case (x, y, z):
...         print(f'({x},{y},{z}) is in space')
...
(2,5) is in plane

>>> point = (3, 1, 7)

>>> match point:
...     case (x, y):
...         print(f'({x},{y}) is in plane')
...     case (x, y, z):
...         print(f'({x},{y},{z}) is in space')
...
(3,1,7) is in space
```

Sin embargo esta aproximación permitiría tratar un punto formado por cadenas de texto...

```pycon
>>> point = ('2', '5')

>>> match point:
...     case (x, y):
...         print(f'({x},{y}) is in plane')
...     case (x, y, z):
...         print(f'({x},{y},{z}) is in space')
...
(2,5) is in plane
```

Por tanto debemos restringir nuestros patrones a valores enteros:

=== "Strings bajo control"

    ```pycon
    >>> point = ('2', '5')
    
    >>> match point:
    ...     case (int(), int()):
    ...         print(f'{point} is in plane')
    ...     case (int(), int(), int()):
    ...         print(f'{point} is in space')
    ...     case _:
    ...         print('Unknown!')
    ...
    Unknown!
    ```

=== "Sigue funcionando con enteros"

    ```pycon
    >>> point = (3, 9, 1)
    
    >>> match point:
    ...     case (int(), int()):
    ...         print(f'{point} is in plane')
    ...     case (int(), int(), int()):
    ...         print(f'{point} is in space')
    ...     case _:
    ...         print('Unknown!')
    ...
    (3, 9, 1) is in space
    ```

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:

```pycon
>>> point = (8, 3, 5)

>>> match point:
...     case (int(x), int(y)):
...         dist_to_origin = (x ** 2 + y ** 2) ** (1 / 2)
...     case (int(x), int(y), int(z)):
...         dist_to_origin = (x ** 2 + y ** 2 + z ** 2) ** (1 / 2)
...     case _:
...         print('Unknown!')
...

>>> dist_to_origin
9.899494936611665
```

Con este enfoque, nos aseguramos que los puntos de entrada deben tener todas sus coordenadas como valores enteros:

```pycon
>>> point = ('8', 3, 5)  # Nótese el 8 como "string"

>>> match point:
...     case (int(x), int(y)):
...         dist_to_origin = (x ** 2 + y ** 2) ** (1 / 2)
...     case (int(x), int(y), int(z)):
...         dist_to_origin = (x ** 2 + y ** 2 + z ** 2) ** (1 / 2)
...     case _:
...         print('Unknown!')
...
Unknown!
```

Cambiando de <span class="example">ejemplo:material-flash:</span>, veamos un fragmento de código en el que tenemos que **comprobar la estructura de un bloque de autenticación** definido mediante un [diccionario](../datastructures/dicts.md). Los métodos válidos de autenticación son únicamente dos: bien usando nombre de usuario y contraseña, o bien usando correo electrónico y «token» de acceso. Además, los valores deben venir en formato cadena de texto:

```pycon
>>> auths = [
...     {'username': 'sdelquin', 'password': '1234'},
...     {'email': 'sdelquin@gmail.com', 'token': '4321'},
...     {'email': 'test@test.com', 'password': 'ABCD'},
...     {'username': 'sdelquin', 'password': 1234}
... ]

>>> for auth in auths:
...     print(auth)
...     match auth:
...         case {'username': str(username), 'password': str(password)}:
...             print('Authenticating with username and password')
...             print(f'{username}: {password}')
...         case {'email': str(email), 'token': str(token)}:
...             print('Authenticating with email and token')
...             print(f'{email}: {token}')
...         case _:
...             print('Authenticating method not valid!')
...     print('---')
...
{'username': 'sdelquin', 'password': '1234'}
Authenticating with username and password
sdelquin: 1234
---
{'email': 'sdelquin@gmail.com', 'token': '4321'}
Authenticating with email and token
sdelquin@gmail.com: 4321
---
{'email': 'test@test.com', 'password': 'ABCD'}
Authenticating method not valid!
---
{'username': 'sdelquin', 'password': 1234}
Authenticating method not valid!
---
```

Aún un último <span class="example">ejemplo:material-flash:</span> que determina, dada la edad de una persona, si puede o no beber alcohol:

```pycon
>>> age = 21

>>> match age:
...     case 0 | None:#(1)!
...         print('Not a person')
...     case n if n < 17:#(2)!
...         print('Nope')
...     case n if n < 22:#(3)!
...         print('Not in the US')
...     case _:
...         print('Yes')
...
Not in the US
```
{ .annotate }

1. Nótese el uso del OR...
2. Uso de condicional en la propia expresión.
3. Uso de condicional en la propia expresión.

## Operador morsa { #walrus }

<span class="pyversion"><a href="https://docs.python.org/3.8/">Python <span class="version">:octicons-tag-24: 3.8</span></a></span> introdujo el ^^polémico^^ [operador morsa](https://peps.python.org/pep-0572/)[^7] `#!python :=` que permitía unificar sentencias de asignación dentro de expresiones.

Supongamos un <span class="example">ejemplo:material-flash:</span> en el que computamos el **perímetro de una circunferencia**, indicando al usuario que debe incrementarlo siempre y cuando no llegue a un mínimo establecido.

=== "Versión clásica"

    ```pycon
    >>> radius = 4.25
    ... perimeter = 2 * 3.14 * radius
    ... if perimeter < 100:
    ...     print('Increase radius to reach minimum perimeter')
    ...     print('Actual perimeter: ', perimeter)
    ...
    Increase radius to reach minimum perimeter
    Actual perimeter:  26.69
    ```

=== "Versión con operador morsa"

    ```pycon hl_lines="2"
    >>> radius = 4.25
    ... if (perimeter := 2 * 3.14 * radius) < 100:
    ...     print('Increase radius to reach minimum perimeter')
    ...     print('Actual perimeter: ', perimeter)
    ...
    Increase radius to reach minimum perimeter
    Actual perimeter:  26.69
    ```

!!! tip "Equilibro"

    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.

??? danger "Renuncia de Guido van Rossum"

    La adopción del «walrus operator» en el lenguaje fue una de las polémicas más polarizadas en la historia reciente de Python. Tal es así, que al día siguiente de que Guido van Rossum aceptara su introducción en el lenguaje, tuvo un aluvión de críticas que colmaron la paciencia del creador holandés. Así las cosas, Guido escribió [esta carta](https://www.mail-archive.com/python-committers@python.org/msg05628.html) abandonando su puesto como líder y transfiriendo su poder de decisión sobre Python; y terminaba con un «I'm tired, and need a very long break.»

## Ejercicios { #exercises }

1. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `rps`
2. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `min3values`
3. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `blood-donation`
4. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `facemoji`
5. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `shortcuts`



[^1]: Reglas de indentación definidas en [PEP 8](https://www.python.org/dev/peps/pep-0008/#indentation).
[^2]: Fuente: [Best practices for writing code comments](https://stackoverflow.blog/2021/12/23/best-practices-for-writing-code-comments/)
[^3]: El anidamiento (o «nesting») hace referencia a incorporar sentencias unas dentro de otras mediante la inclusión de diversos niveles de profunidad (indentación).
[^4]: El valor nulo se conoce en otros lenguajes de programación como `nil`, `null`, `nothing`, ...
[^5]: Uso de `#!python is` en comparación de valores nulos explicada [aquí](https://jaredgrubb.blogspot.com/2009/04/python-is-none-vs-none.html) por Jared Grubb.
[^6]: El error está perfectamente analizado en [esta respuesta de StackOverflow](https://stackoverflow.com/a/67525259).
[^7]: Se denomina así porque el operador `#!python :=` tiene similitud con los colmillos de una morsa.


================================================
FILE: docs/core/controlflow/index.md
================================================
# Control de flujo

El control de flujo en programación se refiere a la manera en que se determina el orden en que se ejecutan las instrucciones de un programa. En lugar de seguir una secuencia lineal, los programas pueden tomar decisiones, repetir bloques de código o desviar la ejecución según ciertas condiciones. Esto se logra mediante estructuras como condicionales (if, else), bucles (for, while) y declaraciones de control como break o continue. Dominar el control de flujo es clave para desarrollar programas dinámicos y adaptables, capaces de responder a distintos escenarios. En este capítulo, aprenderás cómo utilizar estas estructuras en Python para crear algoritmos más complejos y eficientes.


================================================
FILE: docs/core/controlflow/loops.md
================================================
---
icon: material/dots-circle
tags:
  - Fundamentos del lenguaje
  - Control de flujo
  - Bucles
---

# Bucles { #loops }

![Banner](images/loops/banner.jpg)
/// caption
Imagen generada con Inteligencia Artificial
///

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.

## La sentencia `while` { #while }

El primer mecanismo que existe en Python para repetir instrucciones es usar la sentencia `#!python while`. La semántica tras esta sentencia es: «Mientras se cumpla una condición[^1] haz algo».

Veamos un primer <span class="example">ejemplo:material-flash:</span> con un sencillo bucle que repite un saludo mientras así se desee:

```pycon hl_lines="3"
>>> want_greet = 'S'#(1)!

>>> while want_greet == 'S':#(2)!
...     print('Hola qué tal!')
...     want_greet = input('¿Quiere otro saludo? [S/N] ')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] N
Que tenga un buen día
```
{ .annotate }

1. Importante dar un valor inicial.
2.  - La condición del bucle se comprueba en cada nueva **iteración** (repetición).
    - En este caso chequeamos que la variable `want_greet`  sea igual a `#!python 'S'`.

!!! note "Iteración"

    En este contexto, llamamos **iteración** a cada «repetición» del bucle. **Iterar** significa «repetir» una determinada acción.

### Romper un bucle while { #while-break }

Python ofrece la posibilidad de romper o finalizar un bucle antes de que se cumpla la condición de parada.

Supongamos que en el <span class="example">ejemplo:material-flash:</span> anterior establecemos **un máximo de 4 saludos**:

```pycon hl_lines="11"
>>> MAX_GREETS = 4

>>> num_greets = 0
>>> want_greet = 'S'

>>> while want_greet == 'S':
...     print('Hola qué tal!')
...     num_greets += 1
...     if num_greets == MAX_GREETS:
...         print('Número máximo de saludos alcanzado')
... ┌────── break
... ↓   want_greet = input('¿Quiere otro saludo? [S/N] ')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
Número máximo de saludos alcanzado
Que tenga un buen día
```

Como hemos visto en este ejemplo, `#!python break` nos permite finalizar 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.

??? abstract "Solución alternativa"

    Otra forma de resolver este ejercicio sería incorporar la (segunda) condición al bucle:

    ```python
    while want_greet == 'S' and num_questions < MAX_GREETS:
        ...
    ```

#### Comprobar la rotura { #while-else }

Python nos ofrece la posibilidad de **detectar si el bucle ha acabado de forma ordinaria**, esto es, ha finalizado por no cumplirse la condición establecida.

Para ello podemos hacer uso de la sentencia `#!python else` como parte del propio bucle. Si el bucle `#!python while` finaliza normalmente (sin llamada a `#!python break`) el flujo de control pasa a la sentencia opcional `#!python else`.

Veamos su comportamiento siguiendo con el <span class="example">ejemplo:material-flash:</span> que venimos trabajando:

```pycon hl_lines="13"
>>> MAX_GREETS = 4

>>> num_greets = 0
>>> want_greet = 'S'

>>> while want_greet == 'S':
...     print('Hola qué tal!')
...     num_greets += 1
...     if num_greets == MAX_GREETS:
...         print('Máximo número de saludos alcanzado')
...         break
...     want_greet = input('¿Quiere otro saludo? [S/N] ')
... else:#(1)!
...     print('Usted no quiere más saludos')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] N
Usted no quiere más saludos
Que tenga un buen día
```
{ .annotate }

1. El flujo de control entrará por aquí cuando `want_greet` sea distinto de `#!python 'S'` y por tanto no se cumpla la condición del bucle.

!!! warning "Contexto"

    La sentencia `#!python else` sólo tiene sentido en aquellos **bucles** que contienen un `#!python break`.

### Continuar un bucle while { #while-continue }

Hay situaciones en las que, en vez de romper un bucle, nos interesa **saltar adelante hacia la siguiente iteración**.

Para ello Python nos ofrece la sentencia `#!python continue` que hace precisamente eso, descartar el resto del código del bucle y saltar a la siguiente iteración.

Continuamos con el <span class="example">ejemplo:material-flash:</span> anterior pero ahora vamos a **contar el número de respuestas válidas**:

```pycon hl_lines="10"
>>> want_greet = 'S'
>>> valid_options = 0

>>> while want_greet == 'S':
... ↑   print('Hola qué tal!')
... │   want_greet = input('¿Quiere otro saludo? [S/N] ')
... │   if want_greet not in 'SN':#(1)!
... │       print('No le he entendido pero le saludo')
... │       want_greet = 'S'#(2)!
... └─────  continue#(3)!
...     valid_options += 1
... print(f'{valid_options} respuestas válidas')
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] A
No le he entendido pero le saludo
Hola qué tal!
¿Quiere otro saludo? [S/N] B
No le he entendido pero le saludo
Hola qué tal!
¿Quiere otro saludo? [S/N] N
2 respuestas válidas
Que tenga un buen día
```
{ .annotate }

1. Comprobamos si la entrada es un «sí» o un «no».
2. Asignamos «sí» a la opción para que el bucle pueda seguir funcionando.
3. Saltamos de nuevo al comienzo del bucle.

### Bucle infinito { #infinite-loop }

Si no establecemos correctamente la **condición de parada** o bien el valor de alguna variable está fuera de control, es posible que lleguemos a una situación de bucle infinito, del que nunca podamos salir.

Veamos un <span class="example">ejemplo:material-flash:</span> de esto:

```pycon
>>> num = 1

>>> while num != 10:
...     num += 2
...
^CTraceback (most recent call last):
  Cell In[4], line 1
    while num != 10:
KeyboardInterrupt
```

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.

??? tip "Parar el bucle"

    Para abortar una situación de _bucle infinito_ podemos pulsar en el teclado la combinación ++ctrl+c++. Se puede ver reflejado en el intérprete de Python por `KeyboardInterrupt`.

Una posible solución a este problema sería ^^reescribir la condición de parada^^ en el bucle:

```pycon
>>> num = 1

>>> while num < 10:
...     num += 2
...
```

#### Bucles infinitos como recurso { #infinite-loops-as-resource }

Hay ocaciones en las que un **supuesto bucle «infinito»** puede ayudarnos a resolver un problema.

Si retomamos el <span class="example">ejemplo:material-flash:</span> de los saludos, podríamos reescribirlo utilizando un «bucle infinito» de la siguiente manera:

```pycon
>>> while True:#(1)!
...     print('Hola qué tal!')
...     if (want_greet := input('¿Quiere otro saludo? [S/N] ')) != 'S':#(2)!
...         break#(3)!
... print('Que tenga un buen día')
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] S
Hola qué tal!
¿Quiere otro saludo? [S/N] N
Que tenga un buen día
```
{ .annotate }

1. Este bucle por sí solo implicaría un bucle infinito, pero veremos que no es así.
2. Usando el [operador morsa](conditionals.md#walrus) pedimos la entrada y comprobamos su valor.
3. La sentencia `#!python break` nos «salva» de este bucle infinito cuando no se quieren más saludos.

En comparación con el enfoque «clásico» del bucle `#!python while`:

- Como ^^ventaja^^ podemos observar que no es necesario asignar un valor inicial a `want_greet` antes de entrar al bucle.
- Como ^^desventaja^^ el código resulta menos «idiomático» ya que la condición del bucle no nos da ninguna pista de lo que está ocurriendo.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `m5-limited`

## La sentencia `for` { #for }

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 iterables** (_que permiten ser iteradas/recorridas_) son: [cadenas de texto](../datatypes/strings.md), [listas](../datastructures/lists.md), [tuplas](../datastructures/tuples.md), [diccionarios](../datastructures/dicts.md), [ficheros](../datastructures/files.md), etc.

La sentencia `#!python for` nos permite realizar esta acción.

A continuación se plantea un <span class="example">ejemplo:material-flash:</span> en el que recorremos (iteramos sobre) una cadena de texto:

```pycon
>>> word = 'Python'

>>> for letter in word:#(1)!
...     print(letter)
...
P
y
t
h
o
n
```
{ .annotate }

1.  - La variable `letter` va tomando cada uno de los elementos de `word`.
    - Dado que `word` es una _cadena de texto_, cada elemento es un [caracter](../datatypes/strings.md#get-char).

!!! note "Variable de asignación"

    La **variable de asignación** que utilizamos en el bucle `#!python for` para ir tomando los valores puede tener **cualquier nombre**. Al fin y al cabo es una variable que definimos según nuestras necesidades.

    Suele ser de buen estilo de programación que sea un **nombre en singular** relacionado con la estructura que recorre:

    - `#!python for item in items:`
    - `#!python for num in numbers:`
    - `#!python for product in products:`
    - `#!python for line in lines:`

### Romper un bucle for { #for-break }

Una sentencia `#!python break` dentro de un `#!python for` rompe el bucle, [igual que veíamos](#while-break) para los bucles `#!python while`.

Veamos un <span class="example">ejemplo:material-flash:</span> recorriendo una cadena de texto y parando el bucle cuando encontremos una letra _t_ minúscula:

```pycon hl_lines="5"
>>> word = 'Python'

>>> for letter in word:
...     if letter == 't':
...         break
...     print(letter)
...
P
y
```

!!! tip "Comprobación de rotura y continuación"

    Tanto la [comprobación de rotura de un bucle](#while-else) como la [continuación a la siguiente iteración](#while-continue) se llevan a cabo del mismo modo que hemos visto con los bucles de tipo `#!python while`.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `count-vowels`

### Secuencias de números { #range }

Es muy habitual hacer uso de secuencias de números en bucles. Python no tiene una instrucción específica para ello. Lo que sí aporta es una función `#!python range()` que devuelve un flujo de números en el rango especificado[^3].

La técnica para la generación de secuencias de números es muy similar a la utilizada en los [«slices»](../datatypes/strings.md#slicing) de cadenas de texto. En este caso disponemos de la función `#!python range(start, stop, step)`:

| Parámetro | Carácter | Por defecto |
| --- | --- | --- |
| `start` | Opcional | 0 |
| `stop` | <span class="hl">Obligatorio</span> | - |
| `step` | Opcional | 1 |

Veamos distintos casos de uso:

=== "Rango: $[0,1,2]$"

    ```pycon
    >>> for i in range(3):#(1)!
    ...     print(i)
    ...
    0
    1
    2
    ```    
    { .annotate }
    
    1. `#!python start=0`, `#!python stop=3`, `#!python step=1`
        - También se podría haber escrito `#!python range(0, 3)` pero es innecesario.
        - Recordar que al ser _índice 0_, el rango llega hasta 1 menos que el límite superior.

=== "Rango: $[1,3,5]$"

    ```pycon
    >>> for i in range(1, 6, 2):#(1)!
    ...     print(i)
    ...
    1
    3
    5
    ```
    { .annotate }
    
    1. `#!python start=1`, `#!python stop=6`, `#!python step=2`

=== "Rango: $[2,1,0]$"

    ```pycon
    >>> for i in range(2, -1, -1):#(1)!
    ...     print(i)
    ...
    2
    1
    0
    ```
    { .annotate }
    
    1.  - `#!python start=2`, `#!python stop=-1`, `#!python step=-1`
        - Vamos «hacia atrás» por tanto el límite final estará uno por debajo de donde queremos llegar.

!!! warning "i, j, k"

    Se suelen utilizar nombres de variables `i`, `j`, `k` para lo que se denominan **contadores**[^4]. Este tipo de variables toman valores numéricos enteros como en los ejemplos anteriores.
    
    :material-alarm-light:{ .red } No conviene generalizar el uso de estas variables a situaciones en las que, claramente, tenemos la posibilidad de **asignar un nombre semánticamente más significativo**.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `prime`

#### Usando el guión bajo { #underscore }

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 forma explícita:

```pycon
>>> for _ in range(10):
...     print('Repeat me 10 times!')
...
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
Repeat me 10 times!
```

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `pow`

## Bucles anidados { #nested-loops }

Como ya vimos en las [sentencias condicionales](conditionals.md#if), el **anidamiento** es una técnica por la que incluimos distintos niveles de encapsulamiento de sentencias, unas dentro de otras, con mayor nivel de profundidad.

En el caso de los bucles también es posible hacer anidamiento, tanto para bucles [`while`](#while) como para bucles [`for`](#for).

![Matrioskas](images/loops/matrioskas.png)
///caption
Muñecas rusas © [Marina Yufereva](https://www.revista.escaner.cl/node/7197) (Escáner Cultural)
///

Veamos un <span class="example">ejemplo:material-flash:</span> de **2 bucles anidados** en el que generamos **todas las tablas de multiplicar**:

```pycon
>>> for num_table in range(1, 10):#(1)!
...     for mul_factor in range(1, 10):#(2)!
...         result = num_table * mul_factor
...         print(f'{num_table} * {mul_factor} = {result}')
...     print('----------')
...
1 * 1 = 1
1 * 2 = 2
1 * 3 = 3
1 * 4 = 4
1 * 5 = 5
1 * 6 = 6
1 * 7 = 7
1 * 8 = 8
1 * 9 = 9
----------
2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
2 * 4 = 8
2 * 5 = 10
2 * 6 = 12
2 * 7 = 14
2 * 8 = 16
2 * 9 = 18
----------
3 * 1 = 3
3 * 2 = 6
3 * 3 = 9
3 * 4 = 12
3 * 5 = 15
3 * 6 = 18
3 * 7 = 21
3 * 8 = 24
3 * 9 = 27
----------
4 * 1 = 4
4 * 2 = 8
4 * 3 = 12
4 * 4 = 16
4 * 5 = 20
4 * 6 = 24
4 * 7 = 28
4 * 8 = 32
4 * 9 = 36
----------
5 * 1 = 5
5 * 2 = 10
5 * 3 = 15
5 * 4 = 20
5 * 5 = 25
5 * 6 = 30
5 * 7 = 35
5 * 8 = 40
5 * 9 = 45
----------
6 * 1 = 6
6 * 2 = 12
6 * 3 = 18
6 * 4 = 24
6 * 5 = 30
6 * 6 = 36
6 * 7 = 42
6 * 8 = 48
6 * 9 = 54
----------
7 * 1 = 7
7 * 2 = 14
7 * 3 = 21
7 * 4 = 28
7 * 5 = 35
7 * 6 = 42
7 * 7 = 49
7 * 8 = 56
7 * 9 = 63
----------
8 * 1 = 8
8 * 2 = 16
8 * 3 = 24
8 * 4 = 32
8 * 5 = 40
8 * 6 = 48
8 * 7 = 56
8 * 8 = 64
8 * 9 = 72
----------
9 * 1 = 9
9 * 2 = 18
9 * 3 = 27
9 * 4 = 36
9 * 5 = 45
9 * 6 = 54
9 * 7 = 63
9 * 8 = 72
9 * 9 = 81
----------
```
{ .annotate }

1. Para cada valor que toma la variable `num_table` la otra variable `mul_factor` toma todos sus valores.
2. Como resultado tenemos una combinación completa de los valores en el rango especificado.

??? warning "Complejidad ciclomática"

    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](https://es.wikipedia.org/wiki/Complejidad_ciclom%C3%A1tica) de nuestro código, lo que se traduce en mayores tiempos de ejecución.

    :simple-readdotcv: Revisa [este artículo](https://samwho.dev/big-o/) de [Sam Rose](https://samwho.dev/) sobre **Notación O** (*orden de crecimiento*).

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `mosaic`

## Ejercicios { #exercises }

1. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `letdig`
2. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `m3-sum-limited`
3. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `repeat-please`
4. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `one-tree`
5. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `chess-horse`
6. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `domino`
7. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `fmin`
8. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `ascii-table`
9. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `guess-number`
10. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `gcd`
11. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `hamming`
12. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `cartesian`
13. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `cumprod-sq`
14. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `isalpha`
15. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `kpower`
16. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `fibonacci`


[^1]: Esta condición del bucle se conoce como **condición de parada**.
[^2]: Realizar cierta acción varias veces. En este caso la acción es tomar cada elemento.
[^3]: 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.
[^4]: Esto viene de tiempos antiguos en [FORTRAN](https://fortran-lang.org/es/index) donde `i` era la primera letra que tenía valor entero por defecto.


================================================
FILE: docs/core/datastructures/dicts.md
================================================
---
icon: material/code-braces
tags:
  - Fundamentos del lenguaje
  - Estructuras de datos
  - Diccionarios
---

# Diccionarios { #dicts }

![Banner](images/dicts/banner.jpg)
///caption
Imagen generada con Inteligencia Artificial
///

Podemos trasladar el concepto de _diccionario_ de la vida real al mundo Python. Al fin y al cabo un diccionario es un objeto que contiene palabras, y cada palabra tiene asociado un significado. Haciendo el paralelismo, diríamos que en Python un diccionario es también un objeto indexado por **claves** (las palabras) que tienen asociados unos **valores** (los significados).

![Dark image](images/dicts/dicts-dark.svg#only-dark)
![Light image](images/dicts/dicts-light.svg#only-light)

Los diccionarios en Python tienen las siguientes **características**:

- Mantienen el **orden** en el que se insertan las claves[^1].
- Son **mutables**, con lo que permiten añadir, borrar y modificar sus elementos.
- Las **claves** deben ser **únicas**. A menudo se utilizan las _cadenas de texto_ como claves, pero en realidad podría ser **cualquier tipo de datos inmutable**: enteros, flotantes, tuplas (entre otros).
- Tienen un **acceso muy rápido** a sus elementos, debido a la forma en la que están implementados internamente[^2].

!!! note "Hashes"

    En otros lenguajes de programación, a los diccionarios se les conoce como _arrays asociativos_, _«hashes»_ o _«hashmaps»_. Básicamente porque al final lo que utilizan es una [función hash](#hashables) para determinar la ubicación de las claves.

## Creando diccionarios { #create }

Para crear un diccionario usamos llaves `{}` rodeando asignaciones `clave: valor` que están separadas por comas.

Veamos algunos <span class="example">ejemplos:material-flash:</span> de diccionarios:

```pycon
>>> rae = {#(1)!
...     'bifronte': 'De dos frentes o dos caras',
...     'anarcoide': 'Que tiende al desorden',
...     'montuvio': 'Campesino de la costa'
... }

>>> population_can = {#(2)!
...     2015: 2_135_209,
...     2016: 2_154_924,
...     2017: 2_177_048,
...     2018: 2_206_901,
...     2019: 2_220_270
... }

>>> empty_dict = {}#(3)!
```
{ .annotate }

1. Un diccionario con claves [cadenas de texto](../datatypes/strings.md) y valores [cadenas de texto](../datatypes/strings.md).
2. Un diccionario con claves [números enteros](../datatypes/numbers.md#integers) y valores [números enteros](../datatypes/numbers.md#integers).
3. El **diccionario vacío** (_0 elementos_).

!!! exercise "Ejercicio"

    Entra en el intérprete interactivo de Python <span class="green">❯❯❯</span> y crea un diccionario con los nombres (como claves) de 5 personas de tu familia y sus edades (como valores).
    
## Conversión { #cast }

Para convertir otros tipos de datos en un diccionario podemos usar la función `#!python dict()`.

Veamos varios <span class="example">ejemplos:material-flash:</span> donde creamos un diccionario a partir de...

=== "... una lista de cadenas de texto"

    ```pycon
    >>> dict(['a1', 'b2'])
    {'a': '1', 'b': '2'}
    ```

=== "... una tupla de cadenas de texto"

    ```pycon
    >>> dict(('a1', 'b2'))
    {'a': '1', 'b': '2'}
    ```
    
=== "... una lista de listas"

    ```pycon
    >>> dict([['a', 1], ['b', 2]])
    {'a': 1, 'b': 2}
    ```

:material-check-all:{ .blue } Si nos fijamos bien, _cualquier iterable que tenga una estructura interna de 2 elementos_ es susceptible de convertirse en un diccionario a través de la función `#!python dict()`.

### Creando con `dict()` { #dict-create }

También es posible utilizar la función `#!python dict()` para crear dicionarios y no tener que utilizar llaves y comillas.

Planteemos un <span class="example">ejemplo:material-flash:</span> donde queremos transformar la siguiente tabla en un diccionario:

| Atributo | Valor |
| --- | --- |
| `name` | Guido |
| `surname` | van Rossum |
| `job` | Python creator |

Utilizando la construcción mediante `#!python dict()` podemos pasar ^^clave y valor^^ como **argumentos** de la función:

```pycon
>>> person = dict(
...     name='Guido',
...     surname='van Rossum',
...     job='Python creator'
... )

>>> person
{'name': 'Guido', 'surname': 'van Rossum', 'job': 'Python creator'}
```

El inconveniente que tiene esta aproximación es que las **claves deben ser identificadores válidos** en Python. Por <span class="example">ejemplo:material-flash:</span> no se permiten espacios:

```pycon hl_lines="7"
>>> person = dict(
...     name='Guido van Rossum',
...     date of birth='31/01/1956'
  Cell In[1], line 1
    person = dict(
                 ^
SyntaxError: '(' was never closed
```

### Creando con relleno { #dict-filled }

Python permite crear un diccionario especificando sus claves y un único valor de «relleno» utilizando la función [`fromkeys()`](https://docs.python.org/3/library/stdtypes.html#dict.fromkeys).

Por <span class="example">ejemplo:material-flash:</span> creemos un diccionario para almacenar el «rating» (valoración) de productos en una tienda de comercio online (inicialmente tendrán 0):

```pycon
>>> dict.fromkeys(['portátil', 'nevera', 'ventilador', 'monitor'], 0)
{'portátil': 0, 'nevera': 0, 'ventilador': 0, 'monitor': 0}
```

!!! note "Sobre cualquier iterable"

    Es posible aplicar `fromkeys()` sobre **cualquier iterable** con referencia a las claves.

## Operaciones con diccionarios { #operations }

Existen multitud de operaciones que se pueden realizar sobre diccionarios. A continuación veremos la mayoría de ellas:

### Obtener un elemento { #get-item }

Para obtener un elemento (valor de una clave) de un diccionario basta con **escribir la clave entre corchetes**.

Veamos un <span class="example">ejemplo:material-flash:</span>:

```pycon hl_lines="7"
>>> rae = {
...     'bifronte': 'De dos frentes o dos caras',
...     'anarcoide': 'Que tiende al desorden',
...     'montuvio': 'Campesino de la costa'
... }

>>> rae['anarcoide']
'Que tiende al desorden'
```

Si intentamos acceder a una clave que no existe, obtendremos un error de tipo [`KeyError`](https://docs.python.org/3/library/exceptions.html#KeyError):

```pycon hl_lines="5"
>>> rae['acceso']
Traceback (most recent call last):
  Cell In[1], line 1
    rae['acceso']
KeyError: 'acceso'
```

#### Usando `get()` { #get }

Existe una función muy útil para «superar» los posibles errores de acceso por claves inexistentes. Se trata de `#!python 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 `#!python None` salvo que le indiquemos otro valor por defecto, pero en ninguno de los dos casos obtendremos un error.

Veamos un <span class="example">ejemplo:material-flash:</span> de cada uno de los escenarios indicados:

```pycon
>>> rae
{'bifronte': 'De dos frentes o dos caras',
 'anarcoide': 'Que tiende al desorden',
 'montuvio': 'Campesino de la costa'}

>>> rae.get('bifronte')#(1)!
'De dos frentes o dos caras'

>>> rae.get('programación')#(2)!

>>> rae.get('programación', 'No disponible')#(3)!
'No disponible'
```
{ .annotate }

1. La clave existe y se devuelve su valor.
2. La clave no existe y se devuelve `#!python None` (no aparece nada en la salida).
3. La clave no existe pero aportamos un valor por defecto.

### Añadir o modificar un elemento { #add-modify }

Para añadir un elemento a un diccionario sólo es necesario hacer referencia a la _clave_ y asignarle un _valor_:

- Si la clave **ya existía** en el diccionario, **se reemplaza** el valor existente por el nuevo.
- Si la clave **es nueva**, **se añade** al diccionario con su valor. _No vamos a obtener un error a diferencia de las listas_.

Partimos del siguiente diccionario de <span class="example">ejemplo:material-flash:</span> para mostrar ambos escenarios:

```pycon
rae = {
...     'bifronte': 'De dos frentes o dos caras',
...     'anarcoide': 'Que tiende al desorden',
...     'montuvio': 'Campesino de la costa'
... }
```

=== "Añadir elemento :octicons-diff-added-24:"

    ```pycon
    >>> rae['enjuiciar'] = 'Someter una cuestión a examen, discusión y juicio'

    >>> rae
    {'anarcoide': 'Que tiende al desorden',
     'bifronte': 'De dos frentes o dos caras',
     'enjuiciar': 'Someter una cuestión a examen, discusión y juicio',
     'montuvio': 'Campesino de la costa'}
    ```
    
=== "Modificar elemento :fontawesome-solid-down-left-and-up-right-to-center:"

    ```pycon
    >>> rae['enjuiciar'] = 'Instruir, juzgar o sentenciar una causa'

    >>> rae
    {'anarcoide': 'Que tiende al desorden',
     'bifronte': 'De dos frentes o dos caras',
     'enjuiciar': 'Instruir, juzgar o sentenciar una causa',
     'montuvio': 'Campesino de la costa'}
    ```

!!! info "Valor por defecto"

    Python ofrece la función [`setdefault()`](https://docs.python.org/dev/library/stdtypes.html#dict.setdefault) cuyo comportamiento es el siguiente:

    1. Si la clave indicada existe, se devuelve su valor.
    2. Si la clave indicada no existe, se añade la clave con el valor indicado.

    Veamos un <span class="example">ejemplo:material-flash:</span>:

    ```pycon
    >>> rae = {
    ...     'bifronte': 'De dos frentes o dos caras',
    ...     'anarcoide': 'Que tiende al desorden',
    ...     'montuvio': 'Campesino de la costa'
    ... }
    
    >>> rae.setdefault('poniente', 'Viento del oeste')
    'Viento del oeste'
    
    >>> rae
    {'bifronte': 'De dos frentes o dos caras',
     'anarcoide': 'Que tiende al desorden',
     'montuvio': 'Campesino de la costa',
     'poniente': 'Viento del oeste'}
    
    >>> rae.setdefault('poniente', 'Viento del oeste')
    'Viento del oeste'    
    ```

#### Patrón creación { #create-pattern }

Una forma muy habitual de trabajar con diccionarios es empezar con uno vacío e ir añadiendo elementos poco a poco. Se podría hablar de un **patrón creación**.

A continuación se muestra un <span class="example">ejemplo:material-flash:</span> de creación de un diccionario donde las **claves** son las **letras vocales** y los **valores** son sus [códigos UNICODE](../datatypes/strings.md#unicode):

```pycon
>>> VOWELS = 'aeiou'

>>> cvowels = {}

>>> for vowel in VOWELS:
...     cvowels[vowel] = ord(vowel)
...

>>> cvowels
{'a': 97, 'e': 101, 'i': 105, 'o': 111, 'u': 117}
```

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `extract-cities`

### Pertenencia de una clave { #in }

La forma **pitónica** de comprobar la existencia de una ^^clave^^ dentro de un diccionario, es utilizar el operador `#!python in`:

```pycon
>>> 'bifronte' in rae
True

>>> 'almohada' in rae
False

>>> 'montuvio' not in rae
False
```

!!! note "Booleano"

    El operador `#!python in` siempre devuelve un valor _booleano_, es decir, verdadero o falso.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `count-letters`

### Longitud de un diccionario { #length }

Podemos conocer el número de elementos («clave-valor») que tiene un diccionario mediante la función `#!python len()`:

```pycon
>>> 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
```

### Obtener todos los elementos { #get-items }

Python ofrece mecanismos para obtener todos los elementos de un diccionario.

Partimos del siguiente diccionario de <span class="example">ejemplo:material-flash:</span> para ilustrar estos mecanismos:

```pycon
>>> rae
{'anarcoide': 'Que tiende al desorden',
 'bifronte': 'De dos frentes o dos caras',
 'enjuiciar': 'Instruir, juzgar o sentenciar una causa',
 'montuvio': 'Campesino de la costa'}
```

=== "Obtener claves :octicons-key-16:"

    ```pycon
    >>> rae.keys()
    dict_keys(['bifronte', 'anarcoide', 'montuvio', 'enjuiciar'])
    ```

=== "Obtener valores :material-content-save:"

    ```pycon
    >>> rae.values()
    dict_values([
        'De dos frentes o dos caras',
        'Que tiende al desorden',
        'Campesino de la costa',
        'Instruir, juzgar o sentenciar una causa'
    ])
    ```

=== "Obtener claves y valores :octicons-key-16::material-content-save:"

    ```pycon
    >>> 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')
    ])
    ```

    :material-check-all:{ .blue } Cabe destacar que los «items» se devuelven como una **[lista](./lists.md) de [tuplas](./tuples.md)**, donde cada tupla contiene dos elementos: el primero representa la **clave** y el segundo representa el **valor**.

### Iterar sobre un diccionario { #iterate }    

En función de los [elementos que podemos obtener](#get-items), Python proporciona tres maneras de iterar sobre un diccionario:

=== "Iterar sobre claves :octicons-key-16:"

    ```pycon
    >>> for word in rae.keys():
    ...     print(word)
    ...
    bifronte
    anarcoide
    montuvio
    enjuiciar
    ```

=== "Iterar sobre valores :material-content-save:"

    ```pycon
    >>> for meaning in rae.values():
    ...     print(meaning)
    ...
    De dos frentes o dos caras
    Que tiende al desorden
    Campesino de la costa
    Instruir, juzgar o sentenciar una causa
    ```

=== "Iterar sobre claves y valores :octicons-key-16::material-content-save:"

    ```pycon
    >>> for word, meaning in rae.items():
    ...     print(f'{word}: {meaning}')#(1)!
    ...
    bifronte: De dos frentes o dos caras
    anarcoide: Que tiende al desorden
    montuvio: Campesino de la costa
    enjuiciar: Instruir, juzgar o sentenciar una causa
    ```
    { .annotate }
    
    1. Recuerda el uso de los [«f-strings»](../../core/datatypes/strings.md#fstrings) para formatear cadenas de texto.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `avg-population`

### Borrar elementos { #remove }

Python nos ofrece varios mecanismos para borrar elementos en un diccionario:

=== "Por su clave :octicons-key-16:"

    ```pycon hl_lines="7"
    >>> rae = {
    ...     'bifronte': 'De dos frentes o dos caras',
    ...     'anarcoide': 'Que tiende al desorden',
    ...     'montuvio': 'Campesino de la costa'
    ... }
    
    >>> del rae['bifronte']
    
    >>> rae
    {'anarcoide': 'Que tiende al desorden', 'montuvio': 'Campesino de la costa'}
    ```

=== "Por su clave (con extracción) :material-party-popper:"

    ```pycon hl_lines="7"
    >>> rae = {
    ...     'bifronte': 'De dos frentes o dos caras',
    ...     'anarcoide': 'Que tiende al desorden',
    ...     'montuvio': 'Campesino de la costa'
    ... }
    
    >>> rae.pop('anarcoide')#(1)!
    'Que tiende al desorden'
    
    >>> rae
    {'bifronte': 'De dos frentes o dos caras', 'montuvio': 'Campesino de la costa'}
    
    >>> rae.pop('anarcoide')#(2)!
    Traceback (most recent call last):
      Cell In[1], line 1
        rae.pop('anarcoide')
    KeyError: 'anarcoide'
    ```
    { .annotate }
    
    1. `#!python pop()` extrae la clave (y el valor) indicados.
    2. Si una clave no existe obtenemos un error de tipo [`KeyError`](https://docs.python.org/3/library/exceptions.html#KeyError).

=== "Borrado completo :material-eraser:"

    ```pycon hl_lines="7"
    >>> rae = {
    ...     'bifronte': 'De dos frentes o dos caras',
    ...     'anarcoide': 'Que tiende al desorden',
    ...     'montuvio': 'Campesino de la costa'
    ... }
    
    >>> rae.clear()#(1)!
    
    >>> rae
    {}
    ```
    { .annotate }
    
    1. Se borra la zona de memoria existente.

=== "Borrado completo (reasignado) :octicons-trash-16:"

    ```pycon hl_lines="7"
    >>> rae = {
    ...     'bifronte': 'De dos frentes o dos caras',
    ...     'anarcoide': 'Que tiende al desorden',
    ...     'montuvio': 'Campesino de la costa'
    ... }
    
    >>> rae = {}#(1)!
    
    >>> rae
    {}
    ```    
    { .annotate }
    
    1. Se busca una nueva zona de memoria vacía.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `merge-dicts`

### Combinar diccionarios { #combine }

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:

- [x] Si la clave no existe, se añade con su valor.
- [x] Si la clave ya existe, se añade con el valor del «último»[^3] diccionario en la mezcla.

Partiendo de los siguientes diccionarios vamos a mostrar <span class="example">ejemplos:material-flash:</span> sobre los mecanismos que ofrece Python para combinar diccionarios:

```pycon
>>> 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'
... }
```

=== "Sin modificar los diccionarios originales :material-silverware-clean:"

    ```pycon
    >>> rae1 | rae2#(1)!
    {'bifronte': 'De dos frentes o dos caras',
     'enjuiciar': 'Instruir, juzgar o sentenciar una causa',
     'anarcoide': 'Que tiende al desorden',
     'montuvio': 'Campesino de la costa'}
    ```
    { .annotate }
    
    1. En versiones anteriores a <span class="pyversion"><a href="https://docs.python.org/3.9/">Python <span class="version">:octicons-tag-24: 3.9</span></a></span> habría que utilizar: `#!python {**rae1, **rae2}`

=== "Modificando los diccionarios originales :material-message-arrow-right-outline:"

    ```pycon
    >>> 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'}
    ```

??? tip "Orden de la mezcla"

    Hay que tener en cuenta el orden en el que especificamos los diccionarios a la hora de su combinación (mezcla) ya que es relevante en el resultado final. En este caso _el orden de los factores sí altera el producto_.

## Cuidado con las copias { #copy }

Al igual que [ocurría con las listas](lists.md#copy), si hacemos un cambio en un diccionario, se verá reflejado en todas las variables que hagan referencia al mismo. Esto se deriva de su propiedad de ser _mutable_.

Veamos un <span class="example">ejemplo:material-flash:</span> concreto:

```pycon hl_lines="12 17"
>>> original_rae = {
...     'bifronte': 'De dos frentes o dos caras',
...     'anarcoide': 'Que tiende al desorden',
...     'montuvio': 'Campesino de la costa'
... }

>>> copy_rae = original_rae#(1)!

>>> original_rae['bifronte'] = 'bla bla bla'#(2)!

>>> 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'}
```
{ .annotate }

1. Con esta asignación, ambas variables (nombres) quedan apuntando a la misma zona de memoria.
2. Esta modificación se realiza sobre la misma zona de memoria, lo que afecta a ambas variables.

Una **posible solución** a este problema es hacer una «copia dura». Para ello Python proporciona la función `#!python copy()`:

```pycon hl_lines="9 12 17"
>>> original_rae = {
...     'bifronte': 'De dos frentes o dos caras',
...     'anarcoide': 'Que tiende al desorden',
...     'montuvio': 'Campesino de la costa'
... }

>>> copy_rae = original_rae.copy()#(1)!

>>> original_rae['bifronte'] = 'bla bla bla'#(2)!

>>> original_rae
{'bifronte': 'bla bla bla',
'anarcoide': 'Que tiende al desorden',
'montuvio': 'Campesino de la costa'}

>>> copy_rae
{'bifronte': 'De dos frentes o dos caras',
 'anarcoide': 'Que tiende al desorden',
 'montuvio': 'Campesino de la costa'}
```
{ .annotate }

1. La copia se realiza en zonas de memoria distintas.
2. Esta modificación ya no afecta a ambas variables, al estar segmentadas en memoria.

??? warning "Copia profunda"

    En el caso de que estemos trabajando con diccionarios que contienen elementos mutables, debemos hacer uso de la función `#!python deepcopy()` dentro del módulo `copy` de la librería estándar.

## Diccionarios por comprensión { #comprehension }

De forma análoga a cómo se escriben las [listas por comprensión](lists.md#comprehension), podemos aplicar este método a los diccionarios usando llaves `{ }`.

Veamos un <span class="example">ejemplo:material-flash:</span> en el que creamos un **diccionario por comprensión** donde las claves son palabras y los valores son sus longitudes:

```pycon hl_lines="3"
>>> words = ('sun', 'space', 'rocket', 'earth')

>>> words_length = {word: len(word) for word in words}

>>> words_length
{'sun': 3, 'space': 5, 'rocket': 6, 'earth': 5}
```

También podemos aplicar **condiciones** a estas comprensiones. Continuando con el <span class="example">ejemplo:material-flash:</span> anterior, podemos incorporar la restricción de sólo incluir palabras que no empiecen por vocal:

```pycon hl_lines="3"
>>> words = ('sun', 'space', 'rocket', 'earth')

>>> words_length = {w: len(w) for w in words if w[0] not in 'aeiou'}

>>> words_length
{'sun': 3, 'space': 5, 'rocket': 6}
```

??? info "PEP-274"

    Se puede consultar el [PEP-274](https://www.python.org/dev/peps/pep-0274/) para ver más ejemplos sobre diccionarios por comprensión.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `split-marks`

## Objetos «hashables» { #hashables }

La única restricción que deben cumplir las **claves** de un diccionario es ser **«hashables»**. 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 función `#!python hash()`, que devuelve un número entero y es utilizado para indexar la tabla «hash» que se mantiene internamente:

```pycon
>>> hash(999)
999

>>> hash(3.14)
322818021289917443

>>> hash('hello')#(1)!
-8103770210014465245

>>> hash(('a', 'b', 'c'))
-2157188727417140402
```
{ .annotate }

1. La función «built-in» `hash()` realmente hace una llamada al método mágico `__hash__()` del objeto en cuestión: `#!python 'hello'.__hash__()`

Para que un objeto sea «hashable», debe ser de un **tipo inmutable**:

```pycon hl_lines="5"
>>> hash(['a', 'b', 'c'])
Traceback (most recent call last):
  Cell In[1], line 1
    hash(['a', 'b', 'c'])
TypeError: unhashable type: 'list'
```

!!! success "Claves inmutables"

    De lo anterior se deduce que las claves de los diccionarios, al tener que ser «hashables», sólo pueden ser objetos inmutables.

## Ejercicios

1.  [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `group-words`
2.  [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `same-dict-values`
3.  [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `dict-from-list`
4.  [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `clear-dict-values`
5.  [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `fix-keys`
6.  [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `order-stock`
7.  [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `inventory-updates`
8.  [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `sort-dict`
9.  [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `money-back`
10. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `money-back-max`
11. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `first-ntimes`
12. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `fix-id`
13. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `dict-pull`



[^1]: «Los diccionarios preservan el orden de inserción» extraído de la [documentación oficial de Python sobre diccionarios](https://docs.python.org/3/library/stdtypes.html#mapping-types-dict).
[^2]: Artículo de Ramil Suleimanov sobre [«Por qué los diccionarios en Python son tan rápidos»](https://medium.com/@r_suleimanov/why-is-python-dict-so-fast-555e330a8ded).
[^3]: En este contexto «último» significa el que esté más a la derecha en la sentencia de asignación.


================================================
FILE: docs/core/datastructures/files.md
================================================
---
icon: material/file-cabinet
tags:
  - Fundamentos del lenguaje
  - Estructuras de datos
  - Ficheros
---

# Ficheros { #files }

![Banner](images/files/banner.jpg)
/// caption
Imagen generada con Inteligencia Artificial
///

Aunque los ficheros encajarían más en un apartado de «entrada/salida» ya que representan un **medio de almacenamiento persistente**, también podrían ser vistos como _estructuras de datos_, puesto que nos permiten guardar información y asignarle un cierto formato.

Un **fichero** es un _conjunto de bytes_ almacenados en algún dispositivo. El [sistema de ficheros](https://bit.ly/405ABbw) es la estructura lógica que alberga los ficheros y está jerarquizado a través de _directorios_ (o carpetas). Cada fichero se **identifica unívocamente a través de una ruta** que nos permite acceder a él.

En esta sección nos centraremos en el manejo de **ficheros de texto plano** (aquellos que son entendibles por humanos). Pero también existen _ficheros binarios_ que Python es capaz de manejar.

Hay tres modos de apertura de un fichero:

- [x] [Lectura](#read).
- [x] [Escritura](#write).
- [x] [Añadido](#append).

## Lectura de un fichero { #read }

Para abrir un fichero en **modo lectura** utilizamos la función `#!python open()` con el modificador `#!python 'r'`.

En el siguiente <span class="example">ejemplo:material-flash:</span> vamos a leer el contenido de un fichero que contiene las **temperaturas mínimas y máximas** de cada día de la última semana en una región determinada:

```text title="store/temps.dat" linenums="1"
23 29
23 31
26 34
23 33
22 29
22 28
22 28
```

Abrir un fichero significa crear un objeto «manejador»[^1] que nos permita realizar operaciones sobre dicho fichero:

```pycon
>>> f = open('store/temps.dat', 'r')#(1)!
```
{ .annotate }

1. Es posible omitir `#!python 'r'` ya que el modo de apertura por defecto es _lectura_.

La función `#!python open()` recibe como primer argumento la **ruta al fichero** que queremos manejar (como un «string») y como segundo argumento el **modo de apertura** (también como un «string»). Nos **devuelve el manejador del fichero**, que en este caso lo estamos asignando a una variable llamada `f` pero le podríamos haber puesto cualquier otro nombre.

!!! note "Rutas relativas vs Rutas absolutas"

    Es importante dominar los conceptos de ruta relativa y ruta absoluta para el trabajo con ficheros (véase [este artículo](https://sanchezcorbalan.es/rutas-relativas-vs-rutas-absolutas/) de _Sánchez Corbalán_).

El **manejador del fichero** se implementa mediante un [flujo de entrada/salida](https://docs.python.org/es/3/library/io.html#io.TextIOWrapper) para las operaciones de lectura/escritura. Este objeto almacena, entre otras cosas, la ruta al fichero, el modo de apertura y la codificación:

```pycon
>>> f
<_io.TextIOWrapper name='store/temps.dat' mode='r' encoding='UTF-8'>
```

!!! tip "Codificaciones"

    Existen muchas [codificaciones de caracteres](https://es.wikipedia.org/wiki/Codificaci%C3%B3n_de_caracteres) para ficheros, pero la más utilizada es [UTF-8](https://es.wikipedia.org/wiki/UTF-8) ya que es capaz de representar cualquier caracter [Unicode](../datatypes/strings.md#unicode) al utilizar una longitud variable de 1 a 4 bytes.

Hay que tener en cuenta que **la ruta al fichero** que abrimos (en modo lectura) **debe existir**, ya que de lo contrario obtendremos un error:

```pycon hl_lines="5"
>>> f = open('foo.txt', 'r')
Traceback (most recent call last):
  Cell In[1], line 1
    f = open('foo.txt', 'r')
FileNotFoundError: [Errno 2] No such file or directory: 'foo.txt'
```

Una vez abierto el fichero ya podemos proceder a leer su contenido. Para ello Python nos ofrece la posibilidad de leer todo el fichero de una vez o bien leerlo línea a línea.

!!! example "Lectura y escritura"

    Es posible utilizar `#!python open(path, 'r+')` para abrir el fichero en modo lectura y escritura simultáneamente.

### Lectura completa

Siguiendo nuestro <span class="example">ejemplo:material-flash:</span> de temperaturas, veamos cómo leer todo el contenido del fichero de una sola vez. Para esta operación, Python nos provee de dos funciones:

=== "`read()` :fontawesome-regular-file-text:"

    Devuelve todo el contenido del fichero como una única **cadena de texto**:

    ```pycon
    >>> f = open('store/temps.dat')
    
    >>> f.read()
    '23 29\n23 31\n26 34\n23 33\n22 29\n22 28\n22 28\n'
    ```

=== "`readlines()` :material-account-file-text-outline:"

    Devuelve todo el contenido del fichero como una **lista** donde cada elemento de la lista representa una línea del fichero:

    ```pycon
    >>> f = open('store/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']
    ```

!!! warning "Saltos de línea"

    Nótese que, en ambos casos, los saltos de línea `#!python '\n'` siguen apareciendo en los datos leídos, por lo que habría que [limpiar](../datatypes/strings.md#strip) estos caracteres.

### Lectura línea a línea { #read-line-by-line }

Hay situaciones en las que interesa leer el contenido del fichero **línea a línea**. Imaginemos un fichero de tamaño considerable (varios GB). Si intentamos leer completamente este fichero de sola una vez podríamos ocupar demasiada RAM y reducir el rendimiento de nuestra máquina.

Es por ello que Python nos ofrece varias aproximaciones a la lectura de ficheros línea a línea. La más usada es ^^iterar sobre el propio manejador del fichero^^[^2].

Veamos cómo aplicarlo en el <span class="example">ejemplo:material-flash:</span> de las temperaturas:

```pycon
>>> f = open('store/temps.dat')

>>> for line in f:    # that easy!
...     print(line)#(1)!
...
23 29

23 31

26 34

23 33

22 29

22 28

22 28
```
{ .annotate }

1. Notése que cada línea tiene un «espacio de más» que proviene del propio `#!python print()`.

#### Enumerando líneas { #enumerate }

En ocasiones no sólo necesitamos recorrer cada línea del fichero sino también ir llevando un «índice» que nos indique el número de línea que estamos procesando.

Dado que los manejadores de ficheros también son **objetos iterables** podemos hacer uso de la función [`enumerate()`](lists.md#enumerate).

A continuación mostramos un <span class="example">ejemplo:material-flash:</span> donde aprovechamos esta característica para incluir los días de la semana en el fichero de temperaturas:

```pycon
>>> f = open('store/temps.dat')

>>> for line_no, line in enumerate(f, start=1):
...     print(f'D{line_no}: {line.strip()}')
...
D1: 23 29
D2: 23 31
D3: 26 34
D4: 23 33
D5: 22 29
D6: 22 28
D7: 22 28
```

### Lectura de una línea { #readline }

Es posible que sólo necesitemos leer una línea del fichero. Para ello Python nos ofrece la función `#!python readline()` que devuelve la «siguiente» línea del fichero.

Veamos cómo hacerlo con el <span class="example">ejemplo:material-flash:</span> del fichero de temperaturas:

```pycon
>>> f = open('store/temps.dat')

>>> f.readline()#(1)!
'23 29\n'
```
{ .annotate }

1. Devuelve una única línea, en este caso la primera del fichero.

:material-check-all:{ .blue } Es importante señalar que cuando utilizamos la función `#!python readline()` el ^^puntero de lectura^^ se desplaza hasta la siguiente línea del fichero. Este hecho nos permite seguir leyendo desde donde nos quedamos.

A continuación se muestra un trozo de código sobre el <span class="example">ejemplo:material-flash:</span> de las temperaturas en el que mezclamos ambas técnicas de lectura:

```pycon
>>> f = open('store/temps.dat')#(1)!

>>> for _ in range(3):#(2)!
...     print(f.readline().strip())
...
23 29
23 31
26 34

>>> for line in f:#(3)!
...     print(line.strip())
...
23 33
22 29
22 28
22 28
```
{ .annotate }

1. Puntero de lectura en la posición 0 del fichero.
2. Lectura de las 3 primeras líneas del fichero.
3. Lectura de las restantes líneas del fichero.

La función `#!python readline()` devuelve la **cadena vacía** cuando el _puntero de lectura_ ha llegado al final del fichero. Bajo esta premisa podríamos implementar una forma **poco ortodoxa** de leer un fichero:

```pycon
>>> f = open('store/temps.dat')

>>> while line := f.readline():
...     print(line.strip())
...
23 29
23 31
26 34
23 33
22 29
22 28
22 28
```

### Lectura con posicionamiento { #seek }

Hay que tener en cuenta que, una vez que leemos un fichero, no lo podemos volver a leer «directamente». O dicho de otra manera, el iterable que lleva implícito «se agota».

Analicemos este escenario con el <span class="example">ejemplo:material-flash:</span> anterior:

```pycon
>>> f = open('store/temps.dat')

>>> for line in f:#(1)!
...     print(line.strip(), end=' ')
...
23 29 23 31 26 34 23 33 22 29 22 28 22 28

>>> for line in f:#(2)!
...     print(line.strip(), end=' ')
```
{ .annotate }

1. Recorrido de todo el fichero línea a línea.
2.  - Al intentar recorre de nuevo el fichero no sale nada por pantalla.
    - El puntero de lectura ha llegado al final.

Aunque no es tan usual, existe la posibilidad de **volver a leer el fichero desde el principio** reposicionando el **puntero de lectura** mediante la función [`seek()`](https://docs.python.org/3/tutorial/inputoutput.html#methods-of-file-objects).

A continuación se muestra como <span class="example">ejemplo:material-flash:</span> una «doble» lectura del fichero de temperaturas:

```pycon hl_lines="8"
>>> f = open('store/temps.dat')

>>> for line in f:
...     print(line.strip(), end=' ')
...
23 29 23 31 26 34 23 33 22 29 22 28 22 28

>>> f.seek(0)#(1)!
0

>>> for line in f:
...     print(line.strip(), end=' ')
...
23 29 23 31 26 34 23 33 22 29 22 28 22 28
```
{ .annotate }

1.  - Situamos el puntero de lectura en el _byte_ 0.
    - Devuelve la posición absoluta (en _bytes_) en la que se encuentra el puntero de lectura.

??? tip "Posicionamiento relativo"

    La función [`seek()`](https://docs.python.org/3/tutorial/inputoutput.html#methods-of-file-objects) también permite indicar un **desplazamiento relativo** si se usa el segundo argumento.
    
    | Uso | Mover el puntero de lectura... |
    | --- | --- |
    | `#!python f.seek(10, 0)` | 10 _bytes_ desde el ^^principio del fichero^^ (`0`) `DEFAULT` |
    | `#!python f.seek(7, 1)` | 7 _bytes_ desde la ^^posición actual del puntero de lectura^^ (`1`) |
    | `#!python f.seek(-5, 2)` | 5 _bytes_ desde el ^^final del fichero^^ (`2`) |

## Escritura de un fichero { #write }

Para abrir un fichero en **modo escritura** utilizamos la función `#!python open()` con el modificador `#!python 'w'`.

A continuación vamos a implementar un <span class="example">ejemplo:material-flash:</span> para escribir en un fichero las temperaturas mínimas y máximas de la última semana en una región determinada.

Lo primero será abrir el fichero en modo escritura:

```pycon
>>> f = open('store/temps.dat', 'w')
```

:material-alarm-light:{.acc} La apertura de un fichero en _modo escritura_ ^^borrará todo el contenido^^ que contuviera.

??? failure "Ruta al fichero"

    Las carpetas (o directorios) intermedios hasta llegar al fichero indicado **deben existir** previamente. De lo contrario obtendremos un error:

    ```pycon hl_lines="6"
    >>> f = open('foo/bar/temps.dat', 'w')
    Traceback (most recent call last):
      Cell In[1], line 1
        f = open('foo/bar/temps.dat', 'w')
        return io_open(file, *args, **kwargs)
    FileNotFoundError: [Errno 2] No such file or directory: 'foo/bar/temps.dat'
    ```

Ahora supongamos que disponemos de una estructura de datos (_[tupla](./tuples.md) de tuplas_) con las temperaturas:

```pycon
>>> temps = (
...   (23, 29),
...   (23, 31),
...   (26, 34),
...   (23, 33),
...   (22, 29),
...   (22, 28),
...   (22, 28),
... )
```

Python proporciona la función (método) `#!python write()` para escribir en un fichero:

```pycon hl_lines="2 4"
>>> for min_temp, max_temp in temps:
...     f.write(f'{min_temp} {max_temp}\n')#(1)!
...
>>> f.close()#(2)!
```
{ .annotate }

1.  - Construimos un «f-string» con la cadena a escribir en el fichero.
    - No olvidarse del saltó de línea `#!python '\n'` para incluir cada línea.
2. Es fundamental cerrar el fichero, especialmente en modo escritura, ya que de lo contrario podríamos perder los datos.

Si tratáramos de escribir directamente las temperaturas (como enteros) en el fichero, obtendríamos un error:

```pycon hl_lines="8"
>>> for min_temp, max_temp in temps:
...     f.write(min_temp)#(1)!
...     f.write(max_temp)#(2)!
...
Traceback (most recent call last):
  Cell In[1], line 2
    f.write(min_temp)
TypeError: write() argument must be str, not int
```
{ .annotate }

1. La función `#!python write()` sólo admite _cadenas de texto_.
2. La función `#!python write()` sólo admite _cadenas de texto_.

!!! info "Escribiendo líneas"

    Python proporciona la función [`f.writelines()`](https://docs.python.org/3/library/io.html#io.IOBase.writelines) que nos permite escribir un _iterable_ de líneas al fichero de una sola vez. Los saltos de línea no se añaden automáticamente.

!!! example "Escritura y lectura"

    Es posible utilizar `#!python open(path, 'w+')` para abrir el fichero en modo escritura y lectura simultáneamente.

### Usando contextos { #contexts }

Python proporciona [gestores de contexto](../modularity/oop.md#context-manager) como aproximación al manejo de ficheros. En este escenario usaremos la sentencia `#!python with` y el contexto creado se ocupará de abrir y cerrar el fichero automáticamente (**incluso si ha habido cualquier error**).

Veamos cómo aplicarlo con el <span class="example">ejemplo:material-flash:</span> anterior de las temperaturas:

```pycon
>>> with open('store/temps.dat', 'w') as f:#(1)!
...     for min_temp, max_temp in temps:
...         f.write(f'{min_temp} {max_temp}\n')
...
```
{ .annotate }

1.  - Abrimos el fichero en modo escritura.
    - Creamos un objeto `f` como manejador.

!!! tip "Uso de contextos"

    Aunque estos contextos también se pueden utilizar en modo lectura, son ^^realmente importantes cuando escribimos datos en un fichero^^, ya que nos aseguran el cierre del mismo y evitamos pérdidas de datos.

#### Múltiples ficheros { #multiple-files }

Cuando tenemos que abrir varios ficheros utilizando _gestores de contexto_ tenemos dos opciones: **anidamiento** o **misma línea**:

=== "Anidamiento"

    ```python
    >>> with open('file1', 'w') as f1:
    ...     with open('file2', 'w') as f2:
    ...         # file management
    ```

=== "Misma línea"

    ```python
    >>> with open('file1', 'w') as f1, open('file2', 'w') as f2:
    ...     # file management
    ```

Esto vale tanto para apertura de ficheros en _modo lectura_ como en _modo escritura_.

### Añadiendo líneas { #append }

Añadir información a un fichero se podría ver como un _caso especial_ de escribir información; con la diferencia de que no podemos modificar el contenido previo, sino únicamente añadir nuevos datos.

Para abrir un fichero en **modo añadido** utilizamos la función `#!python open()` con el modificador `#!python 'a'`.

!!! example "Añadido y lectura"

    Es posible utilizar `#!python open(path, 'a+')` para abrir el fichero en modo añadido y lectura simultáneamente.

## Ejercicios { #exercises }

1. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `wc`
2. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `read-csv`
3. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `txt2md`
4. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `avg-temps`
5. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `find-words`
6. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `sum-matrix`
7. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `longest-word`
8. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `word-freq`
9. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `get-line`
10. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `replace-chars`
11. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `histogram-txt`
12. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `submarine`
13. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `common-words`
14. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `split-file`

[^1]: Es muy frecuente encontrar en la documentación el término «handler» para referirse al objeto manejador del fichero.
[^2]: Los manejadores de ficheros son estructuras de datos [iterables](../modularity/oop.md#iterables).


================================================
FILE: docs/core/datastructures/index.md
================================================
# Estructuras de datos

Las estructuras de datos son formas organizadas de almacenar y gestionar conjuntos de información para que puedan ser utilizadas de manera eficiente por un programa. En Python, existen diversas estructuras integradas como listas, tuplas, conjuntos y diccionarios, cada una con características específicas que las hacen más adecuadas para ciertos tipos de tareas. Comprender cómo funcionan estas estructuras y cuándo utilizarlas es fundamental para escribir código limpio, optimizado y fácil de mantener. En este capítulo, exploraremos las principales estructuras de datos en Python, su sintaxis, operaciones comunes y casos prácticos donde se aplican.


================================================
FILE: docs/core/datastructures/lists.md
================================================
---
icon: material/list-box
tags:
  - Fundamentos del lenguaje
  - Estructuras de datos
  - Listas
---

# Listas { #lists }

![Banner](images/lists/banner.jpg)
/// caption
Imagen generada con Inteligencia Artificial
///

Las listas permiten **almacenar objetos** mediante un **orden definido** y con posibilidad de duplicados. Las listas son estructuras de datos **mutables**, lo que significa que podemos añadir, eliminar o modificar sus elementos.

## Creando listas { #create }

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 <span class="example">ejemplos:material-flash:</span> de listas:

```pycon
>>> languages = ['Python', 'Ruby', 'Javascript']#(1)!

>>> fibonacci = [0, 1, 1, 2, 3, 5, 8, 13]#(2)!

>>> empty_list = []#(3)!

>>> data = [#(4)!
...    'Tenerife',
...    {'cielo': 'limpio', 'temp': 24},
...    3718,
...    (28.2933947, -16.5226597)
... ]
```
{ .annotate }

1. Una lista de 3 [cadenas de texto](../datatypes/strings.md).
2. Una lista de 8 [números enteros](../datatypes/numbers.md#integers).
3. La **lista vacía** (_0 elementos_).
4. Una lista **heterogénea** de 4 elementos de distinta naturaleza.

??? note "Datos heterogéneos"

    Una lista en Python (a diferencia de otros lenguajes de programación) puede contener **datos heterogéneos**. Esto hace de la lista una estructura de datos muy versátil.

!!! exercise "Ejercicio"

    Entra en el intérprete interactivo de Python <span class="green">❯❯❯</span> y crea una lista con las 5 ciudades que más te gusten.

## Conversión { #cast }

Para convertir otros tipos de datos en una lista podemos usar la función `#!python list()`. Por <span class="example">ejemplo:material-flash:</span> podemos convertir una _cadena de texto_ en una lista:

```pycon
>>> list('Python')
['P', 'y', 't', 'h', 'o', 'n']
```

Si nos fijamos en lo que ha pasado, al convertir la cadena de texto Python se ha creado una lista con 6 elementos, donde cada uno de ellos representa un carácter de la cadena. Podemos extender este comportamiento a cualquier otro tipo de datos que permita ser iterado (_iterables_).

Otro <span class="example">ejemplo:material-flash:</span> interesante de conversión puede ser la de los [rangos](../controlflow/loops.md#range). En este caso queremos obtener una **lista explícita** con los valores que constituyen el rango $[0,9]$:

```pycon
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

??? warning "Nombre de variable"

    Aunque está permitido, no suele ser una buena práctica llamar `list` a una variable ya que destruirías la función que nos permite trabajar con listas. Tampoco parece muy razonable utilizar nombres como `<algo>_list` o `list_<algo>` ya que no es necesario incluir en el nombre de una variable su propia naturaleza.

## Operaciones con listas { #operations }

Existen multitud de operaciones que se pueden realizar sobre listas. A continuación veremos la mayoría de ellas:

### Obtener un elemento { #get-item }

Igual que en el caso de las [cadenas de texto](../datatypes/strings.md), podemos obtener un elemento de una lista a través del **índice** (lugar) que ocupa. Veamos un <span class="example">ejemplo:material-flash:</span>:

```pycon
>>> shopping = ['Agua', 'Huevos', 'Aceite']

>>> shopping[0]
'Agua'

>>> shopping[1]
'Huevos'

>>> shopping[2]
'Aceite'

>>> shopping[-1]#(1)!
'Aceite'
```
{ .annotate }

1. ¡Aquí también funcionan los índices negativos!

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 final obtendremos un error ([excepción](../modularity/exceptions.md)):

```pycon hl_lines="7 13"
>>> shopping = ['Agua', 'Huevos', 'Aceite']

>>> shopping[3]
Traceback (most recent call last):
  Cell In[2], line 1
    shopping[3]
IndexError: list index out of range

>>> shopping[-5]
Traceback (most recent call last):
  Cell In[3], line 1
    shopping[-5]
IndexError: list index out of range
```

### Trocear una lista { #slicing }

El troceado de listas funciona de manera totalmente análoga al [troceado de cadenas](../datatypes/strings.md#slicing). Veamos algunos <span class="example">ejemplos:material-flash:</span>:

```pycon
>>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> shopping[:3]#(1)!
['Agua', 'Huevos', 'Aceite']

>>> shopping[2:4]
['Aceite', 'Sal']

>>> shopping[-1:-4:-1]
['Limón', 'Sal', 'Aceite']

>>> shopping[::-1]#(2)!
['Limón', 'Sal', 'Aceite', 'Huevos', 'Agua']
```
{ .annotate }

1. También podríamos haber escrito `#!python shopping[0:3]` aunque no es habitual.
2. Equivale a invertir la lista.

En el troceado de listas, a diferencia de lo que ocurre al obtener elementos, no debemos preocuparnos por acceder a índices no válidos (fuera de rango) ya que Python los restringirá a los límites de la lista:

```pycon
>>> shopping
['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> shopping[10:]
[]

>>> shopping[-100:2]
['Agua', 'Huevos']

>>> shopping[2:100]
['Aceite', 'Sal', 'Limón']
```

:material-check-all:{ .blue } Ninguna de las operaciones anteriores modifican la lista original, simplemente devuelven una lista nueva.

### Invertir una lista { #reverse }

Python nos ofrece varios mecanismos para invertir los elementos de una lista, en función del resultado que busquemos:

=== "Conservando la lista original :material-texture-box:"

    - **Opcion A**{ .pink } :material-arrow-right-box: Mediante [troceado de listas](#slicing) con «step» negativo:

        ```pycon
        >>> shopping
        ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']
        
        >>> shopping[::-1]
        ['Limón', 'Sal', 'Aceite', 'Huevos', 'Agua']
        ```

    - **Opcion B**{ .pink } :material-arrow-right-box: Mediante la función `#!python reversed()`:

        ```pycon
        >>> shopping
        ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']
        
        >>> list(reversed(shopping))
        ['Limón', 'Sal', 'Aceite', 'Huevos', 'Agua']
        ```

=== "Modificando la lista original :fontawesome-solid-bolt:"

    Mediante la función `#!python reverse()` (_nótese que es sin «d» al final_):

    ```pycon
    >>> shopping
    ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']
    
    >>> shopping.reverse()
    
    >>> shopping#(1)!
    ['Limón', 'Sal', 'Aceite', 'Huevos', 'Agua']
    ```
    { .annotate }
    
    1. Se ha modificado la lista original.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `slicerev`

### Añadir al final de la lista { #append }

Una de las operaciones más utilizadas en listas es **añadir elementos al final** de las mismas. Para ello Python nos ofrece la función `#!python append()`. Se trata de un método «destructivo» que modifica la lista original.

Veamos un <span class="example">ejemplo:material-flash:</span> donde añadimos un producto a la lista de la compra:

```pycon hl_lines="3"
>>> shopping = ['Agua', 'Huevos', 'Aceite']

>>> shopping.append('Atún')

>>> shopping
['Agua', 'Huevos', 'Aceite', 'Atún']
```

#### Patrón creación { #create-pattern }

Una forma 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 <span class="example">ejemplo:material-flash:</span> en el que queremos construir una lista con los números pares en el intervalo $[0,20]$:

```pycon
>>> even_numbers = []

>>> for num in range(20 + 1):#(1)!
...     if num % 2 == 0:
...         even_numbers.append(num)
...

>>> even_numbers
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
```
{ .annotate }

1. Para «llegar» al 20 hay que incrementar en una unidad.

### Añadir en cualquier posición { #insert }

Ya hemos visto cómo añadir elementos al final de una lista. Sin embargo, Python ofrece una función `#!python insert()` que vendría a ser una generalización de la anterior, para incorporar elementos en cualquier posición.

Simplemente debemos especificar el **índice de inserción** y el **elemento a insertar**. También se trata de una función destructiva.

En el siguiente <span class="example">ejemplo:material-flash:</span> insertamos dos nuevos productos a la lista de la compra en posiciones arbitrarias:

```pycon hl_lines="3 8"
>>> shopping = ['Agua', 'Huevos', 'Aceite']

>>> shopping.insert(1, 'Jamón')#(1)!

>>> shopping
['Agua', 'Jamón', 'Huevos', 'Aceite']

>>> shopping.insert(3, 'Queso')#(2)!

>>> shopping
['Agua', 'Jamón', 'Huevos', 'Queso', 'Aceite']
```
{ .annotate }

1. Se podría leer como: «Quiero que `#!python 'Jamón'` quede en la posición 1 de la lista».
2. Se podría leer como: «Quiero que `#!python 'Queso'` quede en la posición 3 de la lista».

Al igual que ocurría con el [troceado de listas](#slicing), en este tipo de inserciones no obtendremos un error si especificamos índices fuera de los límites de la lista. Estos se ajustarán al principio o al final en función del valor que indiquemos:

```pycon
>>> shopping = ['Agua', 'Huevos', 'Aceite']

>>> shopping.insert(100, 'Mermelada')#(1)!

>>> shopping
['Agua', 'Huevos', 'Aceite', 'Mermelada']

>>> shopping.insert(-100, 'Arroz')#(2)!

>>> shopping
['Arroz', 'Agua', 'Huevos', 'Aceite', 'Mermelada']
```
{ .annotate }

1. Se inserta lo más a la «derecha» posible.
2. Se inserta lo más a la «izquierda» posible.

!!! abstract "append vs insert"

    Podría existir la tentación de utilizar `#!python insert()` para **añadir elementos al final** de una lista...

    ```pycon hl_lines="2"
    >>> values = [1, 2, 3]
    >>> values.insert(len(values), 4)
    >>> values
    [1, 2, 3, 4]
    ```

    :material-alarm-light-off-outline:{ .red } ¡No lo hagas! Utiliza [`append()`](#append) :material-arrow-right-box: Es más eficiente y más legible.

### Repetir elementos { #repeat }

Al igual que con las [cadenas de texto](../datatypes/strings.md#repeat), el operador `#!python *` nos permite repetir los elementos de una lista.

Siguiendo con el <span class="example">ejemplo:material-flash:</span> de la _lista de la compra_, podríamos querer comprar 3 unidades de cada producto:

```pycon
>>> shopping = ['Agua', 'Huevos', 'Aceite']

>>> shopping * 3
['Agua',
 'Huevos',
 'Aceite',
 'Agua',
 'Huevos',
 'Aceite',
 'Agua',
 'Huevos',
 'Aceite']
```

### Combinar listas { #combine }

Python nos ofrece varios mecanismos para combinar dos listas, en función del resultado que busquemos:

=== "Conservando la lista original :material-texture-box:"

    Mediante el operador `+`:

    ```pycon hl_lines="4"
    >>> shopping = ['Agua', 'Huevos', 'Aceite']
    >>> fruitshop = ['Naranja', 'Manzana', 'Piña']
    
    >>> shopping + fruitshop
    ['Agua', 'Huevos', 'Aceite', 'Naranja', 'Manzana', 'Piña']
    ```

=== "Modificando la lista original :fontawesome-solid-bolt:"

    Mediante la función `#!python extend()`:

    ```pycon hl_lines="4"
    >>> shopping = ['Agua', 'Huevos', 'Aceite']
    >>> fruitshop = ['Naranja', 'Manzana', 'Piña']
    
    >>> shopping.extend(fruitshop)#(1)!
    
    >>> shopping
    ['Agua', 'Huevos', 'Aceite', 'Naranja', 'Manzana', 'Piña']
    ```
    { .annotate }
    
    1. Esto es equivalente a: `#!python shopping += fruitshop`

    Hay que tener en cuenta que `extend()` funciona adecuadamente si pasamos **una lista como argumento**. En otro caso, quizás los resultados no sean los esperados.

    Veamos un <span class="example">ejemplo:material-flash:</span>:

    ```pycon
    >>> shopping = ['Agua', 'Huevos', 'Aceite']

    >>> shopping.extend('Limón')#(1)!

    >>> shopping
    ['Agua', 'Huevos', 'Aceite', 'L', 'i', 'm', 'ó', 'n']
    ```
    { .annotate }

    1.  - `extend()` «recorre» (o itera) sobre cada uno de los elementos del objeto en cuestión.
        - Al ser una _cadena de texto_ cada elemento es un carácter.

Se podría pensar en utilizar `append()` **para combinar listas**. La realidad es que no funciona exactamente como esperamos; la segunda lista se añadiría **como una sublista** de la principal.

Veamos un <span class="example">ejemplo:material-flash:</span>:

```pycon
>>> shopping = ['Agua', 'Huevos', 'Aceite']
>>> fruitshop = ['Naranja', 'Manzana', 'Piña']

>>> shopping.append(fruitshop)

>>> shopping
['Agua', 'Huevos', 'Aceite', ['Naranja', 'Manzana', 'Piña']]
```

### Modificar listas { #modify }

Para modificar un elemento de una lista debemos acceder a su índice y asignar el valor correspondiente.

En el siguiente <span class="example">ejemplo:material-flash:</span> preferimos comprar jugo que agua:

```pycon hl_lines="6"
>>> shopping = ['Agua', 'Huevos', 'Aceite']

>>> shopping[0]
'Agua'

>>> shopping[0] = 'Jugo'

>>> shopping
['Jugo', 'Huevos', 'Aceite']
```

En el caso de acceder a un **índice no válido** de la lista, incluso para modificar, obtendremos un error:

```pycon hl_lines="5"
>>> shopping[100]
Traceback (most recent call last):
  Cell In[1], line 1
    shopping[100]
IndexError: list index out of range
```

#### Modificar con troceado

No sólo es posible modificar un elemento de cada vez, sino que podemos asignar valores a trozos de una lista.

En el siguiente <span class="example">ejemplo:material-flash:</span> reemplazamos _huevos, aceite y sal_ por _atún y pasta_:

```pycon hl_lines="6"
>>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> shopping[1:4]
['Huevos', 'Aceite', 'Sal']

>>> shopping[1:4] = ['Atún', 'Pasta']#(1)!

>>> shopping
['Agua', 'Atún', 'Pasta', 'Limón']
```
{ .annotate }

1. La lista que asignamos no necesariamente debe tener la misma longitud que el trozo que sustituimos.

### Borrar elementos { #remove }

Python nos ofrece varios mecanismos para borrar elementos de una lista:

=== "Por su índice :octicons-number-24:"

    Mediante la sentencia `#!python del`:

    ```pycon hl_lines="3"
    >>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']
    
    >>> del shopping[3]
    
    >>> shopping
    ['Agua', 'Huevos', 'Aceite', 'Limón']
    ```

=== "Por su valor :octicons-briefcase-24:"

    Mediante la función `#!python remove()`:

    ```pycon hl_lines="3"
    >>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']
    
    >>> shopping.remove('Sal')#(1)!
    
    >>> shopping
    ['Agua', 'Huevos', 'Aceite', 'Limón']
    ```
    { .annotate }
    
    1. Si existen valores duplicados, la función `remove()` sólo borarrá la primera ocurrencia.

=== "Por su índice (con extracción) :octicons-repo-pull-16:"

    La sentencia `#!python del` y la función `#!python remove()` efectivamente borran el elemento indicado de la lista, pero no «devuelven»[^1] nada. Sin embargo, Python nos ofrece la función `pop()` que además de borrar, nos «recupera» el elemento; algo así como una _extracción_. Lo podemos ver como una combinación de _acceso + borrado_:

    ```pycon hl_lines="3 10"
    >>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']
    
    >>> product = shopping.pop()#(1)!
    >>> product
    'Limón'
    
    >>> shopping
    ['Agua', 'Huevos', 'Aceite', 'Sal']
    
    >>> product = shopping.pop(2)#(2)!
    >>> product
    'Aceite'
    
    >>> shopping
    ['Agua', 'Huevos', 'Sal']
    ```
    { .annotate }
    
    1. Cuando no se indica el índice, Python extrae en último elemento. Equivale a: `#!python shopping.pop(-1)`
    2. Extraer el elemento en la posición 2.

=== "Por su rango :fontawesome-solid-pizza-slice:"

    Mediante [troceado de listas](#slicing):

    ```pycon hl_lines="3"
    >>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']
    
    >>> shopping[1:4] = []
    
    >>> shopping
    ['Agua', 'Limón']
    ```

#### Borrado completo de la lista { #clear }

Python nos ofrece varios mecanismos para borrar una lista por completo:

=== "Borrado in-situ :material-pail-remove:"

    Mediante la función `clear()`:

    ```pycon hl_lines="3"
    >>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']
    
    >>> shopping.clear()#(1)!
    
    >>> shopping
    []
    ```
    { .annotate }
    
    1. Misma zona de memoria.

=== "Reinicializando a vacío :fontawesome-solid-wine-glass-empty:"

    Mediante la asignación a lista vacía:

    ```pycon
    >>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']
    
    >>> shopping = []#(1)!
    
    >>> shopping
    []
    ```
    { .annotate }
    
    1. Nueva zona de memoria.

!!! info "Recolector de basura"

    La memoria que queda «en el limbo» después de asignar un nuevo valor a la lista es detectada por el [recolector de basura](https://dev.to/pragativerma18/understanding-pythons-garbage-collection-and-memory-optimization-4mi2) de Python, quien se encarga de liberar aquellos datos que no están referenciados por ninguna variable.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `qletters`

### Encontrar un elemento { #find }

Si queremos descubrir el índice que corresponde a un determinado valor dentro una lista debemos usar la función `#!python index()`.

Como <span class="example">ejemplo:material-flash:</span> supongamos que queremos encontrar el _aceite_ en nuestra lista de la compra:

```pycon hl_lines="3"
>>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> shopping.index('Aceite')
2
```

Hay que tener en cuenta que si el elemento que buscamos no está en la lista, obtendremos un error:

```pycon hl_lines="7"
>>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> shopping.index('Pollo')
Traceback (most recent call last):
  Cell In[2], line 1
    shopping.index('Pollo')
ValueError: 'Pollo' is not in list
```

!!! info "Múltiples ocurrencias"

    Si buscamos un valor que existe más de una vez en una lista, la función `index()` sólo nos devolverá **el índice de la primera ocurrencia**.

!!! warning "No existe `find`"

    En listas no disponemos de la función `find()` que sí estaba disponible para [cadenas de texto](../datatypes/strings.md#search).

### Pertenencia de un elemento { #in }

Si queremos comprobar la existencia de un determinado elemento en una lista, podríamos buscarlo por su índice, pero la **forma pitónica** de hacerlo es utilizar el operador `#!python in`.

Si no estamos seguros de si hemos incluido ciertos productos en nuestro <span class="example">ejemplo:material-flash:</span> de la lista de la compra, lo podemos comprobar de la siguiente manera:

```pycon
>>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> 'Aceite' in shopping  # ¿Apunté aceite? 🤔
True

>>> 'Pollo' in shopping   # ¿Apunté pollo? 🤔
False
```

!!! note "Valor booleano"

    El operador `in` siempre devuelve un [valor booleano](../datatypes/numbers.md#booleans), es decir, verdadero o falso.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `isogram`

### Longitud de una lista { #length }

Podemos conocer el número de elementos que tiene una lista mediante la función `#!python len()`.

Por <span class="example">ejemplo:material-flash:</span> para conocer la cantidad de productos de nuestra lista de la compra haríamos lo siguiente:

```pycon hl_lines="3"
>>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> len(shopping)
5
```

### Contar ocurrencias { #count }

Para contar cuántas veces aparece un determinado valor dentro de una lista podemos usar la función `count()`.

Un <span class="example">ejemplo:material-flash:</span> «divertido» de la serie [The Big Bang Theory](https://www.imdb.com/title/tt0898266/):

```pycon
>>> sheldon_greeting = ['Penny', 'Penny', 'Penny']

>>> sheldon_greeting.count('Howard')
0

>>> sheldon_greeting.count('Penny')
3
```

### Dividir «string» como lista { #split }

Una **tarea muy habitual** al trabajar con _cadenas de texto_ es **dividirlas** por algún tipo de separador. En este sentido, Python nos ofrece la función `split()`, que debemos usar anteponiendo el «string» que queramos dividir.

Veamos un <span class="example">ejemplo:material-flash:</span> con ciertos refranes:

```pycon hl_lines="2 6"
>>> proverb = 'No hay mal que por bien no venga'
>>> proverb.split()#(1)!
['No', 'hay', 'mal', 'que', 'por', 'bien', 'no', 'venga']

>>> tools = 'Martillo,Sierra,Destornillador'
>>> tools.split(',')#(2)!
['Martillo', 'Sierra', 'Destornillador']
```
{ .annotate }

1. Si no se indica nada, la función `split()` usa por defecto cualquier secuencia de espacios en blanco, tabuladores y saltos de línea como separador.
2. En este caso se ha indicado que el separador sea una coma.

Existe _una variante_ de `split()` en la que indicamos el **número máximo de divisiones**. Supongamos un <span class="example">ejemplo:material-flash:</span> en el que nos dan una URL y nos piden separar el dominio de la ruta:

```pycon hl_lines="6"
>>> url = 'python.org/downloads/releases'

>>> url.split('/')#(1)!
['python.org', 'downloads', 'releases']

>>> url.split('/', 1)#(2)!
['python.org', 'downloads/releases']
```
{ .annotate }

1. Aquí `split()` no nos está sirviendo mucho...
2. Al indicar el máximo de «una división» hemos conseguido el resultado.

:material-check-all:{ .blue } También existe la función `rsplit()` que se comporta exactamente igual que la función `split()` pero **empezando por la derecha**.

##### Diviendo líneas { #splitlines }

Otro caso de uso muy habitual es querer dividir un fragmento de texto por sus saltos de línea. Para ello Python nos proporciona la función [`splitlines()`](https://docs.python.org/es/3/library/stdtypes.html#str.splitlines)(1).
{ .annotate }

1. Admite un parámetro `keepends` (`#!python bool`) que indica si queremos mantener los saltos de línea o no.

Por <span class="example">ejemplo:material-flash:</span>, supongamos que tenemos una estrofa de la canción [«Cómo hablar» de Amaral](https://www.youtube.com/watch?v=DjkLlTLMNDs) y queremos obtener una lista con cada frase:

```python
>>> lyrics = """Si volviera a nacer, si empezara de nuevo
... Volvería a buscarte en mi nave del tiempo
... Es el destino quien nos lleva y nos guia
... Nos separa y nos une a traves de la vida"""

>>> lyrics.splitlines()
['Si volviera a nacer, si empezara de nuevo',
 'Volvería a buscarte en mi nave del tiempo',
 'Es el destino quien nos lleva y nos guia',
 'Nos separa y nos une a traves de la vida']
```

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `num-words`

#### Particionado de cadenas de texto { #partition }

Existe una forma algo más «elaborada» de dividir una cadena a través del **particionado**. Para ello podemos valernos de la función `partition()` que proporciona Python.

Esta función toma un argumento como separador, y divide la cadena de texto en 3 partes:

1. Lo que queda a la izquierda del separador.
2. El separador en sí mismo.
3. Lo que queda a la derecha del separador.

Veamos un <span class="example">ejemplo:material-flash:</span> muy sencillo a partir de una operación matemática:

```pycon hl_lines="3"
>>> text = '3+4'

>>> text.partition('+')#(1)!
('3', '+', '4')
```
{ .annotate }

1. Funciona igualmente con separadores de más de un carácter:

    ```pycon
    >>> text = '3//4'
    >>> text.partition('//')
    ('3', '//', '4')
    ```

:material-check-all:{ .blue } También existe la función `rpartition()` que se comporta exactamente igual que la función `partition()` pero **empezando por la derecha**.

### Unir lista como «string» { #join }

Dada una lista, podemos convetirla a una cadena de texto, uniendo todos sus elementos mediante algún **separador**. Para ello hacemos uso de la función `join()` con la siguiente estructura:

![Dark image](images/lists/join-list-dark.svg#only-dark)
![Light image](images/lists/join-list-light.svg#only-light)

Veamos varios <span class="example">ejemplos:material-flash:</span> uniendo los productos de la lista de la compra:

```pycon
>>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> ','.join(shopping)
'Agua,Huevos,Aceite,Sal,Limón'

>>> ' '.join(shopping)
'Agua Huevos Aceite Sal Limón'

>>> '|'.join(shopping)
'Agua|Huevos|Aceite|Sal|Limón'
```

:material-alarm-light:{.acc} Hay que tener en cuenta que `join()` **sólo funciona si todos sus elementos son cadenas de texto**:

```pycon hl_lines="5"
>>> ', '.join([1, 2, 3, 4, 5])
Traceback (most recent call last):
  Cell In[1], line 1
    ', '.join([1, 2, 3, 4, 5])
TypeError: sequence item 0: expected str instance, int found
```

??? tip "join vs split"

    La función `join()` es realmente la **opuesta** a la función [split()](#split).

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `fix-date`

### Ordenar una lista { #sort }

Python nos ofrece varios mecanismos para ordenar una lista:

=== "Conservando la lista original :material-texture-box:"

    Mediante la función `sorted()`:

    ```pycon hl_lines="3"
    >>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']
    
    >>> sorted(shopping)#(1)!
    ['Aceite', 'Agua', 'Huevos', 'Limón', 'Sal']
    ```
    { .annotate }
    
    1.  - `sorted()` «devuelve» una **nueva lista ordenada**.
        - La lista `shopping` se mantiene **intacta**.

=== "Modificando la lista original :fontawesome-solid-bolt:"
    
    Mediante la función `sort()`:

    ```pycon hl_lines="3"
    >>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']
    
    >>> shopping.sort()#(1)!
    
    >>> shopping
    ['Aceite', 'Agua', 'Huevos', 'Limón', 'Sal']
    ```
    { .annotate }
    
    1.  - `sort()` no devuelve nada.
        - Sólo se encarga de ordenar (modificar) la lista indicada `shopping`.

:material-check-all:{ .blue } **Ambos métodos** admiten un _parámetro_ «booleano» `reverse` para indicar si queremos que la ordenación se haga en **sentido inverso**:

```pycon hl_lines="3"
>>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> sorted(shopping, reverse=True)
['Sal', 'Limón', 'Huevos', 'Agua', 'Aceite']
```

### Iterar sobre una lista { #iterate }

Al igual que [hemos visto con las cadenas de texto](../controlflow/loops.md#for), también podemos iterar sobre los elementos de una lista utilizando la sentencia `for`.

Recorremos por <span class="example">ejemplo:material-flash:</span> los productos de la lista de la compra:

```pycon
>>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> for product in shopping:
...     print(product)
...
Agua
Huevos
Aceite
Sal
Limón
```

!!! info "break y continue"

    En esta estructura también es posible utilizar tanto `break` como `continue`.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `chars-list`

#### Iterar usando enumeración { #enumerate }

Hay veces que no sólo nos interesa **«visitar»** cada uno de los elementos de una lista, sino **también conocer su ^^índice^^** dentro de la misma. Para ello Python nos ofrece la función `enumerate()`.

Para el <span class="example">ejemplo:material-flash:</span> de la lista de la compra, nos podría interesar aplicar esta estructura programática:

```pycon hl_lines="3"
>>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> for index, product in enumerate(shopping):#(1)!
...     print(index, product)
...
0 Agua
1 Huevos
2 Aceite
3 Sal
4 Limón
```
{ .annotate }

1. En cada iteración del bucle las variables `index` y `product` reciben el índice y el producto existentes en la lista de la compra.

Por defecto `enumerate()` empieza sus índices en 0 (_como era de esperar_) pero si quisiéramos modificar este comportamiento también se podría.

<span class="example">Ejemplo:material-flash:</span> empezando la enumeración de la lista de la compra en 10:

```pycon hl_lines="3"
>>> shopping = ['Agua', 'Huevos', 'Aceite', 'Sal', 'Limón']

>>> for index, product in enumerate(shopping, 10):
...     print(index, product)
...
10 Agua
11 Huevos
12 Aceite
13 Sal
14 Limón
```

#### Iterar sobre múltiples listas { #zip }

Python ofrece la posibilidad de iterar sobre **múltiples listas en paralelo** utilizando la función `zip()`. Se basa en ir «juntando» ambas listas elemento a elemento:

![Zip diagram](images/lists/zip.svg)

Veamos un <span class="example">ejemplo:material-flash:</span> en el que añadimos _ciertos detalles_ a nuestra lista de la compra:

```pycon hl_lines="4"
>>> shopping = ['Agua', 'Aceite', 'Arroz']
>>> details = ['mineral natural', 'de oliva virgen', 'basmati']

>>> for product, detail in zip(shopping, details):#(1)!
...     print(product, detail)
...
Agua mineral natural
Aceite de oliva virgen
Arroz basmati
```
{ .annotate }

1. En cada iteración del bucle la variable `product` toma un elemento de la lista `shopping` y la variable `detail` toma un elemento de la lista `details`.

!!! tip "Distinta longitud"

    En el caso de que las listas no tengan la misma longitud, la función `zip()` realiza la combinación hasta que se agota la lista más corta.

Dado que [`zip()`](https://docs.python.org/3/library/functions.html#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:

```pycon
>>> shopping = ['Agua', 'Aceite', 'Arroz']
>>> details = ['mineral natural', 'de oliva virgen', 'basmati']

>>> list(zip(shopping, details))#(1)!
[('Agua', 'mineral natural'),
 ('Aceite', 'de oliva virgen'),
 ('Arroz', 'basmati')]
```
{ .annotate }

1. Formalmente lo que devuelve `#!python zip()` son [tuplas](./tuples.md), en este caso «envueltas» en una lista.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `dot-product`

### Comparar listas { #compare }

¿Cómo determina Python si una lista es mayor o menor que otra? Analicemos lo que ocurre en el siguiente <span class="example">ejemplo:material-flash:</span>:

```pycon
>>> [1, 2, 3] < [1, 2, 4]
True
```

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.

:material-check-all:{ .blue } Entender la forma en la que se comparan dos listas es importante para poder aplicar otras funciones y obtener los resultados deseados.

!!! abstract "Ver también"

    Esta comparación funciona de forma totalmente análoga a la [comparación de cadenas de texto](../datatypes/strings.md#compare).

## Cuidado con las copias { #copy }

Las listas son [estructuras de datos mutables](../datatypes/data.md#mutability) y esta característica nos obliga a tener cuidado cuando realizamos copias de listas, ya que la modificación de una de ellas puede afectar a la otra.

Veamos un <span class="example">ejemplo:material-flash:</span> sencillo:

```pycon hl_lines="3"
>>> original_list = [4, 3, 7, 1]

>>> copy_list = original_list#(1)!

>>> original_list[0] = 15#(2)!

>>> original_list#(3)!
[15, 3, 7, 1]

>>> copy_list#(4)!
[15, 3, 7, 1]
```
{ .annotate }

1. Con esta asignación hacemos que `copy_list` «apunte» a la misma _zona de memoria_ que `original_list`.
2. En esa _zona de memoria_ estamos modificando el primer elemento de la lista `original_list`.
3. Obviamente esta lista se habrá modificado.
4. También se ha modificado la «copia».

Una **posible solución** a este problema sería efectuar una «copia dura». Para ello Python proporciona la función `copy()`:

```pycon hl_lines="3"
>>> original_list = [4, 3, 7, 1]

>>> copy_list = original_list.copy()#(1)!

>>> original_list[0] = 15#(2)!

>>> original_list#(3)!
[15, 3, 7, 1]

>>> copy_list#(4)!
[4, 3, 7, 1]
```
{ .annotate }

1.  - La función `copy()` hace una copia en _otra zona de memoria_.
    - Aquí también valdría utilizar un troceado «completo» :material-arrow-right-box: `#!python copy_list = original_list[:]`
2. Modificamos el primer elemento de `original_list`.
3. La modificación de este elemento sólo afecta a `original_list`.
4. Dado que `copy_list` está en _otra zona de memoria_ no se ve afectada por el cambio.

??? info "Copia profunda"

    En el caso de que estemos trabajando con listas que contienen elementos mutables, debemos hacer uso de la función `deepcopy()` dentro del módulo `copy` de la librería estándar.

## Veracidad múltiple { #all-any }

Si bien podemos usar [sentencias condicionales](../controlflow/conditionals.md) para comprobar la veracidad de determinadas expresiones, Python nos ofrece dos funciones «built-in» con las que podemos evaluar si se cumplen **todas las condiciones** `all()` o si se cumple **alguna condición** `any()`. Estas funciones trabajan sobre iterables, y el caso más evidente es una **lista**.

Supongamos un <span class="example">ejemplo:material-flash:</span> en el que queremos comprobar **si una determinada palabra cumple las siguientes condiciones**:

- Su longitud total es mayor que 4.
- Empieza por «p».
- Contiene, al menos, una «y».

=== "Versión clásica"

    ```pycon
    >>> word = 'python'
    
    >>> if len(word) > 4 and word.startswith('p') and word.count('y') >= 1:
    ...     print('Cool word!')
    ... else:
    ...     print('No thanks')
    ...
    Cool word!
    ```

=== "Veracidad múltiple: `all()`"

    ```pycon hl_lines="7"
    >>> word = 'python'
    
    >>> enough_length = len(word) > 4#(1)!
    >>> right_beginning = word.startswith('p')#(2)!
    >>> min_ys = word.count('y') >= 1#(3)!
    
    >>> is_cool_word = all([enough_length, right_beginning, min_ys])#(4)!
    
    >>> if is_cool_word:
    ...     print('Cool word!')
    ... else:
    ...     print('No thanks')
    ...
    Cool word!
    ```
    { .annotate }
    
    1. `#!python True`
    2. `#!python True`
    3. `#!python True`
    4. `#!python all([True, True, True])` :material-arrow-right-box: `#!python True`
    
=== "Veracidad múltiple: `any()`"

    ```pycon hl_lines="7"
    >>> word = 'yeah'
    
    >>> enough_length = len(word) > 4#(1)!
    >>> right_beginning = word.startswith('p')#(2)!
    >>> min_ys = word.count('y') >= 1#(3)!
    
    >>> is_fine_word = any([enough_length, right_beginning, min_ys])#(4)!
    
    >>> if is_fine_word:
    ...     print('Fine word!')
    ... else:
    ...     print('No thanks')
    ...
    Fine word!
    ```
    { .annotate }
    
    1. `#!python False`
    2. `#!python False`
    3. `#!python True`
    4. `#!python any([False, False, True])` :material-arrow-right-box: `#!python True`
    
:material-check-all:{ .blue } Ojo con el comportamiento de estas funciones cuando trabajan con la **lista vacía**:

```pycon
>>> all([])
True

>>> any([])
False
```

!!! tip "Casos de uso"

    Este enfoque puede ser interesante cuando se manejan muchas condiciones o bien cuando queremos separar las condiciones y agruparlas en una única lista.

## Listas por comprensión { #comprehension }

Las listas por comprensión establecen una técnica para crear listas de forma más compacta basándose en el concepto matemático de [conjuntos definidos por comprensión](http://recursostic.educacion.es/descartes/web/materiales_didacticos/conjuntos_y_operaciones_agsm/conjuntos_12.html).

Podríamos decir que su sintaxis sigue un modelo **VLC (Value-Loop-Condition)** tal y como se muestra en la siguiente figura:

![Dark image](images/lists/list-comprehension-dark.svg#only-dark)
![Light image](images/lists/list-comprehension-light.svg#only-light)

Empecemos por un <span class="example">ejemplo:material-flash:</span> en el que convertimos una cadena de texto con valores numéricos en una lista con los mismos valores pero convertidos a enteros:

=== "Versión clásica :material-phone-classic:"

    ```pycon
    >>> values = '32,45,11,87,20,48'
    
    >>> int_values = []
    
    >>> for value in values.split(','):
    ...     int_value = int(value)
    ...     int_values.append(int_value)
    ...
    
    >>> int_values
    [32, 45, 11, 87, 20, 48]
    ```

=== "Versión por comprensión :material-code-brackets:"

    ```pycon hl_lines="3"
    >>> values = '32,45,11,87,20,48'
    
    >>> int_values = [int(value) for value in values.split(',')]
    
    >>> int_values
    [32, 45, 11, 87, 20, 48]
    ```

A continuación se presenta un diagrama con la transformación de la estructura clásica en listas por comprensión:

![Transformation of list comprehensions](./images/lists/list-comprehensions-transformation.svg)

### Condiciones en comprensiones { #comprehension-conditions }

También existe la posibilidad de incluir **condiciones** en las listas por comprensión.

Continuando con el <span class="example">ejemplo:material-flash:</span> anterior, supongamos que sólo queremos crear la lista con ^^aquellos valores que empiecen por el dígito 4^^:

```pycon hl_lines="3"
>>> values = '32,45,11,87,20,48'

>>> int_values = [int(v) for v in values.split(',') if v.startswith('4')]

>>> int_values
[45, 48]
```

### Anidamiento en comprensiones { #comprehension-nested }

En la iteración que usamos dentro de la lista por comprensión es posible usar [bucles anidados](../controlflow/loops.md#nested-loops).

Veamos un <span class="example">ejemplo:material-flash:</span> en el que generamos ^^todas las combinaciones de una serie de valores^^:

```pycon hl_lines="4"
>>> values = '32,45,11,87,20,48'
>>> svalues = values.split(',')

>>> combinations = [f'{v1}x{v2}' for v1 in svalues for v2 in svalues]

>>> combinations
['32x32',
 '32x45',
 '32x11',
 '32x87',
 '32x20',
 '32x48',
 '45x32',
 '45x45',
 ...
 '48x45',
 '48x11',
 '48x87',
 '48x20',
 '48x48']
```

!!! tip "Casos de uso"

    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_.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `fcomp`

## `sys.argv` { #sys-argv }
    
Cuando queramos ejecutar un programa Python desde **línea de comandos**, tendremos la posibilidad de acceder a los argumentos de dicho programa. Para ello se utiliza una lista «especial» que la encontramos dentro del módulo `sys` y que se denomina `argv`:

![Dark image](images/lists/sys-argv-dark.svg#only-dark)
![Light image](images/lists/sys-argv-light.svg#only-light)

Veamos una aplicación de lo anterior en un <span class="example">ejemplo:material-flash:</span> que **convierte un número decimal a una determinada base**, ambos argumentos pasados por línea de comandos:

```python title="dec2base.py"
import sys

number = int(sys.argv[1])#(1)!
tobase = int(sys.argv[2])#(2)!

match tobase:
    case 2:
        result = f'{number:b}'
    case 8:
        result = f'{number:o}'
    case 16:
        result = f'{number:x}'
    case _:
        result = None

if result is None:
    print(f'Base {tobase} not implemented!')
else:
    print(result)
```
{ .annotate }

1. El ~~primer~~ segundo argumento es el número a convertir.
2. El ~~segundo~~ tercer argumento es la base a la que convertir.

Si lo ejecutamos obtendríamos lo siguiente:

```console
$ python dec2base.py 65535 2
1111111111111111
```

## Funciones matemáticas { #math }

Python nos ofrece, entre otras[^2], estas **tres funciones matemáticas** básicas que se pueden aplicar sobre listas.

=== "Suma :octicons-diff-added-16:"

    Mediante la función `sum()`:

    ```pycon
    >>> data = [5, 3, 2, 8, 9, 1]
    >>> sum(data)
    28
    ```

=== "Máximo :octicons-arrow-up-16:"

    Mediante la función `max()`:

    ```pycon
    >>> data = [5, 3, 2, 8, 9, 1]
    >>> max(data)
    9
    ```    

=== "Mínimo :octicons-arrow-down-16:"

    Mediante la función `min()`:

    ```pycon
    >>> data = [5, 3, 2, 8, 9, 1]
    >>> min(data)
    1
    ```

!!! exercise "Ejercicio"

    Escribe un programa `avg.py` que reciba [desde línea de comandos](#sys-argv) una serie de números y calcule la **media de dichos valores** (_redondeando a 2 cifras decimales_).

    La llamada se haría de la siguiente manera:

    ```console
    $ python avg.py 32 56 21 99 12 17
    ```

    **Ejemplo:**

    - Entrada :material-arrow-left-box: `32 56 21 99 12 17`
    - Salida :material-arrow-right-box: `39.50`

    :octicons-light-bulb-16:{ .yellow } Ten en cuenta que `sys.argv` es una lista (como otra cualquiera) y que admite [troceado de listas](#slicing).

## Lista de listas { #list-of-lists }

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 <span class="example">ejemplo:material-flash:</span> para un contexto deportivo. Un equipo de fú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](https://es.wikipedia.org/wiki/Copa_Mundial_Femenina_de_F%C3%BAtbol_de_2023) en 2023 había una disposición _4-3-3_ con las siguientes jugadoras:

![Campeonas 2023](./images/lists/spain2023-worldchampions.svg)
///caption
Equipo campeón del Mundial de Fútbol en 2023
///

Veamos una posible representación de este equipo de fútbol usando **una lista compuesta de listas**. Primero definimos cada una de las líneas:

```pycon
>>> goalkeeper = 'Cata'
>>> defenders = ['Olga', 'Laia', 'Irene', 'Ona']
>>> midfielders = ['Jenni', 'Teresa', 'Aitana']
>>> forwards = ['Mariona', 'Salma', 'Alba']
```

Y ahora las juntamos en una única lista:

```pycon
>>> team = [goalkeeper, defenders, midfielders, forwards]

>>> team
['Cata',
 ['Olga', 'Laia', 'Irene', 'Ona'],
 ['Jenni', 'Teresa', 'Aitana'],
 ['Mariona', 'Salma', 'Alba']]
```

Podemos comprobar el **acceso a distintos elementos**:

```pycon
>>> team[0]#(1)!
'Cata'

>>> team[1][0]#(2)!
'Olga'

>>> team[2]#(3)!
['Jenni', 'Teresa', 'Aitana']

>>> team[3][1]#(4)!
'Salma'
```
{ .annotate }

1. Portera.
2. Lateral izquierdo.
3. Centrocampistas.
4. Delantera centro.

También podemos **recorrer toda la alineación** (por líneas):

```pycon
>>> for playline in team:
...     if isinstance(playline, list):#(1)!
...         for player in playline:
...             print(player, end=' ')
...         print()
...     else:
...         print(playline)
...
Cata
Olga Laia Irene Ona
Jenni Teresa Aitana
Mariona Salma Alba
```
{ .annotate }

1. Es necesario comprobar si es una lista porque la portera está «sola».

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `mul-matrix2`

## Ejercicios { #exercises }

1. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `max-value`
2. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `max-value-with-min`
3. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `min-value`
4. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `min-value-with-max`
5. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `remove-dups`
6. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `flatten-list`
7. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `remove-consecutive-dups`
8. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `all-same`
9. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `sum-diagonal`
10. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `powers2`
11. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `sum-mixed`
12. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `n-multiples`
13. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `drop-even`
14. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `nth-power`
15. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `name-initials`
16. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `non-consecutive`
17. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `mul-reduce`
18. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `digit-rev-list`
19. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `time-plus-minutes`
20. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `add-positives`
21. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `add-opposites`
22. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `descending-numbers`
23. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `merge-sorted`
24. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `trimmed-add`
25. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `wolves`
26. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `minmax`
27. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `cascading-subsets`
28. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `diff-cuboid`
29. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `fl-strip`
30. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `logical-chain`
31. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `first-unused-id`
32. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `find-odds`
33. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `chemistry`
34. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `next-item`
35. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `v-partition`
36. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `attach-len`
37. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `reversing-words`
38. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `barycenter`
39. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `sort-custom`
40. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `flatten-list-deep`
41. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `first-duplicated`
42. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `fill-values`
43. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `frange`
44. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `qual-name`
45. [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `mul-matrix`


[^1]: Más adelante veremos el comportamiento de las [funciones](../modularity/functions.md). Devolver o retornar un valor es el resultado de aplicar una función.
[^2]: Existen multitud de paquetes científicos en Python para trabajar con listas o vectores numéricos. Una de las más famosas es la librería [Numpy](../../third-party/data-science/numpy.md).


================================================
FILE: docs/core/datastructures/sets.md
================================================
---
icon: material/set-center
tags:
  - Fundamentos del lenguaje
  - Estructuras de datos
  - Conjuntos
---

# Conjuntos { #sets }

![Banner](images/sets/banner.jpg)
/// caption
Imagen generada con Inteligencia Artificial
///

Un **conjunto** en Python es una estructura de datos que almacena valores **únicos** y **sin orden** establecido. Mantiene muchas similitudes con el [concepto matemático de conjunto](https://es.wikipedia.org/wiki/Conjunto).

## Creando conjuntos { #create }

Para crear un conjunto usamos llaves `{}` rodeando valores separados por comas.

Veamos algunos <span class="example">ejemplos:material-flash:</span> de conjuntos:

```pycon
>>> marks = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}#(1)!

>>> vowels = {'a', 'e', 'i', 'o', 'u'}#(2)!

>>> continents = {'Europa', 'América', 'Asia', 'África', 'Oceanía'}#(3)!
```
{ .annotate }

1. Un conjunto con las posibles notas de una prueba.
2. Un conjunto con las vocales (en español).
3. Un conjunto con los continentes.

La excepción la tenemos a la hora de crear un **conjunto vacío**, ya que, siguiendo la lógica de apartados anteriores, deberíamos hacerlo a través de llaves:

```pycon
# DON'T DO THIS!
>>> wrong_empty_set = {}#(1)!

>>> type(wrong_empty_set)
dict
```
{ .annotate }

1. Si hacemos esto, estaremos creando un **diccionario vacío**.

La única opción para crear un _conjunto vacío_ es utilizar la función `#!python set()`:

```pycon
>>> empty_set = set()

>>> type(empty_set)
set
```

!!! exercise "Ejercicio"

    Entra en el intérprete interactivo de Python <span class="green">❯❯❯</span> y crea un conjunto con tus tres colores favoritos.

## Conversión { #cast }

Para convertir otros tipos de datos en un conjunto podemos usar la función `#!python set()`.

Veamos varios <span class="example">ejemplos:material-flash:</span> donde creamos un conjunto a partir de...

=== "... una cadena de texto"

    ```pycon
    >>> set('aplatanada')
    {'a', 'd', 'l', 'n', 'p', 't'}
    ```

=== "... una lista"

    ```pycon
    >>> set([1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5])
    {1, 2, 3, 4, 5}
    ```

=== "... una tupla"

    ```pycon
    >>> set(('ADENINA', 'TIMINA', 'TIMINA', 'GUANINA', 'ADENINA', 'CITOSINA'))
    {'ADENINA', 'CITOSINA', 'GUANINA', 'TIMINA'}
    ```    

=== "... un diccionario"

    ```pycon
    >>> set({'manzana': 'rojo', 'plátano': 'amarillo', 'kiwi': 'verde'})
    {'kiwi', 'manzana', 'plátano'}
    ```

!!! info "Valores únicos"

    Como se puede ver en los ejemplos anteriores, `#!python set()` es utilizado habitualmente como mecanismo para **extraer los valores únicos** de otras estructuras de datos.

!!! warning "Orden"

    Python [almacena los conjuntos](https://www.reddit.com/r/learnpython/comments/1b0aama/comment/ks7bjvm/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button) ordenados según [el valor «hash»](dicts.md#hashables) de sus elementos. Pero esto es únicamente un «detalle de implementación» en el que no se puede confiar.[^1]

## Operaciones con conjuntos { #operations }

Existen multitud de operaciones que se pueden realizar sobre conjuntos. A continuación veremos la mayoría de ellas:

### Obtener un elemento { #get-item }

Como ya se ha indicado previamente los conjuntos no tienen un orden establecido, es por ello que **no podemos acceder a un elemento en concreto**.

Como consecuencia de lo anterior, **tampoco podemos modificar 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 { #add }

Para añadir un elemento a un conjunto debemos utilizar la función `#!python add()`. Al no importar el orden dentro del conjunto, la inserción no establece a priori la posición donde se realizará.

A modo de <span class="example">ejemplo:material-flash:</span> vamos a partir de un conjunto que representa a los **cuatro integrantes originales** del la banda pop británica más famosa de todos los tiempos: [The Beatles](https://es.wikipedia.org/wiki/The_Beatles).

```pycon
>>> beatles = set(['Lennon', 'McCartney', 'Harrison', 'Starr'])#(1)!
```
{ .annotate }

1. John Lennon, Paul McCartney, George Harrison y Ringo Starr

Ahora vamos a añadir a [Pete Best](https://es.wikipedia.org/wiki/Pete_Best) a la banda...

```pycon
>>> beatles.add('Best')

>>> beatles
{'Best', 'Harrison', 'Lennon', 'McCartney', 'Starr'}
```

!!! warning "Recordatorio"

    No confundir la función [`append()`](lists.md#append) de las listas con la función `#!python add()` de los conjuntos. Ambas sirve para añadir elementos pero en estructuras de diferente naturaleza.

El siguiente fragmento de código nos demuestra claramente que, aunque lo intentemos por **fuerza bruta** :sweat_smile: nunca vamos a poder añadir _elementos repetidos_ en un conjunto:

```pycon
>>> items = set()#(1)!

>>> for _ in range(1_000_000):#(2)!
...     items.add('z')
...

>>> items#(3)!
{'z'}
```
{ .annotate }

1. Creamos un conjunto vacío.
2. Tratamos de añadir el emento `#!python 'z'` un millón de veces.
3. Sólo obtendremos una `#!python 'z'`.

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `tuple-set`

### Borrar elementos { #remove }

Para borrar un elemento de un conjunto podemos utilizar la función `#!python remove()`. Siguiendo con el <span class="example">ejemplo:material-flash:</span> anterior, vamos a borrar el último «beatle» añadido:

```pycon
>>> beatles
{'Best', 'Harrison', 'Lennon', 'McCartney', 'Starr'}

>>> beatles.remove('Best')

>>> beatles
{'Harrison', 'Lennon', 'McCartney', 'Starr'}
```

Si tratamos de **borrar un elemento que no existe** obrendremos un `#!python KeyError` (al igual que ocurría en los [diccionarios](dicts.md#get-item)):

```pycon hl_lines="5"
>>> beatles.remove('Sinatra')
Traceback (most recent call last):
  Cell In[1], line 1
    beatles.remove('Sinatra')
KeyError: 'Sinatra'
```

!!! tip "Borrado sin errores"

    Existe una función que permite borrar elementos de un conjunto sin quejarse cuando no existan:

    ```python
    >>> beatles.discard('Sinatra')
    ```

### Longitud de un conjunto { #length }

Podemos conocer el número de elementos ([cardinalidad](https://es.wikipedia.org/wiki/Cardinalidad)) que tiene un conjunto mediante la función `#!python len()`.

De vueltas con el <span class="example">ejemplo:material-flash:</span> de «The Beatles», tendríamos:

```pycon
>>> beatles
{'Harrison', 'Lennon', 'McCartney', 'Starr'}

>>> len(beatles)
4
```

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `diverse-word`

### Iterar sobre un conjunto { #iterate }    

Tal y como se ha visto para otros tipos de datos _iterables_, la forma de recorrer los elementos de un conjunto es utilizar la sentencia `#!python for`.

Para el <span class="example">ejemplo:material-flash:</span> anterior sería muy sencillo:

```pycon
>>> for beatle in beatles:
...     print(beatle)
...
Harrison
McCartney
Starr
Lennon
```

### Pertenencia de un elemento { #in }

Al igual que con otros tipos de datos, Python nos ofrece el operador `#!python in` para determinar si un elemento pertenece a un conjunto.

Veamos cómo aplicarlo en el <span class="example">ejemplo:material-flash:</span> de «The Beatles»:

```pycon
>>> beatles
{'Harrison', 'Lennon', 'McCartney', 'Starr'}

>>> 'Lennon' in beatles
True

>>> 'Fari' in beatles
False

>>> 'Fari' not in beatles
True
```

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `half-out`

### Ordenar un conjunto { #sort }

Ya hemos comentado que los conjuntos **no mantienen un orden**. ¿Pero qué ocurre si intentamos ordenarlo?

Veamos el comportamiento a través del <span class="example">ejemplo:material-flash:</span> anterior:

```pycon
>>> beatles
{'McCartney', 'Starr', 'Lennon', 'Harrison'}

>>> sorted(beatles)#(1)!
['Harrison', 'Lennon', 'McCartney', 'Starr']
```
{ .annotate }

1. Lo que obtenemos es **una lista** con los elementos ordenados.

Obviamente, el uso de `#!python .sort()` no está permitido sobre un conjunto:

```pycon hl_lines="5"
>>> beatles.sort()
Traceback (most recent call last):
  Cell In[1], line 1
    beatles.sort()
AttributeError: 'set' object has no attribute 'sort'
```

## Teoría de conjuntos { #set-theory }

A continuación veremos las distintas operaciones sobre conjuntos que se pueden hacer en Python basadas en los [Diagramas de Venn](https://es.wikipedia.org/wiki/Diagrama_de_Venn) y la [Teoría de conjuntos](https://es.wikipedia.org/wiki/Teor%C3%ADa_de_conjuntos):

### Aritmética { #arithmetic }

Partimos de dos conjuntos $A = \{1, 2\}$ y $B = \{2, 3\}$:

```pycon
>>> A = {1, 2}
>>> B = {2, 3}
```

Se definen las siguientes operaciones «aritméticas» entre conjuntos:

![Dark image](images/sets/venn-dark.svg#only-dark)
![Light image](images/sets/venn-light.svg#only-light)

<div class="grid cards" markdown>

-   **Intersección** :material-set-center: $A \cap B$

    ---

    Elementos comunes de $A$ y $B$.

    ![Dark image](images/sets/venn-intersection-dark.svg#only-dark)
    ![Light image](images/sets/venn-intersection-light.svg#only-light)

    ```pycon
    >>> A & B#(1)!
    {2}
    ```
    { .annotate }
    
    1. Equivalente: `#!python A.intersection(B)`

-   **Unión** :material-set-all: $A \cup B$

    ---

    Elementos que están en $A$, en $B$ o ambos.

    ![Dark image](images/sets/venn-union-dark.svg#only-dark)
    ![Light image](images/sets/venn-union-light.svg#only-light)

    ```pycon
    >>> A | B#(1)!
    {1, 2, 3}
    ```
    { .annotate }
    
    1. Equivalente: `#!python A.union(B)`

-   **Diferencia** :material-set-left: $A \setminus B$

    ---

    Elementos de $A$ que no están en $B$.

    ![Dark image](images/sets/venn-difference-dark.svg#only-dark)
    ![Light image](images/sets/venn-difference-light.svg#only-light)

    ```pycon
    >>> A - B#(1)!
    {1}
    ```
    { .annotate }
    
    1. Equivalente: `#!python A.difference(B)`

-   **Diferencia simétrica** :material-set-left-right: $A \triangle B$

    ---

    Elementos no comunes de $A$ y $B$.

    ![Dark image](images/sets/venn-symdiff-dark.svg#only-dark)
    ![Light image](images/sets/venn-symdiff-light.svg#only-light)

    ```pycon
    >>> A ^ B#(1)!
    {1, 3}
    ```
    { .annotate }
    
    1. Equivalente: `#!python A.symmetric_difference(B)`

</div>

Todos las operaciones anteriores admiten una versión funcional:

| Operación | Equivalente | Actualizando $A$ |
| --- | --- | --- |
| $A \cap B$ | `#!python A.intersection(B)` | `#!python A.intersection_update(B)` |
| $A \cup B$ | `#!python A.union(B)` | `#!python A.update(B)` |
| $A \setminus B$ | `#!python A.difference(B)` | `#!python A.difference_update(B)` |
| $A \triangle B$ | `#!python A.symmetric_difference(B)` | `#!python A.symmetric_difference_update(B)` |

### Inclusión { #inclusion }

Partimos de dos conjuntos $A = \{1, 2\}$ y $B = \{2, 3\}$:

```pycon
>>> A = {1, 3, 5, 7, 9}
>>> B = {3, 5, 7}
```

Las operaciones de inclusión tratan de descubrir si un conjunto «abarca» a otro (o viceversa).

<div class="grid cards" markdown>

-   **Subconjunto** $B \subset A$

    ---

    Todos los elementos de $B$ están en $A$.

    ![Subset](images/sets/inclusion.svg)

    «$B$ es un subconjunto de $A$»

    ```pycon
    >>> B < A#(1)!
    True
    ```
    { .annotate }
    
    1. También existe `#!python B <= A`

-   **Superconjunto** $A \supset B$

    ---

    Los elementos de $A$ contienen a los de $B$.

    ![Subset](images/sets/inclusion.svg)

    «$A$ es un superconjunto de $B$»

    ```pycon
    >>> A > B#(1)!
    True
    ```
    { .annotate }
    
    1. También existe `#!python A >= B`

</div>

## Conjuntos por comprensión { #comprehension }

Los conjuntos, al igual que las [listas](lists.md#comprehension) y los [diccionarios](dicts.md#comprehension), también se pueden crear por **comprensión**.

Veamos un <span class="example">ejemplo:material-flash:</span> en el que construimos un _conjunto por comprensión_ con aquellos números enteros múltiplos de 3 en el rango $[0,20)$:

```pycon
>>> m3 = {number for number in range(0, 20) if number % 3 == 0}

>>> m3
{0, 3, 6, 9, 12, 15, 18}
```

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `common-consonants`

## Conjuntos inmutables { #frozensets }

Python ofrece la posibilidad de crear **conjuntos inmutables** haciendo uso de la función `#!python frozenset()` que recibe cualquier iterable como argumento.

Supongamos por <span class="example">ejemplo:material-flash:</span> que recibimos una serie de calificaciones de exámenes y queremos crear un conjunto inmutable con los posibles niveles (categorías) de calificaciones:

```pycon hl_lines="6"
>>> marks = [1, 3, 2, 3, 1, 4, 2, 4, 5, 2, 5, 5, 3, 1, 4]

>>> mark_levels = frozenset(marks)

>>> mark_levels
frozenset({1, 2, 3, 4, 5})
```

Veamos qué ocurre si intentamos modificar este conjunto:

```pycon hl_lines="5"
>>> mark_levels.add(100)#(1)!
Traceback (most recent call last):
  Cell In[1], line 1
    mark_levels.add(100)
AttributeError: 'frozenset' object has no attribute 'add'
```
{ .annotate }

1. Al ser inmutable no permite su modificación.

!!! tip "Correspondencia"

    Un `frozenset` es a un `set` lo que una `tuple` es a una `list`: una forma de «congelar» los valores para que no se puedan modificar.

## Objetos «hashables» { #hashables }

La única restricción que deben cumplir los elementos de un conjunto es ser [hashables](dicts.md#hashables). Un objeto es «hashable» si se le puede asignar un valor «hash» que no cambia en ejecución durante toda su vida.

Supongamos un <span class="example">ejemplo:material-flash:</span> en el que estamos construyendo un conjunto con los [elementos químicos](https://es.wikipedia.org/wiki/Elemento_qu%C3%ADmico) de la tabla periódica.

Si intentamos añadir una [lista](./lists.md) como clave de un diccionario obtendremos el siguiente error:

```pycon hl_lines="8"
>>> periodic_table = set()
>>> metals = ['Fe', 'Mg', 'Au', 'Ag', 'Zn']

>>> periodic_table.add(metals)#(1)!
Traceback (most recent call last):
  Cell In[3], line 1
    periodic_table.add(metals)
TypeError: unhashable type: 'list'
```
{ .annotate }

1. Una lista no es un objeto «hashable» derivado de su condición de mutabilidad.

Sin embargo, podríamos conseguir lo que buscamos si, en vez de listas, usáramos **tuplas** para almacenar los elementos químicos (ya que sí son «hashables»):

```pycon
>>> periodic_table = set()

>>> metals = ('Fe', 'Mg', 'Au', 'Ag', 'Zn')
>>> periodic_table.add(metals)

>>> non_metals = ('C', 'H', 'O', 'F', 'Cl')
>>> periodic_table.add(non_metals)

>>> periodic_table
{('Fe', 'Mg', 'Au', 'Ag', 'Zn'), ('C', 'H', 'O', 'F', 'Cl')}
```

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `is-binary`


[^1]: La ordenación de los elementos de un conjunto no está definida en el estándar de Python. Otra cuestión es que exista una ordenación por su «hash» para la implementación concreta de CPython.


================================================
FILE: docs/core/datastructures/tuples.md
================================================
---
icon: octicons/link-16
tags:
  - Fundamentos del lenguaje
  - Estructuras de datos
  - Tuplas
---

# Tuplas { #tuples }

![Banner](images/tuples/banner.jpg)
/// caption
Imagen generada con Inteligencia Artificial
///

El concepto de **tupla** es muy similar al de [lista](lists.md). Aunque hay algunas diferencias menores, lo fundamental es que, mientras una _lista_ es mutable y se puede modificar, una _tupla_ no admite cambios y por lo tanto, es **inmutable**.

## Creando tuplas { #create }

Podemos pensar en crear tuplas tal y como [lo hacíamos con listas](lists.md#create), pero usando **paréntesis** en lugar de _corchetes_:

```pycon
>>> tenerife_geoloc = (28.46824, -16.25462)#(1)!

>>> three_wise_men = ('Melchor', 'Gaspar', 'Baltasar')#(2)!

>>> empty_tuple = ()#(3)!

>>> data = ('Welcome', 17, [0.1, 0.2], True)#(4)!
```
{ .annotate }

1. Una tupla de 2 [números flotantes](../datatypes/numbers.md#floats) representando _latitud_ y _longitud_.
2. Una tupla de 3 [cadenas de texto](../datatypes/strings.md).
3. La **tupla vacía** (_0 elementos_).
4. Una tupla **heterogénea** de 4 elementos de distinta naturaleza.

### Tuplas de un elemento

Hay que prestar especial atención cuando vamos a crear una **tupla de un único elemento**:

=== "Forma incorrecta :material-thumb-down:"

    La intención primera sería hacerlo de la siguiente manera:

    ```pycon
    >>> one_item_tuple = ('Papá Noel')#(1)!

    >>> one_item_tuple
    'Papá Noel'

    >>> type(one_item_tuple)#(2)!
    str
    ```
    { .annotate }

    1. Los paréntesis se ignoran en este contexto.
    2. Lo que hemos creado realmente es una _cadena de texto_.

=== "Forma correcta :material-thumb-up:"

    Para crear una tupla de un elemento debemos **añadir una coma al final**:

    ```pycon hl_lines="1"
    >>> one_item_tuple = ('Papá Noel',)

    >>> one_item_tuple
    ('Papá Noel',)

    >>> type(one_item_tuple)
    tuple
    ```

### Tuplas sin paréntesis

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 efectos prácticos tiene el mismo resultado.

Veamos algunos <span class="example">ejemplos:material-flash:</span> de ello:

```pycon
>>> one_item_tuple = 'Papá Noel',

>>> three_wise_men = 'Melchor', 'Gaspar', 'Baltasar'

>>> tenerife_geoloc = 28.46824, -16.25462
```

## Conversión

Para convertir otros tipos de datos en una tupla podemos usar la función `tuple()`. Por <span class="example">ejemplo:material-flash:</span> podemos convertir una _lista_ en una tupla:

```pycon
>>> shopping = ['Agua', 'Aceite', 'Arroz']

>>> 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 <span class="example">ejemplo:material-flash:</span> que no funciona es intentar _convertir un número en una tupla_:

```pycon hl_lines="5"
>>> tuple(5)
Traceback (most recent call last):
  Cell In[1], line 1
    tuple(5)
TypeError: 'int' object is not iterable
```

??? warning "Nombre de variable"

    Aunque está permitido, no suele ser una buena práctica llamar `tuple` a una variable ya que destruirías la función que nos permite trabajar con tuplas. Tampoco parece muy razonable utilizar nombres como `<algo>_tuple` o `tuple_<algo>` ya que no es necesario incluir en el nombre de una variable su propia naturaleza.

## Modificar una tupla { #modify }

Como se ha comentado al principio de este capítulo, las tuplas son **estructuras de datos inmutables**. Una vez que las creamos con un valor, no podemos modificarlas.

Veamos qué ocurre en el siguiente <span class="example">ejemplo:material-flash:</span> donde queremos suplantar al Rey Mago _Melchor_:

```pycon hl_lines="7"
>>> three_wise_men = 'Melchor', 'Gaspar', 'Baltasar'

>>> three_wise_men[0] = 'Tom Hanks'
Traceback (most recent call last):
  Cell In[2], line 1
    three_wise_men[0] = 'Tom Hanks'
TypeError: 'tuple' object does not support item assignment
```
    
## Operaciones con tuplas { #operations }

Con las tuplas podemos realizar [todas las operaciones que vimos con listas](lists.md#operations) **salvo las que conlleven una modificación** «in-situ» de la misma:

- `reverse()`
- `append()`
- `extend()`
- `remove()`
- `clear()`
- `sort()`

Un par de detalles:

<div class="annotate" markdown>
- Sí es posible aplicar `sorted()` o `reversed()` sobre una tupla ya que no estamos modificando su valor sino creando un nuevo objeto.(1)
- La comparación de tuplas funciona exactamente igual que la [comparación de listas](lists.md#compare).
</div>
1. Estas funciones devuelven **una lista** en vez de una tupla.

## Desempaquetado de tuplas { #unpack }

El **desempaquetado** es una característica de las tuplas que nos permite **asignar una tupla a variables independientes**:

![Dark image](images/tuples/tuple-unpacking-dark.svg#only-dark)
![Light image](images/tuples/tuple-unpacking-light.svg#only-light)

Veamos un <span class="example">ejemplo:material-flash:</span> desempaquetando a los tres Reyes Magos:

```pycon hl_lines="3"
>>> three_wise_men = ('Melchor', 'Gaspar', 'Baltasar')

>>> king1, king2, king3 = three_wise_men

>>> king1
'Melchor'
>>> king2
'Gaspar'
>>> king3
'Baltasar'
```

Veamos otro <span class="example">ejemplo:material-flash:</span> en el que utilizamos la función «built-in» [`divmod()`](https://docs.python.org/3/library/functions.html#divmod) que devuelve el cociente y el resto de una división usando una única llamada. Lo interesante (para el caso que nos ocupa) es que se suele utilizar el desempaquetado de tuplas para obtener los valores por separado:

```pycon
>>> quotient, remainder = divmod(7, 3)#(1)!

>>> quotient
2
>>> remainder
1
```
{ .annotate }

1.  
    ```
    7 │ 3
      └————
    1   2
    ```

!!! exercise "Ejercicio"

    [pypas](../../third-party/learning/pypas.md) &nbsp;:fontawesome-solid-hand-holding-heart:{ .acc .slide } `dec2bin`

### Intercambio de valores { #swap-values }

Veamos cómo implementar el intercambio de los valores de dos variables:

=== "Forma «tradicional» :material-town-hall:"

    Necesitamos usar una variable intermedia $z$ para no perder valores:

    ```pycon hl_lines="4"
    >>> a = 1
    >>> b = 2

    >>> z = a

    >>> a = b
    >>> b = z

    >>> a
    2
    >>> b
    1
    ```

=== "Forma directa :material-sign-direction:"

    Usando desempaquetado podemos hacerlo de forma directa:

    ```pycon hl_lines="4"
    >>> a = 1
    >>> b = 2

    >>> a, b = b, a

    >>> a
    2
    >>> b
    1
    ```
    
### Desempaquetado extendido { #extended-unpacking }

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 posibles implementaciones del _desempaquetado extendido_ en un <span class="example">ejemplo:material-flash:</span> de «ranking» de lenguajes de programación:

=== "Primero, otros y último"

    ```pycon hl_lines="3"
    >>> ranking = ('Python', 'Java', 'C', 'TypeScript', 'Rust')

    >>> first, *others, last = ranking

    >>> first
    'Python'
    >>> others
    ['Java', 'C', 'TypeScript']
    >>> last
    'Rust'
    ```
    
=== "Primero y otros"

    ```pycon hl_lines="3"
    >>> ranking = ('Python', 'Java', 'C', 'TypeScript', 'Rust')

    >>> first, *others = ranking

    >>> first
    'Python'
    >>> others
    ['Java', 'C', 'TypeScript', 'Rust']
    ```

=== "Primero, segundo y otros"

    ```pycon hl_lines="3"
    >>> ranking = ('Python', 'Java', 'C', 'TypeScript', 'Rust')

    >>> first, second, *others = ranking

    >>> first
    'Python'
    >>> second
    'Java'
    >>> others
    ['C', 'TypeScript', 'Rust']
    ```

=== "Otros, penúltimo y último"

    ```pycon hl_lines="3"
    >>> ranking = ('Python', 'Java', 'C', 'TypeScript', 'Rust')

    >>> *others, next_to_last, last = ranking

    >>> others
    ['Python', 'Java', 'C']
    >>> next_to_last
    'TypeScript'
    >>> last
    'Rust'
    ```

=== "Otros y último"

    ```pycon hl_lines="3"
    >>> ranking = ('Python', 'Java', 'C', 'TypeScript', 'Rust')

    >>> *others, last = ranking

    >>> others
    ['Python', 'Java', 'C', 'TypeScript']
    >>> last
    'Rust'
    ```

??? note "Limitación al desempaquetado extendido"

    La condición necesaria para realizar desempaquetado extendido es que el número de elementos de destino debe ser **menor o igual** al número de elementos de origen.

### Desempaquetado genérico { #generic-unpacking }

El desempaquetado de tuplas es **extensible a cualquier otro tipo de datos que sea iterable**.

Veamos algunos <span class="example">ejemplos:material-flash:</span>:

=== "Sobre cadenas de texto :material-format-quote-open:"

    ```pycon
    >>> oxygen = 'O2'
    >>> first, last = oxygen
    >>> first, last
    ('O', '2')
    
    >>> text = 'Hello, World!'
    >>> head, *body, tail = text
    >>> head, body, tail
    ('H', ['e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd'], '!')
    ```

=== "Sobre listas :material-code-brackets:"

    ```pycon
    >>> writer1, writer2, writer3 = ['Virginia Woolf', 'Jane Austen', 'Mary Shelley']
    >>> writer1, writer2, writer3
    ('Virginia Woolf', 'Jane Austen', 'Mary Shelley')
    
    >>> text = 'Hello, World!'
    >>> word1, word2 = text.split()
    >>> word1, word2
    ('Hello,', 'World!')
    ```

## ¿Tuplas por comprensión? { #tuple-comprehension }

Los tipos de datos mutables (_listas, diccionarios y conjuntos_) sí permiten comprensiones pero **no así los tipos de datos inmutables** como cadenas de texto y tuplas.

Si intentamos crear una **tupla por comprensión** utilizando paréntesis alrededor de la expresión, vemos que no aparece ningún error al ejecutarlo:

```pycon
>>> myrange = (number for number in range(1, 6))
```

Sin embargo lo que obtendremos **no es una tupla** sino un [generador](../modularity/functions.md#generators):

```pycon
>>> myrange
<generator object <genexpr> at 0x10b3732e0>
```

## Tuplas vs Listas { #tuples-vs-lists }

Aunque las tuplas y las listas puedan parecer estructuras de datos muy similares, sabemos que las tuplas carecen de ciertas operaciones, especialmente las que tienen que ver con la modificación de sus valores, ya que no son inmutables.

Si las listas son más flexibles y potentes, **¿por qué íbamos a necesitar tuplas?** Veamos 4 potenciales ^^ventajas^^ del uso de tuplas frente a las listas:

1. Las tuplas ocupan **menos espacio** en memoria.
2. En las tuplas existe **protección** frente a cambios indeseados.
3. Las tuplas se pueden usar como **claves de diccionarios** (son [«hashables»](dicts.md#hashables)).
4. Las [`namedtuples`](https://docs.python.org/es/3/library/collections.html#collections.namedtuple) son una alternativa sencilla a los objetos.


================================================
FILE: docs/core/datatypes/data.md
================================================
---
icon: fontawesome/solid/memory
tags:
  - Fundamentos del lenguaje
  - Tipos de datos
  - Datos
---

# Datos { #data }

![Banner](images/data/banner.jpg)
/// caption
Imagen generada con Inteligencia Artificial
///

Los programas están formados por **código** y **datos**[^1]. Pero a nivel interno de la memoria del ordenador (RAM) no son más que una secuencia de bits `100101101...` 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[^2].

!!! quote "Rich Hickey"

    «Programming is not about typing, it's about thinking»

Desde la perspectiva de un lenguaje de programación, cada «trozo» de memoria contiene realmente un objeto, de ahí que se diga que en Python **todo son objetos**. Y cada objeto contiene, al menos, los siguientes campos:

- [x] El **tipo** del dato almacenado.
- [x] Un **identificador** único (para distinguirlo de otros objetos).
- [x] El **valor** del objeto (consistente con su tipo).

![PyObject](images/data/pyobject.svg)

## Tipos de datos { #datatypes }

A continuación se muestran los distintos [tipos de datos](https://docs.python.org/es/3/library/stdtypes.html) que podemos encontrar en Python, sin incluir aquellos que proveen paquetes externos:

| Nombre 🇪🇸 | Tipo :material-language-python: | Ejemplos :material-cube-unfolded: |
| --- | --- | --- |
| Booleano | [`bool`](numbers.md#booleans) | `#!python True`, `#!python False`
| Entero | [`int`](numbers.md#integers) | `#!python 21`, `#!python 34500`, `#!python 34_500`
| Flotante | [`float`](numbers.md#floats) | `#!python 3.14`, `#!python 0.00314e3`
| Complejo | `#!python complex` | `#!python 2j`, `#!python 3+5j`
| Cadena | [`str`](strings.md) | `#!python 'tfn'`, `#!python "tenerife"`
| Tupla | [`tuple`](../datastructures/tuples.md) | `#!python (1, 3, 5)`
| Lista | [`list`](../datastructures/lists.md) | `#!python ['Chrome', 'Firefox', 'Safari']`
| Conjunto | [`set`](../datastructures/sets.md) | `#!python {2, 4, 6}`
| Diccionario | [`dict`](../datastructures/dicts.md) | `#!python {'Spiderman': 'Marvel', 'Superman': 'DC'}`

## Variables { #variables }

Las variables son fundamentales ya que permiten definir **nombres** para los **valores** que tenemos en memoria y que vamos a usar en nuestro programa.

### Reglas para nombrar variables { #naming-rules }

En Python existen una serie de reglas para los nombres de variables:

<div class="annotate" markdown>
1. Sólo pueden contener los siguientes caracteres[^3]:
    - Letras mayúsculas.
    - Letras minúsculas.
    - Dígitos.
    - Guiones bajos `_`
2. Deben empezar con una letra o un guión bajo, nunca con un dígito.
3. No pueden ser una palabra reservada del lenguaje («keywords»)(1)
</div>

1. Podemos obtener un listado de las palabras reservadas del lenguaje de la siguiente forma:
```pycon
>>> help('keywords')

Here is a list of the Python keywords.  Enter any keyword to get more help.

False               class               from                or
None                continue            global              pass
True                def                 if                  raise
and                 del                 import              return
as                  elif                in                  try
assert              else                is                  while
async               except              lambda              with
await               finally             nonlocal            yield
break               for                 not
```

!!! info "In english, please! 🇬🇧"

    Por lo general se prefiere 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-– modificarlo. Es sólo una recomendación, nada impide que se haga en castellano.

### Convenciones para nombres { #naming-standards }

Mientras se sigan las [reglas](#naming-rules) que hemos visto para nombrar variables no hay problema en la forma en la que se escriban, pero sí existe una convención para la **nomenclatura de las variables**. Se utiliza el llamado `snake_case`(1) en el que utilizamos **caracteres en minúsculas** (incluyendo dígitos si procede) junto con **guiones bajos** (cuando sean necesarios para su legibilidad).
{ .annotate }

1. Nomenclaturas «case»:

    - [ ] `kebab-case`
    - [ ] `camelCase`
    - [ ] `PascalCase`
    - [x] `snake_case`

Por <span class="example">ejemplo:material-flash:</span> 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 definidas en un documento denominado [PEP 8](https://peps.python.org/pep-0008/#naming-conventions). Se trata de una **guía de estilo** para escribir código Python. Los PEP son las propuestas que se hacen para la mejora del lenguaje.

#### Constantes { #constants }

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~~ debería cambiar 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`.

#### Elegir buenos nombres { #good-names }

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 fundamental 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**. Se nos ocurren cuatro posibilidades:

- [ ] `n`
- [x] `num_items`
- [ ] `number_of_items`
- [ ] `number_of_items_to_be_handled`

No existe una regla mágica que nos diga cuál es el nombre perfecto, 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 fijamos bien, casi no hay mucha informació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 esta decisión dependerá siempre del contexto del problema que estemos tratando y de los acuerdos a los que hayamos llegado con nuestro equipo de trabajo.

Como regla general:

- Usar **sustantivos** para los nombres de [variables](#variables) :material-arrow-right: `article`
- Usar **verbos** para los nombres de [funciones](../modularity/functions.md) :material-arrow-right: `get_article()`
- Usar **adjetivos** para los nombres de [booleanos](numbers.md#booleans) :material-arrow-right: `available`

## Asignación { #assignment }

En Python se utiliza el símbolo `=` para **asignar** un valor a una variable:

``` mermaid
flowchart LR
    name(nombre) ~~~ equals{=} ~~~ value(valor)
    equ
Download .txt
gitextract_sauf215x/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .prettierignore
├── CHANGELOG.md
├── Dockerfile
├── README.md
├── docs/
│   ├── assets/
│   │   ├── css/
│   │   │   └── custom.css
│   │   └── js/
│   │       ├── clipboard.js
│   │       └── mathjax.js
│   ├── core/
│   │   ├── controlflow/
│   │   │   ├── conditionals.md
│   │   │   ├── index.md
│   │   │   └── loops.md
│   │   ├── datastructures/
│   │   │   ├── dicts.md
│   │   │   ├── files.md
│   │   │   ├── index.md
│   │   │   ├── lists.md
│   │   │   ├── sets.md
│   │   │   └── tuples.md
│   │   ├── datatypes/
│   │   │   ├── data.md
│   │   │   ├── index.md
│   │   │   ├── numbers.md
│   │   │   └── strings.md
│   │   ├── devenv/
│   │   │   ├── index.md
│   │   │   ├── real-context.md
│   │   │   ├── thonny.md
│   │   │   └── vscode.md
│   │   ├── index.md
│   │   ├── introduction/
│   │   │   ├── history.md
│   │   │   ├── index.md
│   │   │   ├── machine.md
│   │   │   └── python.md
│   │   └── modularity/
│   │       ├── exceptions.md
│   │       ├── functions.md
│   │       ├── index.md
│   │       ├── modules.md
│   │       └── oop.md
│   ├── index.md
│   ├── stdlib/
│   │   ├── data-access/
│   │   │   ├── index.md
│   │   │   └── sqlite.md
│   │   ├── index.md
│   │   └── text-processing/
│   │       ├── index.md
│   │       ├── re.md
│   │       └── string.md
│   └── third-party/
│       ├── config/
│       │   ├── index.md
│       │   └── prettyconf.md
│       ├── data-science/
│       │   ├── files/
│       │   │   ├── jupyter/
│       │   │   │   ├── equations.tex
│       │   │   │   └── timeit.py
│       │   │   ├── matplotlib/
│       │   │   │   ├── avengers.csv
│       │   │   │   ├── bmw-clean.csv
│       │   │   │   ├── bmw_plot.py
│       │   │   │   ├── eth-usd.csv
│       │   │   │   ├── euro-dollar-clean.csv
│       │   │   │   ├── euro_dollar.py
│       │   │   │   ├── global-temperatures.csv
│       │   │   │   ├── imdb-top-1000.csv
│       │   │   │   ├── medals.xlsx
│       │   │   │   ├── mwh-spain-2021-clean.csv
│       │   │   │   ├── mwh_spain.py
│       │   │   │   ├── nba-data.csv
│       │   │   │   ├── pokemon.csv
│       │   │   │   ├── pokemon_speed.py
│       │   │   │   ├── soften_wave.py
│       │   │   │   ├── tiobe-2020-clean.csv
│       │   │   │   └── tiobe_2020.py
│       │   │   ├── numpy/
│       │   │   │   ├── diag.py
│       │   │   │   ├── diag_transform.py
│       │   │   │   ├── euler_product.py
│       │   │   │   ├── flip_powers.py
│       │   │   │   ├── identity_equation.py
│       │   │   │   ├── lineq.py
│       │   │   │   ├── np_matrix.py
│       │   │   │   ├── np_odds.py
│       │   │   │   ├── np_random.py
│       │   │   │   ├── np_transform.py
│       │   │   │   ├── transpose.py
│       │   │   │   └── vectorize.py
│       │   │   └── pandas/
│       │   │       ├── above_mean.py
│       │   │       ├── comunidades.py
│       │   │       ├── create_dataframe.py
│       │   │       ├── create_series.py
│       │   │       ├── democan.csv
│       │   │       ├── df_access.py
│       │   │       ├── df_oasis.py
│       │   │       ├── grants.py
│       │   │       ├── index_dataframe.py
│       │   │       ├── load_dataframe.py
│       │   │       ├── oasis.csv
│       │   │       ├── pop_density.py
│       │   │       ├── pop_percentage.py
│       │   │       ├── recoding.py
│       │   │       ├── smallest_density.py
│       │   │       └── tech.csv
│       │   ├── index.md
│       │   ├── jupyter.md
│       │   ├── matplotlib.md
│       │   ├── numpy.md
│       │   └── pandas/
│       │       ├── dataframes.md
│       │       ├── index.md
│       │       └── series.md
│       ├── index.md
│       ├── learning/
│       │   ├── index.md
│       │   └── pypas.md
│       ├── networking/
│       │   ├── files/
│       │   │   └── requests/
│       │   │       └── req.py
│       │   ├── index.md
│       │   └── requests.md
│       ├── pdf/
│       │   ├── index.md
│       │   └── weasyprint.md
│       ├── scraping/
│       │   ├── beautifulsoup.md
│       │   ├── files/
│       │   │   ├── beautifulsoup/
│       │   │   │   └── pypi-trend.py
│       │   │   └── selenium/
│       │   │       ├── mercadona.py
│       │   │       ├── wordle_play.py
│       │   │       └── wordle_try.py
│       │   ├── index.md
│       │   └── selenium.md
│       └── webdev/
│           ├── django/
│           │   ├── admin.md
│           │   ├── api.md
│           │   ├── apps.md
│           │   ├── auth.md
│           │   ├── extras.md
│           │   ├── files/
│           │   │   └── api/
│           │   │       ├── categories.json
│           │   │       ├── posts.json
│           │   │       └── users.json
│           │   ├── forms.md
│           │   ├── i18n.md
│           │   ├── index.md
│           │   ├── justfile.md
│           │   ├── middleware.md
│           │   ├── models.md
│           │   ├── production.md
│           │   ├── setup.md
│           │   ├── static.md
│           │   ├── templates.md
│           │   ├── urls.md
│           │   ├── views.md
│           │   └── webdev.md
│           └── index.md
├── includes/
│   └── abbreviations.md
├── justfile
├── pyproject.toml
└── zensical.toml
Download .txt
SYMBOL INDEX (4 symbols across 2 files)

FILE: docs/third-party/data-science/files/pandas/grants.py
  function handle_grants (line 6) | def handle_grants(row):

FILE: docs/third-party/scraping/files/selenium/wordle_try.py
  function init_webdriver (line 9) | def init_webdriver():
  function play (line 13) | def play(driver):
  function try_word (line 19) | def try_word(driver, word: str):
Condensed preview — 140 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,121K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 765,
    "preview": "name: CI\non:\n  push:\n    branches:\n      - main\n  workflow_dispatch:\njobs:\n  deploy-documentation:\n    name: Deploy docu"
  },
  {
    "path": ".gitignore",
    "chars": 103,
    "preview": ".venv\n.artwork\n*.pyc\nTODO\nsite\n.DS_Store\n__pycache__\n.mypy_cache\n.pytest_cache\n*.egg-info\n*.egg\n.cache\n"
  },
  {
    "path": ".prettierignore",
    "chars": 20,
    "preview": "*.md\npyproject.toml\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 11967,
    "preview": "# Changelog\n\nLas versiones siguen [versionado semántico](https://semver.org/) (`<major>.<minor>.<patch>`).\n\n## Version X"
  },
  {
    "path": "Dockerfile",
    "chars": 247,
    "preview": "FROM debian\n\nRUN apt-get update && \\\n    apt-get install -y curl && \\\n    curl -LsSf https://astral.sh/uv/install.sh | s"
  },
  {
    "path": "README.md",
    "chars": 516,
    "preview": "# Aprende Python\n\nAprende el lenguaje de programación Python con este manual que cubre los fundamentos del lenguaje, mód"
  },
  {
    "path": "docs/assets/css/custom.css",
    "chars": 6514,
    "preview": "/* \n  INSPIRATION: https://github.com/astral-sh/uv/blob/main/mkdocs.template.yml\n  MATERIAL COLORS: https://github.com/s"
  },
  {
    "path": "docs/assets/js/clipboard.js",
    "chars": 2098,
    "preview": "document.addEventListener(\"DOMContentLoaded\", function () {\n  document.body.addEventListener(\"click\", function (event) {"
  },
  {
    "path": "docs/assets/js/mathjax.js",
    "chars": 511,
    "preview": "window.MathJax = {\n  tex: {\n    inlineMath: [[\"\\\\(\", \"\\\\)\"]],\n    displayMath: [[\"\\\\[\", \"\\\\]\"]],\n    processEscapes: tru"
  },
  {
    "path": "docs/core/controlflow/conditionals.md",
    "chars": 30152,
    "preview": "---\nicon: material/call-split\ntags:\n  - Fundamentos del lenguaje\n  - Control de flujo\n  - Condicionales\n---\n\n# Condicion"
  },
  {
    "path": "docs/core/controlflow/index.md",
    "chars": 707,
    "preview": "# Control de flujo\n\nEl control de flujo en programación se refiere a la manera en que se determina el orden en que se ej"
  },
  {
    "path": "docs/core/controlflow/loops.md",
    "chars": 18919,
    "preview": "---\nicon: material/dots-circle\ntags:\n  - Fundamentos del lenguaje\n  - Control de flujo\n  - Bucles\n---\n\n# Bucles { #loops"
  },
  {
    "path": "docs/core/datastructures/dicts.md",
    "chars": 25040,
    "preview": "---\nicon: material/code-braces\ntags:\n  - Fundamentos del lenguaje\n  - Estructuras de datos\n  - Diccionarios\n---\n\n# Dicci"
  },
  {
    "path": "docs/core/datastructures/files.md",
    "chars": 17144,
    "preview": "---\nicon: material/file-cabinet\ntags:\n  - Fundamentos del lenguaje\n  - Estructuras de datos\n  - Ficheros\n---\n\n# Ficheros"
  },
  {
    "path": "docs/core/datastructures/index.md",
    "chars": 676,
    "preview": "# Estructuras de datos\n\nLas estructuras de datos son formas organizadas de almacenar y gestionar conjuntos de informació"
  },
  {
    "path": "docs/core/datastructures/lists.md",
    "chars": 48681,
    "preview": "---\nicon: material/list-box\ntags:\n  - Fundamentos del lenguaje\n  - Estructuras de datos\n  - Listas\n---\n\n# Listas { #list"
  },
  {
    "path": "docs/core/datastructures/sets.md",
    "chars": 15281,
    "preview": "---\nicon: material/set-center\ntags:\n  - Fundamentos del lenguaje\n  - Estructuras de datos\n  - Conjuntos\n---\n\n# Conjuntos"
  },
  {
    "path": "docs/core/datastructures/tuples.md",
    "chars": 10816,
    "preview": "---\nicon: octicons/link-16\ntags:\n  - Fundamentos del lenguaje\n  - Estructuras de datos\n  - Tuplas\n---\n\n# Tuplas { #tuple"
  },
  {
    "path": "docs/core/datatypes/data.md",
    "chars": 19630,
    "preview": "---\nicon: fontawesome/solid/memory\ntags:\n  - Fundamentos del lenguaje\n  - Tipos de datos\n  - Datos\n---\n\n# Datos { #data "
  },
  {
    "path": "docs/core/datatypes/index.md",
    "chars": 660,
    "preview": "# Tipos de datos\n\nEn programación, los tipos de datos son fundamentales porque determinan la naturaleza de los valores q"
  },
  {
    "path": "docs/core/datatypes/numbers.md",
    "chars": 22540,
    "preview": "---\nicon: material/calculator\ntags:\n  - Fundamentos del lenguaje\n  - Tipos de datos\n  - Números\n---\n\n# Números { #number"
  },
  {
    "path": "docs/core/datatypes/strings.md",
    "chars": 35791,
    "preview": "---\nicon: material/text-box\ntags:\n  - Fundamentos del lenguaje\n  - Tipos de datos\n  - Cadenas de texto\n---\n\n# Cadenas de"
  },
  {
    "path": "docs/core/devenv/index.md",
    "chars": 793,
    "preview": "# Entornos de desarrollo\n\nUn entorno de desarrollo es un conjunto de herramientas y recursos que facilitan la escritura,"
  },
  {
    "path": "docs/core/devenv/real-context.md",
    "chars": 15328,
    "preview": "---\nicon: octicons/rocket-16\ntags:\n  - Fundamentos del lenguaje\n  - Entornos de desarrollo\n  - Contexto real\n---\n\n# Cont"
  },
  {
    "path": "docs/core/devenv/thonny.md",
    "chars": 7345,
    "preview": "---\nicon: material/baby-carriage\ntags:\n  - Fundamentos del lenguaje\n  - Entornos de desarrollo\n  - Thonny\n---\n\n# Thonny "
  },
  {
    "path": "docs/core/devenv/vscode.md",
    "chars": 11681,
    "preview": "---\nicon: material/microsoft-visual-studio-code\ntags:\n  - Fundamentos del lenguaje\n  - Entornos de desarrollo\n  - Visual"
  },
  {
    "path": "docs/core/index.md",
    "chars": 736,
    "preview": "---\nicon: material/cog\n---\n\n# Fundamentos del lenguaje\n\nLos fundamentos del lenguaje constituyen la base sobre la cual s"
  },
  {
    "path": "docs/core/introduction/history.md",
    "chars": 21688,
    "preview": "---\nicon: octicons/git-commit-16\ntags:\n  - Fundamentos del lenguaje\n  - Introducción\n  - Algo de historia\n---\n\n# Algo de"
  },
  {
    "path": "docs/core/introduction/index.md",
    "chars": 636,
    "preview": "# Introducción\n\nLa programación es el proceso mediante el cual se le da instrucciones a una computadora para que realice"
  },
  {
    "path": "docs/core/introduction/machine.md",
    "chars": 7362,
    "preview": "---\nicon: octicons/cpu-24\ntags:\n  - Fundamentos del lenguaje\n  - Introducción\n  - Hablando con la máquina\n---\n\n# Habland"
  },
  {
    "path": "docs/core/introduction/python.md",
    "chars": 19730,
    "preview": "---\nicon: fontawesome/brands/python\ntags:\n  - Fundamentos del lenguaje\n  - Introducción\n  - Python\n---\n\n# Python { #pyth"
  },
  {
    "path": "docs/core/modularity/exceptions.md",
    "chars": 17096,
    "preview": "---\nicon: octicons/bug-24\ntags:\n  - Fundamentos del lenguaje\n  - Modularidad\n  - Excepciones\n---\n\n# Excepciones { #excep"
  },
  {
    "path": "docs/core/modularity/functions.md",
    "chars": 74924,
    "preview": "---\nicon: material/function\ntags:\n  - Fundamentos del lenguaje\n  - Modularidad\n  - Funciones\n---\n\n# Funciones { #functio"
  },
  {
    "path": "docs/core/modularity/index.md",
    "chars": 762,
    "preview": "# Modularidad\n\nLa modularidad en programación es un principio clave que permite dividir un programa en partes más pequeñ"
  },
  {
    "path": "docs/core/modularity/modules.md",
    "chars": 12700,
    "preview": "---\nicon: material/view-module\ntags:\n  - Fundamentos del lenguaje\n  - Modularidad\n  - Módulos\n---\n\n# Módulos { #modules "
  },
  {
    "path": "docs/core/modularity/oop.md",
    "chars": 72800,
    "preview": "---\nicon: material/temple-buddhist-outline\ntags:\n  - Fundamentos del lenguaje\n  - Modularidad\n  - Programación orientada"
  },
  {
    "path": "docs/index.md",
    "chars": 1167,
    "preview": "# Aprende Python\n\nAprende el lenguaje de programación ^^Python^^ :material-snake:{ .slide .green } y descubre un **mundo"
  },
  {
    "path": "docs/stdlib/data-access/index.md",
    "chars": 738,
    "preview": "# Acceso a datos\n\nEl acceso a datos es un componente esencial en el desarrollo de aplicaciones, ya que la mayoría de los"
  },
  {
    "path": "docs/stdlib/data-access/sqlite.md",
    "chars": 36367,
    "preview": "---\nicon: simple/sqlite\ntags:\n  - Librería estándar\n  - Acceso a datos\n  - sqlite\n---\n\n# sqlite { #sqlite }\n\n![Banner](i"
  },
  {
    "path": "docs/stdlib/index.md",
    "chars": 762,
    "preview": "---\nicon: material/book-open-page-variant-outline\n---\n\n# Librería estándar\n\nLa librería estándar de Python es un conjunt"
  },
  {
    "path": "docs/stdlib/text-processing/index.md",
    "chars": 743,
    "preview": "# Procesamiento de texto\n\nEl procesamiento de texto es una de las aplicaciones más comunes y poderosas de la programació"
  },
  {
    "path": "docs/stdlib/text-processing/re.md",
    "chars": 22222,
    "preview": "---\nicon: material/regex\ntags:\n  - Librería estándar\n  - Procesamiento de texto\n  - re\n---\n\n# re { #re }\n\n![Banner](imag"
  },
  {
    "path": "docs/stdlib/text-processing/string.md",
    "chars": 5155,
    "preview": "---\nicon: material/code-string\ntags:\n  - Librería estándar\n  - Procesamiento de texto\n  - string\n---\n\n# string { #string"
  },
  {
    "path": "docs/third-party/config/index.md",
    "chars": 815,
    "preview": "# Configuraciones\n\nLas configuraciones en programación se refieren a la personalización y ajustes de los parámetros que "
  },
  {
    "path": "docs/third-party/config/prettyconf.md",
    "chars": 5483,
    "preview": "---\nicon: material/image-filter-center-focus-weak\ntags:\n  - Paquetes de terceros\n  - Configuraciones\n  - Pretty Conf\n---"
  },
  {
    "path": "docs/third-party/data-science/files/jupyter/equations.tex",
    "chars": 498,
    "preview": "% Ecuación 1\n$$\n\\int_a^b f'(x)dx = f(b) - f(a)\n$$\n\n% Ecuación 2\n$$\nt' = t \\frac{1}{\\sqrt{1 - \\frac{v^2}{c^2}}}\n$$\n\n% Ecu"
  },
  {
    "path": "docs/third-party/data-science/files/jupyter/timeit.py",
    "chars": 435,
    "preview": "print('Poisson')\n%timeit numpy.random.poisson(size=100)\n%timeit numpy.random.poisson(size=10_000)\n%timeit numpy.random.p"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/avengers.csv",
    "chars": 27639,
    "preview": "URL,Name/Alias,Appearances,Current?,Gender,Probationary Introl,Full/Reserve Avengers Intro,Year,Years since joining,Hono"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/bmw-clean.csv",
    "chars": 632228,
    "preview": ",model,year,price,transmission,mileage,fuelType,tax,mpg,engineSize\n0, 5 Series,2014,11200,Automatic,67068,Diesel,125,57."
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/bmw_plot.py",
    "chars": 641,
    "preview": "import matplotlib.pyplot as plt\nimport pandas as pd\n\ndf = pd.read_csv('pypi/datascience/files/bmw-clean.csv')\n\nfig, ax ="
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/eth-usd.csv",
    "chars": 165402,
    "preview": "Date,Open,High,Low,Close,Adj Close,Volume\n2015-08-07,2.831620,3.536610,2.521120,2.772120,2.772120,164329\n2015-08-08,2.79"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/euro-dollar-clean.csv",
    "chars": 50617,
    "preview": "year,month,day,dollar\n2021,9,20,1.1711\n2021,9,17,1.178\n2021,9,16,1.1763\n2021,9,15,1.1824\n2021,9,14,1.1814\n2021,9,13,1.17"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/euro_dollar.py",
    "chars": 1080,
    "preview": "import matplotlib.pyplot as plt\nimport pandas as pd\n\ndf = pd.read_csv('pypi/datascience/files/euro-dollar-clean.csv')\neu"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/global-temperatures.csv",
    "chars": 205875,
    "preview": "dt,LandAverageTemperature,LandAverageTemperatureUncertainty,LandMaxTemperature,LandMaxTemperatureUncertainty,LandMinTemp"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/imdb-top-1000.csv",
    "chars": 437727,
    "preview": "Poster_Link,Series_Title,Released_Year,Certificate,Runtime,Genre,IMDB_Rating,Overview,Meta_score,Director,Star1,Star2,St"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/mwh-spain-2021-clean.csv",
    "chars": 4528,
    "preview": "Fecha,MWh\n2021-01-02,48.72\n2021-01-03,46.93\n2021-01-04,59.85\n2021-01-05,67.55\n2021-01-06,70.6\n2021-01-07,88.93\n2021-01-0"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/mwh_spain.py",
    "chars": 1082,
    "preview": "import matplotlib.pyplot as plt\nimport pandas as pd\nfrom matplotlib.dates import DateFormatter, DayLocator\n\ndf = pd.read"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/nba-data.csv",
    "chars": 1747328,
    "preview": ",player_name,team_abbreviation,age,player_height,player_weight,college,country,draft_year,draft_round,draft_number,gp,pt"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/pokemon.csv",
    "chars": 44022,
    "preview": "#,Name,Type 1,Type 2,Total,HP,Attack,Defense,Sp. Atk,Sp. Def,Speed,Generation,Legendary\n1,Bulbasaur,Grass,Poison,318,45,"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/pokemon_speed.py",
    "chars": 285,
    "preview": "import matplotlib.pyplot as plt\nimport pandas as pd\n\ndf = pd.read_csv('pypi/datascience/files/pokemon.csv')\n\nfig, ax = p"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/soften_wave.py",
    "chars": 296,
    "preview": "import matplotlib.pyplot as plt\nimport numpy as np\n\nx = np.linspace(0, 2 * np.pi, 1000)\nalpha = 0.7\nbeta = 10\ny = np.e *"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/tiobe-2020-clean.csv",
    "chars": 276,
    "preview": "Language,Ratings\nC,17.38\nJava,11.96\nPython,11.72\nC++,7.56\nC#,3.95\nVisual Basic,3.84\nJavaScript,2.2\nPHP,1.99\nR,1.9\nGroovy"
  },
  {
    "path": "docs/third-party/data-science/files/matplotlib/tiobe_2020.py",
    "chars": 553,
    "preview": "import matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\n\ndf = pd.read_csv('pypi/datascience/files/tiobe-2"
  },
  {
    "path": "docs/third-party/data-science/files/numpy/diag.py",
    "chars": 39,
    "preview": "import numpy as np\n\nnp.diag(range(50))\n"
  },
  {
    "path": "docs/third-party/data-science/files/numpy/diag_transform.py",
    "chars": 182,
    "preview": "import numpy as np\n\nmatrix = np.random.randint(0, 100, size=(10, 10))\n\nmatrix[np.diag_indices(matrix.shape[0])] = 50\nmat"
  },
  {
    "path": "docs/third-party/data-science/files/numpy/euler_product.py",
    "chars": 220,
    "preview": "import math\n\nimport numpy as np\n\ntheta = 2 * math.pi\nk = 20\n\nvalues = np.arange(1, k, dtype='float64')\nz = np.cos(theta "
  },
  {
    "path": "docs/third-party/data-science/files/numpy/flip_powers.py",
    "chars": 146,
    "preview": "import numpy as np\n\nA = np.array([[4, 5, -1], [-3, -4, 1], [-3, -4, 0]])\n\nfor power in range(2, 129):\n    print(np.linal"
  },
  {
    "path": "docs/third-party/data-science/files/numpy/identity_equation.py",
    "chars": 206,
    "preview": "import numpy as np\n\nmatrix = np.array([[1, 2], [3, 5]])\nidentity = np.identity(2)\nzeros = np.zeros((2, 2))\n\nresult = np."
  },
  {
    "path": "docs/third-party/data-science/files/numpy/lineq.py",
    "chars": 142,
    "preview": "import numpy as np\n\n# AX = B\nA = np.array([[1, 4, -1], [5, -2, 1], [2, -2, 1]])\nB = np.array([8, 4, 1]).reshape(3, -1)\n\n"
  },
  {
    "path": "docs/third-party/data-science/files/numpy/np_matrix.py",
    "chars": 157,
    "preview": "import numpy as np\n\narray1 = np.array([88, 23, 39, 41])\narray2 = np.array([[76.4, 21.7, 38.4], [41.2, 52.8, 68.9]])\narra"
  },
  {
    "path": "docs/third-party/data-science/files/numpy/np_odds.py",
    "chars": 92,
    "preview": "import numpy as np\n\nvalues = np.arange(10, 22).reshape(3, 4)\nprint(values[values % 2 != 0])\n"
  },
  {
    "path": "docs/third-party/data-science/files/numpy/np_random.py",
    "chars": 190,
    "preview": "import numpy as np\n\nfloat_values = np.linspace(1, 10, 100).reshape(20, 5)\nnormal_dist = np.random.normal(1, 2, size=128)"
  },
  {
    "path": "docs/third-party/data-science/files/numpy/np_transform.py",
    "chars": 457,
    "preview": "import numpy as np\n\nmatrix = np.array([[17, 12, 31], [49, 11, 51], [21, 31, 62], [63, 75, 22]])\n\nprint('Matrix:')\nprint("
  },
  {
    "path": "docs/third-party/data-science/files/numpy/transpose.py",
    "chars": 231,
    "preview": "import numpy as np\n\nA = np.array([[-1, 2, 1], [3, 0, 1]])\nB = np.array([[4, 0, -1], [-2, 1, 0]])\n\nprint('(A + B)^T = A^T"
  },
  {
    "path": "docs/third-party/data-science/files/numpy/vectorize.py",
    "chars": 208,
    "preview": "import numpy as np\n\nvect_avg = np.vectorize(lambda a, b: (a + b) / 2)\n\nA = np.random.uniform(0, 1000, size=(20, 20))\nB ="
  },
  {
    "path": "docs/third-party/data-science/files/pandas/above_mean.py",
    "chars": 213,
    "preview": "import pandas as pd\n\ndemocan = pd.read_csv('democan.csv', index_col='Island')\n\nmean_population = democan['Population'].m"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/comunidades.py",
    "chars": 695,
    "preview": "import pandas as pd\n\nURL = 'https://es.wikipedia.org/wiki/Comunidad_aut%C3%B3noma'\n\ntables = pd.read_html(URL)\n\ndf_area "
  },
  {
    "path": "docs/third-party/data-science/files/pandas/create_dataframe.py",
    "chars": 465,
    "preview": "import pandas as pd\n\nislands = [\n    'El Hierro',\n    'Fuerteventura',\n    'Gran Canaria',\n    'La Gomera',\n    'Lanzaro"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/create_series.py",
    "chars": 90,
    "preview": "import string\n\nimport pandas as pd\n\npd.Series(range(1, 27), list(string.ascii_uppercase))\n"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/democan.csv",
    "chars": 226,
    "preview": "Island,Population,Area,Province\nEl Hierro,11423,268.71,TF\nFuerteventura,120021,1665.74,LP\nGran Canaria,853262,1560.10,LP"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/df_access.py",
    "chars": 223,
    "preview": "import pandas as pd\n\ndemocan = pd.read_csv('democan.csv', index_col='Island')\n\nds1 = democan.loc[['El Hierro', 'La Gomer"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/df_oasis.py",
    "chars": 222,
    "preview": "import pandas as pd\n\ndf = pd.read_csv('oasis.csv')\ndf['album_release_date'] = pd.to_datetime(df['album_release_date'])\na"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/grants.py",
    "chars": 329,
    "preview": "import pandas as pd\n\ndemocan = pd.read_csv('democan.csv', index_col='Island')\n\n\ndef handle_grants(row):\n    area, popula"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/index_dataframe.py",
    "chars": 857,
    "preview": "import pandas as pd\n\n# ==============================================================================\n# Creación del Dat"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/load_dataframe.py",
    "chars": 71,
    "preview": "import pandas as pd\n\ndemocan = pd.read_csv('democan.csv', index_col=0)\n"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/oasis.csv",
    "chars": 177175,
    "preview": "album_release_date,album_release_year,danceability,energy,key,loudness,mode,speechiness,acousticness,instrumentalness,li"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/pop_density.py",
    "chars": 140,
    "preview": "import pandas as pd\n\ndemocan = pd.read_csv('democan.csv', index_col='Island')\n\ndemocan['Density'] = democan['Population'"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/pop_percentage.py",
    "chars": 247,
    "preview": "import pandas as pd\n\ndemocan = pd.read_csv('democan.csv', index_col='Island')\n\ntotal_population = democan['Population']."
  },
  {
    "path": "docs/third-party/data-science/files/pandas/recoding.py",
    "chars": 203,
    "preview": "import pandas as pd\n\ndemocan = pd.read_csv('democan.csv', index_col='Island')\n\nrecoding = {'LPGC': 'Las Palmas de Gran C"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/smallest_density.py",
    "chars": 172,
    "preview": "import pandas as pd\n\ndemocan = pd.read_csv('democan.csv', index_col='Island')\n\ndemocan['Density'] = democan['Population'"
  },
  {
    "path": "docs/third-party/data-science/files/pandas/tech.csv",
    "chars": 1076,
    "preview": "Company,Revenue,Employees,City,Country\nAmazon,574.8,1525000,Seattle,United States\nApple,394.33,164000,Cupertino,United S"
  },
  {
    "path": "docs/third-party/data-science/index.md",
    "chars": 847,
    "preview": "# Ciencia de datos\n\nLa ciencia de datos es un campo multidisciplinar que utiliza técnicas estadísticas, matemáticas y de"
  },
  {
    "path": "docs/third-party/data-science/jupyter.md",
    "chars": 24613,
    "preview": "---\nicon: simple/jupyter\ntags:\n  - Paquetes de terceros\n  - Ciencia de datos\n  - Jupyter\n---\n\n# Jupyter { #jupyter }\n\n!["
  },
  {
    "path": "docs/third-party/data-science/matplotlib.md",
    "chars": 40837,
    "preview": "---\nicon: material/chart-scatter-plot\ntags:\n  - Paquetes de terceros\n  - Ciencia de datos\n  - Matplotlib\n---\n\n# Matplotl"
  },
  {
    "path": "docs/third-party/data-science/numpy.md",
    "chars": 58082,
    "preview": "---\nicon: simple/numpy\ntags:\n  - Paquetes de terceros\n  - Ciencia de datos\n  - NumPy\n---\n\n# NumPy { #numpy }\n\n![Banner]("
  },
  {
    "path": "docs/third-party/data-science/pandas/dataframes.md",
    "chars": 93598,
    "preview": "---\nicon: octicons/table-24\ntags:\n  - Paquetes de terceros\n  - Ciencia de datos\n  - Pandas\n---\n\n# DataFrames { #datafram"
  },
  {
    "path": "docs/third-party/data-science/pandas/index.md",
    "chars": 903,
    "preview": "---\nicon: simple/pandas\ntags:\n  - Paquetes de terceros\n  - Ciencia de datos\n  - Pandas\n---\n\n# Pandas { #pandas }\n\n![Bann"
  },
  {
    "path": "docs/third-party/data-science/pandas/series.md",
    "chars": 12549,
    "preview": "---\nicon: octicons/pivot-column-24\ntags:\n  - Paquetes de terceros\n  - Ciencia de datos\n  - Pandas\n---\n\n# Series { #serie"
  },
  {
    "path": "docs/third-party/index.md",
    "chars": 778,
    "preview": "---\nicon: material/package-variant\n---\n\n# Paquetes de terceros\n\nLos paquetes de terceros son bibliotecas externas que am"
  },
  {
    "path": "docs/third-party/learning/index.md",
    "chars": 764,
    "preview": "# Aprendizaje\n\nLos paquetes de terceros de Python orientados al aprendizaje del propio lenguaje y a la práctica mediante"
  },
  {
    "path": "docs/third-party/learning/pypas.md",
    "chars": 12382,
    "preview": "---\nicon: material/seed\ntags:\n  - Paquetes de terceros\n  - Aprendizaje\n  - pypas\n---\n\n# pypas { #pypas }\n\n![Banner](imag"
  },
  {
    "path": "docs/third-party/networking/files/requests/req.py",
    "chars": 206,
    "preview": "import requests\n\nresponse = requests.get('https://twitter.com')\nprint(response.status_code)\nprint(len(response.text))\npr"
  },
  {
    "path": "docs/third-party/networking/index.md",
    "chars": 833,
    "preview": "# Redes\n\nEl desarrollo de redes es un área fundamental de la programación que permite la comunicación entre sistemas a t"
  },
  {
    "path": "docs/third-party/networking/requests.md",
    "chars": 13242,
    "preview": "---\nicon: material/download-network\ntags:\n  - Paquetes de terceros\n  - Redes\n  - Requests\n---\n\n# Requests { #requests }\n"
  },
  {
    "path": "docs/third-party/pdf/index.md",
    "chars": 839,
    "preview": "# PDF\n\nTrabajar con archivos PDF es una habilidad útil y necesaria en muchos proyectos de programación, especialmente cu"
  },
  {
    "path": "docs/third-party/pdf/weasyprint.md",
    "chars": 5754,
    "preview": "---\nicon: material/file-document-arrow-right-outline\ntags:\n  - Paquetes de terceros\n  - PDF\n  - WeasyPrint\n---\n\n# WeasyP"
  },
  {
    "path": "docs/third-party/scraping/beautifulsoup.md",
    "chars": 17761,
    "preview": "---\nicon: material/code-block-tags\ntags:\n  - Paquetes de terceros\n  - Scraping\n  - Beautiful Soup\n---\n\n# Beautiful Soup "
  },
  {
    "path": "docs/third-party/scraping/files/beautifulsoup/pypi-trend.py",
    "chars": 440,
    "preview": "import requests\nfrom bs4 import BeautifulSoup\n\nURL = 'https://pypi.org'\n\nresponse = requests.get(URL)\nsoup = BeautifulSo"
  },
  {
    "path": "docs/third-party/scraping/files/selenium/mercadona.py",
    "chars": 1162,
    "preview": "from selenium import webdriver\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.firefox.options impor"
  },
  {
    "path": "docs/third-party/scraping/files/selenium/wordle_play.py",
    "chars": 266,
    "preview": "from selenium import webdriver\n\nURL = 'https://wordle.danielfrg.com/'\n\ndriver = webdriver.Firefox()\n\ndriver.get(URL)\n\npl"
  },
  {
    "path": "docs/third-party/scraping/files/selenium/wordle_try.py",
    "chars": 641,
    "preview": "import time\n\nfrom selenium import webdriver\nfrom selenium.webdriver.common.keys import Keys\n\nURL = 'https://wordle.danie"
  },
  {
    "path": "docs/third-party/scraping/index.md",
    "chars": 883,
    "preview": "# Scraping\n\nEl «web scraping» es una técnica utilizada para extraer información de sitios web de forma automática, lo qu"
  },
  {
    "path": "docs/third-party/scraping/selenium.md",
    "chars": 19798,
    "preview": "---\nicon: simple/selenium\ntags:\n  - Paquetes de terceros\n  - Scraping\n  - Selenium\n---\n\n# Selenium { #selenium }\n\n![Bann"
  },
  {
    "path": "docs/third-party/webdev/django/admin.md",
    "chars": 19554,
    "preview": "---\nicon: fontawesome/solid/wand-magic-sparkles\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Inte"
  },
  {
    "path": "docs/third-party/webdev/django/api.md",
    "chars": 74478,
    "preview": "---\nicon: material/api\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# API { #api }\n\n<span class=\"dj"
  },
  {
    "path": "docs/third-party/webdev/django/apps.md",
    "chars": 6045,
    "preview": "---\nicon: material/application-outline\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Aplicaciones "
  },
  {
    "path": "docs/third-party/webdev/django/auth.md",
    "chars": 19387,
    "preview": "---\nicon: material/key-chain-variant\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Autenticación {"
  },
  {
    "path": "docs/third-party/webdev/django/extras.md",
    "chars": 40466,
    "preview": "---\nicon: material/battery-charging-30\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Extras { #ext"
  },
  {
    "path": "docs/third-party/webdev/django/files/api/categories.json",
    "chars": 354,
    "preview": "[\n{\n  \"model\": \"categories.category\",\n  \"pk\": 1,\n  \"fields\": {\n    \"name\": \"Design\",\n    \"slug\": \"design\"\n  }\n},\n{\n  \"mo"
  },
  {
    "path": "docs/third-party/webdev/django/files/api/posts.json",
    "chars": 992,
    "preview": "[\n{\n  \"model\": \"posts.post\",\n  \"pk\": 1,\n  \"fields\": {\n    \"title\": \"Small Changes\",\n    \"slug\": \"small-changes\",\n    \"co"
  },
  {
    "path": "docs/third-party/webdev/django/files/api/users.json",
    "chars": 680,
    "preview": "[\n  {\n    \"model\": \"auth.user\",\n    \"pk\": 2,\n    \"fields\": {\n      \"password\": \"pbkdf2_sha256$1200000$HOu6XaqEphwyfFLSfP"
  },
  {
    "path": "docs/third-party/webdev/django/forms.md",
    "chars": 59288,
    "preview": "---\nicon: material/form-select\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Formularios { #forms "
  },
  {
    "path": "docs/third-party/webdev/django/i18n.md",
    "chars": 14846,
    "preview": "---\nicon: material/translate\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Internacionalización { "
  },
  {
    "path": "docs/third-party/webdev/django/index.md",
    "chars": 4361,
    "preview": "---\nicon: simple/django\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Django\n\n![Banner](images/ban"
  },
  {
    "path": "docs/third-party/webdev/django/justfile.md",
    "chars": 8881,
    "preview": "---\nicon: material/list-box\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Justfile { #justfile }\n\n"
  },
  {
    "path": "docs/third-party/webdev/django/middleware.md",
    "chars": 11980,
    "preview": "---\nicon: material/middleware-outline\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Middleware { #"
  },
  {
    "path": "docs/third-party/webdev/django/models.md",
    "chars": 132256,
    "preview": "---\nicon: octicons/database-24\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Modelos { #models }\n\n"
  },
  {
    "path": "docs/third-party/webdev/django/production.md",
    "chars": 15571,
    "preview": "---\nicon: fontawesome/solid/industry\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Producción { #p"
  },
  {
    "path": "docs/third-party/webdev/django/setup.md",
    "chars": 17418,
    "preview": "---\nicon: material/engine-outline\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Puesta en marcha {"
  },
  {
    "path": "docs/third-party/webdev/django/static.md",
    "chars": 14046,
    "preview": "---\nicon: material/folder-outline\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Estáticos { #stati"
  },
  {
    "path": "docs/third-party/webdev/django/templates.md",
    "chars": 57651,
    "preview": "---\nicon: material/checkbox-multiple-blank-outline\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# P"
  },
  {
    "path": "docs/third-party/webdev/django/urls.md",
    "chars": 22907,
    "preview": "---\nicon: fontawesome/solid/arrows-to-dot\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# URLs { #ur"
  },
  {
    "path": "docs/third-party/webdev/django/views.md",
    "chars": 17524,
    "preview": "---\nicon: octicons/eye-16\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Vistas { #views }\n\n<span c"
  },
  {
    "path": "docs/third-party/webdev/django/webdev.md",
    "chars": 33337,
    "preview": "---\nicon: octicons/code-16\ntags:\n  - Paquetes de terceros\n  - Desarrollo web\n  - Django\n---\n\n# Desarrollo web { #webdev "
  },
  {
    "path": "docs/third-party/webdev/index.md",
    "chars": 828,
    "preview": "# Desarrollo web\n\nEl desarrollo web es el proceso de crear aplicaciones y sitios web dinámicos y funcionales que puedan "
  },
  {
    "path": "includes/abbreviations.md",
    "chars": 2186,
    "preview": "*[ABC]: Atanasoff Berry Computer\n*[API]: Application Programming Interface\n*[APL]: A Programming Language\n*[ASCII]: Amer"
  },
  {
    "path": "justfile",
    "chars": 418,
    "preview": "# Run development server\nserve port=\"8000\": clean\n    uv run zensical serve -a localhost:{{port}}\n\n# Build site native-w"
  },
  {
    "path": "pyproject.toml",
    "chars": 121,
    "preview": "[project]\nname = \"aprendepython\"\nversion = \"3.2.5\"\nrequires-python = \">=3.14\"\ndependencies = [\n    \"zensical>=0.0.32\",\n]"
  },
  {
    "path": "zensical.toml",
    "chars": 8266,
    "preview": "# ==============================================================================\n# PROJECT\n# ==========================="
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the sdelquin/aprendepython GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 140 files (4.8 MB), approximately 1.3M tokens, and a symbol index with 4 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!