Tu país

Las herramientas que lo admiten usan tu país para la moneda local, el formato de los números, las unidades y el tamaño del papel. Tu elección se guarda solo en este navegador.

Escribe un nombre o un código de dos letras. Usa las flechas arriba y abajo para recorrer los países, Intro para elegir uno y Escape para cerrar.

Python  Módulo 1 – Primeros pasos: instalar, ejecutar y leer Python

uv, pipx y metadatos en línea en scripts de Python

Crea un proyecto de Python con uv, ejecuta un script de un solo archivo que declara sus propias dependencias e instala herramientas aisladas con pipx o uv.

Lección gratis, respuestas Pro: 20 minutos
Gratis: la lección completa. Con un pase: las respuestas modelo de sus 2 preguntas de entrevista.

Qué vas a aprender

  • Crear un proyecto y añadir dependencias con uv init, uv add y uv run
  • Explicar qué anota uv.lock que pyproject.toml no anota
  • Escribir un bloque de metadatos en línea que puedan leer uv run y pipx run
  • Instalar herramientas de línea de órdenes aisladas con pipx o uv tool
  • Elegir entre venv y pip, uv y pipx para cada tarea

Antes de empezar

Qué vas a aprender · Antes de empezar

Qué vas a aprender

  • Crear un proyecto y añadir dependencias con uv init, uv add y uv run
  • Explicar qué anota uv.lock que pyproject.toml no anota
  • Escribir un bloque de metadatos en línea que puedan leer uv run y pipx run
  • Instalar herramientas de línea de órdenes aisladas con pipx o uv tool
  • Elegir entre venv y pip, uv y pipx para cada tarea

Antes de empezar

En esta página

Un proyecto con tres paquetes necesita cuatro pasos en cada computadora en la que se ejecuta: crear un entorno, instalar en él las versiones correctas, anotar esas versiones y repetirlo todo después de cada cambio. El venv y el pip de la lección anterior hacen cada paso, y los pasos se quedan anticuados el día en que alguien olvida uno. Dos herramientas hacen esta contabilidad por ti: uv gestiona un proyecto entero, y pipx instala aplicaciones de línea de órdenes, cada una en un entorno propio.

A continuación configuras un proyecto con uv y ves qué comprueba antes de cada ejecución, comparas un rango de versiones con un archivo de bloqueo, das a un solo script su propia lista de dependencias e instalas herramientas para que nunca choquen con tus proyectos. Ninguna de las dos herramientas forma parte de Python: las dos son programas de código abierto que instalas una vez.

uv mantiene el entorno de un proyecto al día con pyproject.toml

uv, creado por Astral y con licencia MIT o Apache-2.0, es un solo programa que hace el trabajo de varios: instala versiones de Python (uv python install 3.14), crea entornos virtuales, calcula qué versiones de tus dependencias encajan entre sí, las anota y ejecuta tu código. Instálalo con un gestor de paquetes (brew install uv en macOS, winget install --id=astral-sh.uv -e en Windows), con pipx install uv o con el script de instalación de la documentación de uv. Un script que descargas y ejecutas tiene el mismo acceso a tu computadora que tú, así que míralo antes; la documentación explica cómo.

Nota de versión

Esta lección sigue la documentación de uv 0.13 y pipx 1.18. Los dos cambian más deprisa que Python, así que si una orden se comporta de otra forma, compara uv --version o pipx --version con su documentación.

Tres órdenes ponen en marcha un proyecto:

  1. uv init weather-notes crea la carpeta weather-notes con un pyproject.toml, un archivo .python-version que nombra la versión de Python que hay que usar, un README.md, un .gitignore, un repositorio de Git y código inicial en src/weather_notes/__init__.py.
  2. uv add rich, en esa carpeta, escribe el requisito rich>=15.0.0, con la versión más nueva que encaja como la mínima permitida, en pyproject.toml. También crea .venv si no existe, instala rich en él y anota las versiones exactas en uv.lock.
  3. uv run weather-notes ejecuta la orden que configuró uv init, y uv run some_file.py ejecuta cualquier archivo, dentro del entorno del proyecto.

Lo que hace fiable a uv run es lo que comprueba antes de ejecutar nada. Recorre una ejecución:

Interactivo · etapa por etapa Lo que hace uv run antes de ejecutar tu orden Recorre un uv run en el proyecto weather-notes, de pyproject.toml a la orden.
Lo que hace uv run antes de ejecutar tu orden, paso 5 de 5. Se ve: uv run weather-notes, Leer pyproject.toml, ¿Está al día uv.lock? (se vuelve a bloquear si hace falta), ¿Está al día .venv? (al día con uv.lock), Ejecutar la orden. Resaltado: Ejecutar la orden.uv run weather-notesen la carpeta del proyectoLeer pyproject.tomlrich>=15.0.0: un rango¿Está al día uv.lock?se vuelve a bloquear si hace falta¿Está al día .venv?al día con uv.lockEjecutar la ordendentro de .venv

El dibujo muestra la escena completa; sus 5 pasos aparecen abajo.

Todos los pasos en texto
  1. Escribes uv run. En la carpeta del proyecto escribes uv run weather-notes. No se activa nada, y no tienes que acordarte de si alguien cambió las dependencias.
  2. Los rangos. uv lee pyproject.toml. Sus dependencias son rangos, como rich>=15.0.0, que dicen qué versiones serían aceptables, no cuál está instalada.
  3. El bloqueo. uv comprueba que uv.lock sigue coincidiendo con pyproject.toml. Si alguien cambió las dependencias desde el último bloqueo, primero vuelve a bloquear, así que el bloqueo siempre anota una versión exacta de cada paquete.
  4. El entorno. uv comprueba que .venv contiene lo que enumera uv.lock, e instala lo que falte o esté en otra versión. La primera ejecución también crea .venv.
  5. Se ejecuta la orden. Solo ahora uv ejecuta weather-notes, dentro de .venv. Cada ejecución pasa antes estas comprobaciones, así que nadie ejecuta el proyecto con un conjunto de paquetes anticuado.

Así que nunca activas nada y nunca ejecutas con un conjunto de paquetes anticuado. uv remove rich vuelve a quitar una dependencia, uv sync hace que .venv coincida exactamente con uv.lock (después de traer los cambios de un compañero, por ejemplo), y uv lock --upgrade-package rich pasa un paquete a su versión permitida más nueva.

pyproject.toml no es un invento de uv: es el archivo estándar en el que un proyecto de Python se describe a sí mismo, escrito en Tom's Obvious, Minimal Language, un formato de texto para configuraciones: líneas clave = valor agrupadas bajo encabezados entre corchetes., y cualquier herramienta puede leerlo. La biblioteca estándar lo lee con tomllib:

Leer lo que necesita un proyecto en pyproject.toml

project/pyproject.toml

[project]
name = "weather-notes"
version = "0.1.0"
description = "Short notes about the weather"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
    "rich>=15.0.0",
]

[project.scripts]
weather-notes = "weather_notes:main"

[build-system]
requires = ["uv_build>=0.13.1,<0.14"]
build-backend = "uv_build"

project/read_project.py

import tomllib

# tomllib, in the standard library since Python 3.11, reads TOML; it needs the file opened in binary mode.
with open("pyproject.toml", "rb") as file:
    pyproject = tomllib.load(file)

project = pyproject["project"]
print("Project:", project["name"], project["version"])
print("Needs Python", project["requires-python"])
for requirement in project["dependencies"]:
    print("Depends on", requirement)
for command, target in project["scripts"].items():
    print("Adds the command", command, "which runs", target)
print("Built by", pyproject["build-system"]["build-backend"])

Salida

Project: weather-notes 0.1.0
Needs Python >=3.14
Depends on rich>=15.0.0
Adds the command weather-notes which runs weather_notes:main
Built by uv_build

Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 read_project.py

La tabla [project] contiene el nombre, la versión, las versiones de Python que admite el proyecto y sus dependencias; [project.scripts] añade la orden weather-notes, y [build-system] nombra uv_build, el backend que convierte el proyecto en un paquete. Desde uv 0.12, uv init da por defecto un sistema de construcción a una aplicación.

Punto de control

Comprueba: un proyecto gestionado con uv

  1. Pregunta 1 de 2 En el proyecto weather-notes ejecutas uv add rich. ¿Qué cambia?

    Elige una respuesta.

    Ver la respuesta: pregunta 1 de 2

    Respuesta: pyproject.toml recibe el requisito rich>=15.0.0, uv.lock anota las versiones exactas y rich se instala en .venv

    uv add escribe el requisito en pyproject.toml (la versión más nueva que encaja como la mínima permitida), instala el paquete en .venv y anota las versiones exactas en uv.lock.

  2. Pregunta 2 de 2 ¿Qué comprueba uv run antes de ejecutar tu orden?

    Elige una respuesta.

    Ver la respuesta: pregunta 2 de 2

    Respuesta: Que uv.lock coincide con pyproject.toml, y que .venv contiene lo que enumera uv.lock

    Antes de cada ejecución, uv se asegura de que el bloqueo está al día con pyproject.toml y de que el entorno está al día con el bloqueo, y después ejecuta la orden dentro de .venv.

Un archivo de bloqueo convierte rangos en un único conjunto exacto de versiones

Un requisito como rich>=15.0.0 es un especificador de versión: La parte de un requisito que dice qué versiones son aceptables, como >=1.2,<2 o ~=1.2. Las versiones se comparan número a número, y pip instala la más nueva que cumple todas las condiciones. Más en “Buscar e instalar paquetes de terceros” (en inglés), un rango: vale cualquier versión a partir de la 15.0.0. Qué versión obtienes de verdad se decide al construir el entorno, y puede cambiar de una semana a la siguiente. Este programa sigue un paquete inventado, tablekit, a través de sus versiones en las semanas 1, 5 y 9, y compara una instalación nueva desde el rango con una instalación desde el bloqueo:

Un rango o un bloqueo, en las semanas 1, 5 y 9 Python · lock/range_or_lock.py
# tablekit is a made-up package: the versions its index offers in three weeks.
releases = {
    1: ["2.3.0"],
    5: ["2.3.0", "2.4.0"],
    9: ["2.3.0", "2.4.0", "3.0.0"],
}
locked = "2.3.0"  # what uv.lock recorded in week 1


def as_numbers(version):
    return tuple(int(part) for part in version.split("."))


def newest_allowed(available, minimum):
    allowed = [v for v in available if as_numbers(v) >= as_numbers(minimum)]
    return max(allowed, key=as_numbers)


print("Week  tablekit>=2.3.0 gives  uv.lock gives")
for week, available in releases.items():
    print(f"{week:>4}  {newest_allowed(available, '2.3.0'):<21}  {locked}")

Salida

Week  tablekit>=2.3.0 gives  uv.lock gives
   1  2.3.0                  2.3.0
   5  2.4.0                  2.3.0
   9  3.0.0                  2.3.0

Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 range_or_lock.py

In week 9 a fresh install from the range gets 3.0.0, a new major version that may rename what your code calls, while everyone who installs from the lock still gets 2.3.0. A lock file: A file that records every package an application installs, direct and indirect, with its exact version, where each file came from and the file's hash, so the next install gets the same files. pylock.toml is the standard format. records the exact version of every package that was installed, including the dependencies of your dependencies: rich 15.0.0 itself needs markdown-it-py and Pygments, so uv.lock lists them too, each with its exact version. Press Edit, add "3.1.0" to week 9’s list and run it again: the range moves on, the lock does not.

uv guarda su bloqueo en uv.lock, que va en el control de versiones y nunca se edita a mano (la carpeta .venv, de nuevo, no se sube). Un formato estándar para los archivos de bloqueo, pylock.toml, lo define la PEP 751: uv export --format pylock.toml escribe uno, y pip 26.2.1 tiene una orden experimental pip lock que también lo escribe. El requirements.txt que escribe pip freeze es la forma más antigua y sencilla de fijar versiones, y todavía la que más se admite.

Idea clave

pyproject.toml dice qué versiones serían aceptables; el archivo de bloqueo dice cuáles instala todo el mundo, hasta que alguien lo actualiza a propósito y prueba el resultado.

Punto de control

Comprueba: rangos y archivos de bloqueo

  1. Una pregunta Un proyecto requiere tablekit>=2.3.0, y el índice ofrece ahora la 2.3.0, la 2.4.0 y la 3.0.0. ¿Qué versión obtiene una instalación nueva sin archivo de bloqueo?

    Elige una respuesta.

    Ver la respuesta: una pregunta

    Respuesta: La 3.0.0, la versión más nueva que permite el rango

    Un rango dice qué versiones serían aceptables. Sin un bloqueo, cada instalación nueva elige la versión aceptable más nueva de ese día, y así es como la semana 9 de range_or_lock.py obtiene la 3.0.0 mientras el bloqueo mantiene la 2.3.0.

Un script puede llevar su propia lista de dependencias

Not everything is a project. A single file that you run now and then can say what it needs in a comment block at its top, called inline script metadata: A comment block in a Python script, opened by # /// script and closed by # ///, whose lines hold TOML such as requires-python and dependencies. Script runners read it; Python itself ignores it., a packaging standard since PEP 723:

Un script que nombra el paquete que necesita Python · inline/report.py
# /// script
# requires-python = ">=3.14"
# dependencies = [
#   "rich",
# ]
# ///
from rich import print

print("[bold green]Report ready[/bold green]")

Salida (código de salida 1)

Impreso como error (salida de errores estándar)

Traceback (most recent call last):
  File "report.py", line 7, in <module>
    from rich import print
ModuleNotFoundError: No module named 'rich'

Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 report.py

El propio Python ignora el bloque: para python3 solo son comentarios. Por eso la ejecución de arriba, con CPython 3.14.8 sin más, se detiene en la importación con ModuleNotFoundError. Una herramienta que lee el bloque lo hace mejor: uv run report.py y pipx run report.py lo leen, construyen un entorno que contiene rich y ejecutan el script en él. pipx conserva ese entorno y construye uno nuevo cuando cambia la lista de dependencias.

Las reglas son pocas. El bloque empieza con una línea que es exactamente # /// script y termina con una línea que es exactamente # ///. Todas las líneas entre ellas son comentarios: quita el # y el espacio que lo sigue, y lo que queda es TOML, con las claves requires-python (las versiones de Python en las que funciona el script) y dependencies (una lista de requisitos, como la de pyproject.toml). Un script solo puede tener un bloque script; las herramientas rechazan un archivo con dos. Recorre un programa que lee el bloque de report.py como lo hace una de esas herramientas:

Interactivo · línea a línea Leer el bloque de metadatos de report.py Recorre el bucle sobre las líneas de report.py. La marca es la línea que el bucle lee a continuación; fíjate en cómo inside se activa y se desactiva.

Programa · inline/read_metadata.py

import tomllib
from pathlib import Path

src = Path("report.py").read_text()
inside = False
toml = []
for line in src.splitlines():
    if line == "# /// script":
        inside = True
    elif line == "# ///":
        inside = False
    elif inside:
        toml.append(line[2:])

meta = tomllib.loads("\n".join(toml))
print("requires-python:", meta["requires-python"])
print("dependencies:", meta["dependencies"])

Variables

Después de la línea 17: Path vale Path; src vale '# /// script\n# requires-python = ">=3.14"\n# dependencies…; inside vale False; toml vale ['requires-python = ">=3.14"', 'dependencies = [', ' "rich",', ']']; line vale 'print("[bold green]Report ready[/bold green]")'; meta vale {'requires-python': '>=3.14', 'dependencies': ['rich']}.PathPathsrc'# /// script\n# requires-python = ">=3.14"\n# dependencies…insideFalsetoml'requires-python = ">=3.14"'0'dependencies = ['1' "rich",'2']'3line'print("[bold green]Report ready[/bold green]")'meta'requires-python':'>=3.14''dependencies':['rich']
  • recién tomado por el bucle
  • cambiado en este paso
  • ya recorrido por el bucle
  • se lee a continuación
  • donde se detuvo el bucle

Impreso hasta ahora

requires-python: >=3.14
dependencies: ['rich']

Cada paso se registró de una ejecución con Python 3.14.8 en macOS 26 arm64.

El dibujo muestra las variables cuando termina el programa, y los 49 pasos del programa aparecen abajo.

Línea ejecutada 17
Línea siguiente fin
Vuelta del bucle –
Todos los pasos en texto
  1. Inicio. Todavía no se ha ejecutado ninguna línea, así que no hay variables. Python empieza por la línea 1.
  2. Línea 1. import tomllib carga tomllib.
  3. Línea 2. from pathlib import Path carga Path.
  4. Línea 4. src = Path("report.py").read_text() asigna '# /// script\n# requires-python = ">=3.14"\n# dependencies… a src.
  5. Línea 5. inside = False asigna False a inside.
  6. Línea 6. toml = [] hace de toml una lista de 0 elementos.
  7. Línea 7. for line in src.splitlines(): toma el elemento de la posición 0 de src.splitlines(): line es '# /// script'.
  8. Línea 8. line == "# /// script" es verdadero (line es '# /// script'), así que se ejecuta el bloque de if line == "# /// script":.
  9. Línea 9. inside = True asigna True a inside (antes era False). La línea de apertura es exactamente '# /// script', así que las líneas que la siguen pertenecen al bloque.
  10. Línea 7. for line in src.splitlines(): toma el elemento de la posición 1 de src.splitlines(): line es '# requires-python = ">=3.14"'.
  11. Línea 8. line == "# /// script" es falso (line es '# requires-python = ">=3.14"'), así que Python prueba la siguiente condición.
  12. Línea 10. line == "# ///" es falso (line es '# requires-python = ">=3.14"'), así que Python prueba la siguiente condición.
  13. Línea 12. inside es verdadero (inside es True), así que se ejecuta el bloque de elif inside:.
  14. Línea 13. toml.append(line[2:]) añade 'requires-python = ">=3.14"' al final de toml. Una línea dentro del bloque pierde sus dos primeros caracteres, el # y el espacio. Lo que queda es una línea de TOML.
  15. Línea 7. for line in src.splitlines(): toma el elemento de la posición 2 de src.splitlines(): line es '# dependencies = ['.
  16. Línea 8. line == "# /// script" es falso (line es '# dependencies = ['), así que Python prueba la siguiente condición.
  17. Línea 10. line == "# ///" es falso (line es '# dependencies = ['), así que Python prueba la siguiente condición.
  18. Línea 12. inside es verdadero (inside es True), así que se ejecuta el bloque de elif inside:.
  19. Línea 13. toml.append(line[2:]) añade 'dependencies = [' al final de toml.
  20. Línea 7. for line in src.splitlines(): toma el elemento de la posición 3 de src.splitlines(): line es '# "rich",'.
  21. Línea 8. line == "# /// script" es falso (line es '# "rich",'), así que Python prueba la siguiente condición.
  22. Línea 10. line == "# ///" es falso (line es '# "rich",'), así que Python prueba la siguiente condición.
  23. Línea 12. inside es verdadero (inside es True), así que se ejecuta el bloque de elif inside:.
  24. Línea 13. toml.append(line[2:]) añade ' "rich",' al final de toml.
  25. Línea 7. for line in src.splitlines(): toma el elemento de la posición 4 de src.splitlines(): line es '# ]'.
  26. Línea 8. line == "# /// script" es falso (line es '# ]'), así que Python prueba la siguiente condición.
  27. Línea 10. line == "# ///" es falso (line es '# ]'), así que Python prueba la siguiente condición.
  28. Línea 12. inside es verdadero (inside es True), así que se ejecuta el bloque de elif inside:.
  29. Línea 13. toml.append(line[2:]) añade ']' al final de toml.
  30. Línea 7. for line in src.splitlines(): toma el elemento de la posición 5 de src.splitlines(): line es '# ///'.
  31. Línea 8. line == "# /// script" es falso (line es '# ///'), así que Python prueba la siguiente condición.
  32. Línea 10. line == "# ///" es verdadero (line es '# ///'), así que se ejecuta el bloque de elif line == "# ///":.
  33. Línea 11. inside = False asigna False a inside (antes era True). Lo clave '# ///' cierra el bloque. Las líneas que lo siguen son el propio código del programa, que no forma parte de los metadatos.
  34. Línea 7. for line in src.splitlines(): toma el elemento de la posición 6 de src.splitlines(): line es 'from rich import print'.
  35. Línea 8. line == "# /// script" es falso (line es 'from rich import print'), así que Python prueba la siguiente condición.
  36. Línea 10. line == "# ///" es falso (line es 'from rich import print'), así que Python prueba la siguiente condición.
  37. Línea 12. inside es falso (inside es False), así que Python se salta el bloque de elif inside:.
  38. Línea 7. for line in src.splitlines(): toma el elemento de la posición 7 de src.splitlines(): line es ''.
  39. Línea 8. line == "# /// script" es falso (line es ''), así que Python prueba la siguiente condición.
  40. Línea 10. line == "# ///" es falso (line es ''), así que Python prueba la siguiente condición.
  41. Línea 12. inside es falso (inside es False), así que Python se salta el bloque de elif inside:.
  42. Línea 7. for line in src.splitlines(): toma el elemento de la posición 8 de src.splitlines(): line es 'print("[bold green]Report ready[/bold green]")'.
  43. Línea 8. line == "# /// script" es falso (line es 'print("[bold green]Report ready[/bold green]")'), así que Python prueba la siguiente condición.
  44. Línea 10. line == "# ///" es falso (line es 'print("[bold green]Report ready[/bold green]")'), así que Python prueba la siguiente condición.
  45. Línea 12. inside es falso (inside es False), así que Python se salta el bloque de elif inside:.
  46. Línea 7. for line in src.splitlines(): no encuentra ningún elemento en la posición 9 de src.splitlines() (tiene 9 elementos), así que el bucle termina.
  47. Línea 15. meta = tomllib.loads("\n".join(toml)) asigna {'requires-python': '>=3.14', 'dependencies': ['rich']} a meta. tomllib lee las líneas reunidas como TOML, lo que da una cadena para requires-python y una lista para dependencies.
  48. Línea 16. print("requires-python:", meta["requires-python"]) imprime requires-python: >=3.14.
  49. Línea 17. print("dependencies:", meta["dependencies"]) imprime dependencies: ['rich']. Era la última línea, así que el programa termina.

El bucle guardó las cuatro líneas entre las dos marcas, cada una sin sus dos primeros caracteres, y tomllib las convirtió en la cadena >=3.14 y la lista ['rich'].

uv también puede escribir el bloque por ti: uv init --script report.py crea un script nuevo con uno, y uv add --script report.py rich le añade una dependencia. uv espera una clave dependencies incluso cuando la lista está vacía, y un script solo se bloquea cuando lo pides, con uv lock --script report.py, que escribe report.py.lock.

Punto de control

Comprueba: metadatos en línea de los scripts

  1. Una pregunta ¿Qué línea abre un bloque de metadatos en línea de un script?

    Elige una respuesta.

    Ver la respuesta: una pregunta

    Respuesta: # /// script

    El bloque se abre con una línea que es exactamente # /// script y se cierra con una línea que es exactamente # ///; las líneas de comentario entre ellas contienen TOML.

pipx y uv tool dan a cada aplicación su propio entorno

Some packages are applications that you run as commands, such as the linter Ruff, rather than libraries that your code imports. Install those with pipx: A tool that installs each Python application into a virtual environment of its own and links only its commands into a folder on PATH, ~/.local/bin by default. uv tool does the same job. or with uv tool: each application gets a virtual environment of its own, so its dependencies never clash with your projects or with each other, and only its commands are put on your PATH. Step through one install:

Interactivo · paso a paso Una aplicación, un entorno propio Recorre pipx install ruff y después una ejecución de ruff desde la carpeta de un proyecto.
Una aplicación, un entorno propio, paso 5 de 5. Se ve: pipx install ruff, el propio entorno de ruff, El .venv de tu proyecto, ~/.local/bin, ruff check.createsenlaza la ordenencontrada en PATHpipx install ruffse escribe una vezel propio entorno de ruffruff y lo que necesitaEl .venv de tu proyectotus paquetes~/.local/binuna carpeta de PATHruff checkse escribe en cualquier carpeta

El dibujo muestra la escena completa; sus 5 pasos aparecen abajo.

Todos los pasos en texto
  1. Un entorno solo para ruff. pipx crea un entorno virtual solo para ruff e instala en él ruff y todo lo que necesita. No va nada a tu proyecto ni al Python del sistema.
  2. Solo se enlaza la orden. pipx pone la orden de ruff en una carpeta de PATH, ~/.local/bin en Linux; pipx ensurepath añade esa carpeta a PATH si falta.
  3. Ejecútalo desde cualquier sitio. Escribir ruff check en cualquier carpeta encuentra la orden en PATH y ejecuta ruff desde su propio entorno, así que el mismo ruff revisa todos los proyectos.
  4. Tu proyecto se queda como estaba. El .venv de tu proyecto queda intacto. Ahí no se puede importar ruff, y nada de lo que necesita ruff puede chocar con las versiones que usa tu proyecto.
  5. Lo mismo con uv. uv tool install ruff hace el mismo trabajo con uv, y uvx ruff check ejecuta ruff una vez en un entorno temporal sin instalarlo.

Tarea Con pipx Con uv
Instalar una herramienta y poner sus órdenes en PATH pipx install ruff uv tool install ruff
Ejecutar una herramienta una vez, sin instalarla pipx run ruff check uvx ruff check
Actualizar una herramienta instalada pipx upgrade ruff uv tool upgrade ruff
Asegurarte de que la carpeta de las herramientas está en PATH pipx ensurepath uv tool update-shell

pipx viene de un gestor de paquetes (brew install pipx en macOS, sudo apt install pipx en Ubuntu, scoop install pipx en Windows) o, en Windows, de py -m pip install --user pipx. uvx es la forma corta de uv tool run. Una herramienta instalada así no la puede importar tu propio código: vive en su propio entorno, que es justo la idea. Una herramienta que tiene que importar tu proyecto, como pytest al ejecutar tus pruebas, va en el propio proyecto, como dependencia de desarrollo (uv add --dev pytest), y se ejecuta con uv run pytest.

Punto de control

Comprueba: herramientas en entornos propios

  1. Una pregunta Instalaste Ruff con pipx install ruff. En el entorno de tu proyecto, ¿qué hace python -c "import ruff"?

    Elige una respuesta.

    Ver la respuesta: una pregunta

    Respuesta: Falla con ModuleNotFoundError, porque Ruff vive en un entorno propio

    Una aplicación instalada con pipx o uv tool te da su orden, no sus módulos: su entorno es solo suyo, y eso es lo que evita que sus dependencias choquen con las tuyas.

Elige la herramienta según el trabajo

Los tres hacen por debajo los mismos pasos: un entorno virtual, una instalación, un registro de versiones. Se diferencian en cuánto hacen por ti y en para qué sirven:

¿venv y pip, uv o pipx?
Criterio venv + pipuvpipx
Viene con Python SíNo: se instala una vezNo: se instala una vez
Crea entornos virtuales Sí, cuando se lo pidesSí, por sí solo, para un proyecto o un scriptSí, uno para cada aplicación
Puede instalar el propio Python NoSíSí, para una aplicación, si se lo pides
Anota las versiones exactas pip freeze > requirements.txtuv.lock, exportado como pylock.toml si se pideNo para proyectos: gestiona aplicaciones
Cuándo elegirlo Cualquier computadora, sin instalar nada másProyectos y scripts con dependenciasAplicaciones de línea de órdenes que usas en todas partes

Lee la tabla desde el trabajo que tienes. En una computadora del colegio en la que no puedes instalar nada más que Python, venv y pip es lo que tienes, y bastan. Un proyecto de equipo con dependencias y pruebas en cada cambio es trabajo de uv, con uv.lock en el repositorio. Un linter que usas en todas las carpetas va a pipx o a uv tool, y un script que ejecutas una vez al mes lleva su propio bloque y se ejecuta con uv run.

Nada de esto sustituye a saber usar venv y pip: uv y pipx automatizan los mismos pasos, y cuando alguno hace algo inesperado, las órdenes de la lección anterior son la forma de mirar dentro.

Conversor de TOML ↔ JSON (en inglés) Pega un pyproject.toml, o el TOML del bloque de un script, para comprobarlo y verlo como JSON.

Punto de control

Comprueba: elegir entre venv y pip, uv o pipx

  1. Una pregunta En las computadoras del colegio no puedes instalar nada más que el propio Python. Un proyecto allí necesita dos paquetes de PyPI. ¿Qué forma funciona?

    Elige una respuesta.

    Ver la respuesta: una pregunta

    Respuesta: python3 -m venv .venv y después .venv/bin/python -m pip install de los dos paquetes

    uv y pipx ahorran trabajo pero hay que instalarlos una vez; venv y pip forman parte de Python y hacen los mismos pasos en cualquier computadora que tenga Python.

Practica: leer los metadatos de un script

Escribe la parte de un ejecutor de scripts que lee el bloque: devuelve su TOML como un dict, None cuando no hay bloque, y lanza ValueError para un script con dos. Las pruebas de ejemplo se ejecutan en tu navegador:

Ejercicio · Intermedio · Python

Leer los metadatos en línea de un script

Escribe parse_script_metadata(text). text es el código fuente de un script de Python. Devuelve el TOML de su bloque # /// script, leído con tomllib.loads(), como un dict, siguiendo estas reglas de la especificación:

  • El bloque empieza con una línea que es exactamente # /// script y termina con una línea que es exactamente # ///.
  • Todas las líneas intermedias son comentarios: quítales sus dos primeros caracteres, # y un espacio. Una línea que solo es # se convierte en una línea vacía.
  • Un script sin bloque da None.
  • Un script con dos bloques script se rechaza: lanza ValueError.

Código inicial · script_meta.py

import tomllib


def parse_script_metadata(text):
    """Return the TOML of the script's '# /// script' block as a dict, or None if it has none."""
    return None
Las pruebas de ejemplo · test_script_meta.py
from script_meta import parse_script_metadata

BLOCK = """# /// script
# requires-python = ">=3.14"
# dependencies = [
#   "rich",
# ]
# ///
print("Report ready")
"""


def test_one_block():
    """reads the TOML of the block"""
    assert parse_script_metadata(BLOCK) == {"requires-python": ">=3.14", "dependencies": ["rich"]}


def test_no_block():
    """gives None for a script without a block"""
    assert parse_script_metadata('print("Report ready")\n') is None


def test_two_blocks():
    """refuses a script with two script blocks"""
    try:
        parse_script_metadata(BLOCK + BLOCK)
    except ValueError:
        return
    raise AssertionError("a script with two script blocks must raise ValueError")


def test_empty_dependencies():
    """reads a block whose dependencies list is empty"""
    assert parse_script_metadata("# /// script\n# dependencies = []\n# ///\n") == {"dependencies": []}


def test_line_of_only_a_hash():
    """treats a line that is only # as an empty line of the block"""
    text = '# /// script\n# requires-python = ">=3.14"\n#\n# dependencies = []\n# ///\n'
    assert parse_script_metadata(text) == {"requires-python": ">=3.14", "dependencies": []}
Una pista

Recorre text.splitlines() con una variable que contenga las líneas del bloque en el que estás, o None fuera de un bloque. La línea de apertura empieza una lista nueva; # /// la cierra y la añade a una lista de bloques terminados. Después del bucle, cuenta los bloques terminados: ninguno da None, uno va a tomllib.loads("\n".join(...)) y más lanzan ValueError.

Las pruebas de ejemplo se ejecutan en este dispositivo, en tu navegador (Pyodide): no se envía nada a mysmartcopilot.com. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes. Una comprobación en tu navegador es una orientación para ti, no la prueba de que el código sea correcto con cualquier entrada.

Termina con las preguntas de abajo, que siguen tus respuestas: un fallo trae una pregunta más sencilla sobre la misma idea, y cada opción lleva su propia explicación:

Práctica que se adapta a ti

Practica esta lección

10 preguntas sobre 5 ideas de esta lección. Cada respuesta se explica al momento, y lo que aciertas se guarda en este navegador.

  1. Pregunta 1 de 10 ¿Cuál de estos archivos no crea uv init weather-notes?

    Elige una respuesta.

    Ver la respuesta: pregunta 1 de 10

    Respuesta: uv.lock

    uv init escribe pyproject.toml, .python-version, README.md, un .gitignore, código inicial y un repositorio de Git. uv add, uv run y uv sync crean uv.lock y .venv la primera vez que se ejecutan.

  2. Pregunta 2 de 10 Traes el cambio de un compañero que añade un paquete a pyproject.toml y uv.lock. ¿Qué orden hace que tu .venv coincida antes de empezar a trabajar?

    Elige una respuesta.

    Ver la respuesta: pregunta 2 de 10

    Respuesta: uv sync

    uv sync hace que el entorno coincida exactamente con el bloqueo. Como uv run comprueba lo mismo antes de cada ejecución, un uv run también pondría al día .venv.

  3. Pregunta 3 de 10 ¿Qué anota uv.lock que no anota pyproject.toml?

    Elige una respuesta.

    Ver la respuesta: pregunta 3 de 10

    Respuesta: La versión exacta de cada paquete instalado, incluidas las dependencias de tus dependencias

    Un rango como rich>=15.0.0 puede coincidir con muchas versiones. uv.lock anota la que se eligió, y las versiones de las propias dependencias de rich, como markdown-it-py y Pygments.

  4. Pregunta 4 de 10 El bloqueo de tu proyecto contiene tablekit 2.3.0. El índice ofrece ahora la 2.4.0, su versión más nueva, y quieres que todos pasen a ella cuando haya superado tus pruebas. ¿Qué haces?

    Elige una respuesta.

    Ver la respuesta: pregunta 4 de 10

    Respuesta: Ejecutar uv lock --upgrade-package tablekit, ejecutar las pruebas y subir el nuevo uv.lock

    Las actualizaciones son deliberadas: uv lock --upgrade-package pasa un paquete a su versión permitida más nueva, las pruebas muestran si funciona, y el bloqueo subido lleva el cambio a todo el mundo.

  5. Pregunta 5 de 10 ¿Qué hace un python3 report.py sin más con el bloque # /// script del principio de report.py?

    Elige una respuesta.

    Ver la respuesta: pregunta 5 de 10

    Respuesta: Nada; para Python el bloque solo son comentarios, así que la importación falla si rich no está instalado

    El bloque es un conjunto de comentarios, que Python se salta. Por eso la ejecución registrada de report.py se detiene con ModuleNotFoundError, mientras que uv run report.py construiría primero un entorno con rich.

  6. Pregunta 6 de 10 ¿Qué imprime read_metadata.py, el programa que recorriste arriba?

    Elige una respuesta.

    Ver la respuesta: pregunta 6 de 10

    Respuesta: requires-python: >=3.14 dependencies: ['rich']

    Entre las líneas de apertura y cierre, el bucle reúne las líneas de TOML, y tomllib las convierte en un dict: la cadena >=3.14 y la lista ['rich'].

  7. Pregunta 7 de 10 ¿Cuál de estas cosas va en pipx o uv tool en lugar de en el entorno de un proyecto?

    Elige una respuesta.

    Ver la respuesta: pregunta 7 de 10

    Respuesta: Una aplicación de línea de órdenes que usas en todas las carpetas, como Ruff

    pipx y uv tool son para aplicaciones que se usan como órdenes. Las bibliotecas, y las herramientas que necesitan tu proyecto instalado, van en el entorno del proyecto.

  8. Pregunta 8 de 10 ¿Qué hace uvx ruff check?

    Elige una respuesta.

    Ver la respuesta: pregunta 8 de 10

    Respuesta: Ejecuta Ruff una vez en un entorno temporal, sin instalarlo

    uvx, la forma corta de uv tool run, ejecuta una herramienta en un entorno temporal y aislado: práctico para una herramienta que usas de vez en cuando. uv tool install la conserva de forma permanente.

  9. Pregunta 9 de 10 Un script de un solo archivo que ejecutas una vez al mes necesita rich. ¿Qué le encaja mejor?

    Elige una respuesta.

    Ver la respuesta: pregunta 9 de 10

    Respuesta: Un bloque '# /// script' que enumere rich, ejecutado con uv run o pipx run

    Para un solo archivo, los metadatos en línea mantienen las dependencias junto al código, y uv run o pipx run construyen el entorno cada vez que hace falta.

  10. Pregunta 10 de 10 Quieres la orden de Ruff en todos los terminales, sin tocar ningún proyecto. ¿Cómo instalas Ruff?

    Elige una respuesta.

    Ver la respuesta: pregunta 10 de 10

    Respuesta: pipx install ruff, o uv tool install ruff

    Para las aplicaciones de línea de órdenes que quieres en todas partes están pipx y uv tool: un entorno aislado para cada una, y solo las órdenes en PATH.

Preguntas de entrevista

Calentamiento (júnior): ¿qué diferencia hay entre pyproject.toml y uv.lock?

pyproject.toml es la descripción propia del proyecto, el archivo que editas: su nombre y su versión, las versiones de Python que admite y sus dependencias como rangos, como rich>=15.0.0, que dicen qué sería aceptable. uv.lock lo escribe uv: la versión exacta de cada paquete que instala el proyecto, incluidas las dependencias de sus dependencias, para que todas las computadoras y todas las instalaciones posteriores obtengan el mismo conjunto. pyproject.toml lo cambias tú o con uv add, y uv.lock nunca se edita a mano; los dos se suben al repositorio. Cuando quieras versiones más nuevas, actualizas el bloqueo a propósito, con uv lock --upgrade-package, y pruebas el resultado antes de subirlo.

Dos preguntas más difíciles parten de esta lección: una compilación que se rompió durante un fin de semana sin ningún cambio de código, y un equipo con sus herramientas instaladas en sitios equivocados. Piensa tu primer paso antes de abrir cada una:

Las respuestas modelo se abren con cualquier pase: 79 ₹ por 1 semana (2,99 US$), un solo pago que nunca se renueva.

  1. De júnior a nivel medio

    Tu integración continua instala un proyecto con pip install . desde pyproject.toml, cuyas dependencias son rangos como tablekit>=2.3. El lunes la compilación falla con AttributeError donde tu código invoca tablekit, aunque nada ha cambiado desde la compilación correcta del viernes. Diagnostícalo y decide qué subir y ejecutar para que no pueda volver a pasar.

    Respuesta modelo, Pro. Las respuestas modelo se abren con cualquier pase; los precios están en el aviso de esta página. Primer paso y preguntas de seguimiento (en inglés)

  2. De júnior a nivel medio

    Una compañera nueva instaló Ruff, pytest y el generador de código del proyecto con pipx. Ruff funciona, pero pytest no puede importar el proyecto, y el generador falla con ModuleNotFoundError por un paquete que usa el proyecto. ¿Qué herramientas van en el entorno del proyecto y cuáles en pipx o uv tool, y por qué?

    Respuesta modelo, Pro. Primer paso y preguntas de seguimiento (en inglés)

Para recordar

  • uv gestiona un proyecto desde pyproject.toml: uv init, uv add y uv run, y antes de cada ejecución comprueba que uv.lock y .venv están al día.
  • pyproject.toml contiene rangos de versiones aceptables; uv.lock anota un conjunto exacto, dependencias incluidas, y se sube al repositorio para que todo el mundo instale los mismos paquetes.
  • Los metadatos en línea de un script son un bloque de comentarios # /// script con requires-python y dependencies; uv run y pipx run lo leen, mientras que un python3 sin más lo ignora.
  • pipx install y uv tool install dan a cada aplicación de línea de órdenes su propio entorno y ponen solo sus órdenes en tu PATH; las herramientas que importan tu proyecto van en el proyecto.
  • uv y pipx automatizan los pasos de venv y pip, que siguen funcionando en cualquier computadora.

Referencias

Herramientas relacionadas

Informar de un problema con esta lección (en inglés)

Respuestas rápidas y búsqueda de herramientas

Escribe para buscar herramientas o para obtener una respuesta rápida (en inglés), por ejemplo 18% of 2500. Usa las flechas arriba y abajo para recorrer los resultados, Intro para elegir y Escape para cerrar.