37. Funciones integradas y herramientas útiles: dominar las utilidades principales de Python
Python proporciona una rica colección de funciones integradas que siempre están disponibles sin necesidad de importar ningún módulo. Estas funciones forman la base de la programación diaria en Python, ayudándote a trabajar de forma eficiente con datos, secuencias y colecciones. En este capítulo, exploraremos las herramientas integradas más útiles de Python y aprenderemos a aprovecharlas para escribir código más limpio y expresivo.
37.0) Comprender el sistema de tipos de Python
Antes de profundizar en funciones integradas específicas, es útil entender cómo Python organiza sus tipos de datos. Este conocimiento te ayudará a predecir qué operaciones funcionan con qué tipos y a entender los mensajes de error cuando ocurren.
Los tipos de datos de Python se pueden entender desde dos perspectivas complementarias:
Jerarquía de tipos: cómo se relacionan los tipos
Esto muestra cómo Python organiza los tipos en familias en función de lo que SON.
Vista basada en capacidades: lo que los tipos pueden hacer
Para las funciones integradas, lo que importa más es lo que los tipos pueden HACER:
Capacidades clave:
- Iterable (iterable): Se puede usar en bucles (loops)
for→ Funciona consum(),any(),all(),sorted() - Collection (collection): Iterable con
len()→ Funciona conlen()y el operadorin - Sequence (sequence): Collection con indexación → Soporta
[index]y slicing[start:end]
Por qué esto importa
Las funciones integradas requieren capacidades específicas:
| Función | Requiere | Funciona con |
|---|---|---|
len() | colección (collection) | str, list, dict, set, tuple |
sum() | iterable de números (iterable) | list, tuple, set, range, generator |
sorted() | iterable (iterable) | str, list, dict, set, tuple |
[index] | secuencia (sequence) | str, list, tuple, range |
Comprender estas categorías te ayuda a:
- Predecir qué funciones funcionan con qué tipos
- Entender mensajes de error como "object is not iterable"
- Saber cuándo puedes indexar (
[0]) frente a cuándo solo puedes iterar (for)
37.1) Funciones integradas comunes (len, sum, min, max, abs, round)
Las funciones integradas más usadas de Python te ayudan a realizar operaciones comunes sobre datos sin escribir bucles ni lógica compleja. Estas funciones están optimizadas, son legibles y forman la base del código idiomático de Python.
37.1.1) Medir longitud con len()
La función len() devuelve el número de elementos en una colección. Funciona con cadenas, listas(list), tuplas, diccionarios, conjuntos y cualquier otro tipo de colección.
# Contar caracteres en una cadena
message = "Hello, World!"
print(len(message)) # Output: 13
# Contar elementos en una lista
scores = [85, 92, 78, 90, 88]
print(len(scores)) # Output: 5
# Contar pares clave-valor en un diccionario
student = {"name": "Bob", "age": 21, "major": "CS"}
print(len(student)) # Output: 3
# Contar elementos únicos en un conjunto
unique_ids = {101, 102, 103, 101, 102} # Duplicados eliminados
print(len(unique_ids)) # Output: 3La función len() es particularmente útil cuando necesitas conocer el tamaño de los datos antes de procesarlos:
# Procesar datos según el tamaño
data = [12, 45, 23, 67, 89, 34]
if len(data) < 5:
print("Not enough data for analysis")
else:
print(f"Analizando {len(data)} puntos de datos") # Output: Analizando 6 puntos de datos
average = sum(data) / len(data)
print(f"Promedio: {average}") # Output: Promedio: 45.037.1.2) Calcular totales con sum()
La función sum() suma todos los números en un iterable. Es mucho más limpia que escribir un bucle para acumular valores.
# Sumar una lista de números
prices = [19.99, 24.50, 15.75, 32.00]
total = sum(prices)
print(f"Total: ${total}") # Output: Total: $92.24
# Sumar una tupla
daily_steps = (8500, 10200, 7800, 9500, 11000)
weekly_total = sum(daily_steps)
print(f"Total steps this week: {weekly_total}") # Output: Total steps this week: 47000
# Sumar un range
total_1_to_100 = sum(range(1, 101))
print(total_1_to_100) # Output: 5050Un ejemplo práctico combinando sum() y len() para calcular promedios:
# Calcular el promedio de puntuación de una prueba
test_scores = [88, 92, 79, 85, 90, 87]
total_score = sum(test_scores)
num_tests = len(test_scores)
average_score = total_score / num_tests
print(f"Average score: {average_score:.1f}") # Output: Average score: 86.8Limitación importante: sum() solo funciona con números. No puedes usarla para concatenar cadenas ni combinar listas(list):
# Esto genera TypeError
words = ["Hello", " ", "World"]
# sentence = sum(words) # TypeError: unsupported operand type(s)37.1.3) Encontrar extremos con min() y max()
Las funciones min() y max() encuentran los valores más pequeño y más grande en un iterable. Funcionan con números, cadenas y cualquier objeto que pueda compararse.
# Encontrar los números mínimo y máximo
temperatures = [72, 68, 75, 70, 73, 69]
coldest = min(temperatures)
warmest = max(temperatures)
print(f"Temperature range: {coldest}°F to {warmest}°F")
# Output: Temperature range: 68°F to 75°F
# Encontrar las cadenas mínima y máxima (alfabéticamente)
names = ["Zoe", "Alice", "Bob", "Charlie"]
first_alphabetically = min(names)
last_alphabetically = max(names)
print(f"First: {first_alphabetically}, Last: {last_alphabetically}")
# Output: First: Alice, Last: ZoeTambién puedes pasar varios argumentos directamente en lugar de una colección:
# Comparar valores individuales
lowest = min(45, 23, 67, 12, 89)
highest = max(45, 23, 67, 12, 89)
print(f"Lowest: {lowest}, Highest: {highest}")
# Output: Lowest: 12, Highest: 89
# Útil para comparar unos pocos valores específicos
price1 = 19.99
price2 = 24.50
price3 = 15.75
cheapest = min(price1, price2, price3)
print(f"Cheapest option: ${cheapest}") # Output: Cheapest option: $15.7537.1.4) Obtener valores absolutos con abs()
La función abs() devuelve el valor absoluto de un número: su distancia respecto a cero, siempre positiva. Esto es útil cuando te importa la magnitud pero no la dirección.
# Valor absoluto de números negativos
print(abs(-42)) # Output: 42
print(abs(-3.14)) # Output: 3.14
# Valor absoluto de números positivos (sin cambios)
print(abs(42)) # Output: 42
print(abs(3.14)) # Output: 3.14
# Valor absoluto de cero
print(abs(0)) # Output: 0Un caso de uso común es calcular diferencias donde la dirección no importa:
# Calcular el cambio de temperatura (solo magnitud)
morning_temp = 65
evening_temp = 72
temperature_change = abs(evening_temp - morning_temp)
print(f"Temperature changed by {temperature_change}°F")
# Output: Temperature changed by 7°F37.1.5) Redondear números con round()
La función round() redondea un número a un número especificado de decimales. Sin el segundo argumento, redondea al entero más cercano.
# Redondear al entero más cercano
print(round(3.7)) # Output: 4
print(round(3.2)) # Output: 3
print(round(3.5)) # Output: 4
print(round(4.5)) # Output: 4 (redondea al número par más cercano)
# Redondear a un número específico de decimales
price = 19.876
print(round(price, 2)) # Output: 19.88 (2 decimales)
print(round(price, 1)) # Output: 19.9 (1 decimal)
# Redondear a decimales negativos (redondea a decenas, centenas, etc.)
population = 1234567
print(round(population, -3)) # Output: 1235000 (al millar más cercano)
print(round(population, -4)) # Output: 1230000 (a la decena de millar más cercana)Nota sobre valores a mitad: Al redondear un número que está exactamente a mitad de camino entre dos enteros, Python tiene una regla especial. Por ejemplo, 2.5 está exactamente a mitad de camino entre 2 y 3. Podrías esperar que redondee hacia arriba a 3, pero Python redondea al vecino que sea un número par; en este caso, 2.
Esto se llama "redondeo del banquero" o "round half to even". Es parte del estándar IEEE 754 y ayuda a reducir el sesgo a lo largo de muchas operaciones de redondeo.
# Los valores a mitad se redondean al número par más cercano
print(round(0.5)) # Output: 0 (0 is even)
print(round(1.5)) # Output: 2 (2 is even)
print(round(2.5)) # Output: 2 (2 is even)
print(round(3.5)) # Output: 4 (4 is even)
print(round(4.5)) # Output: 4 (4 is even)37.2) Enumerar secuencias con enumerate()
Al iterar sobre una secuencia, a menudo necesitas tanto el elemento como su posición. La función enumerate() proporciona ambos, eliminando la necesidad de variables contador manuales.
37.2.1) El problema con los contadores manuales
Antes de aprender sobre enumerate(), los programadores suelen usar una variable contador para llevar el seguimiento de la posición:
# Enfoque con contador manual (funciona, pero no es ideal)
fruits = ["apple", "banana", "cherry", "date"]
index = 0
for fruit in fruits:
print(f"{index}: {fruit}")
index += 1
# Output:
# 0: apple
# 1: banana
# 2: cherry
# 3: dateEste enfoque tiene varias desventajas:
- Variable extra que gestionar (
index) - Es fácil olvidarse de incrementar el contador
37.2.2) Usar enumerate() para posición y valor
La función enumerate() resuelve este problema de forma elegante. Toma un iterable y devuelve pares de (índice, elemento):
# Usar enumerate() - más limpio y más idiomático en Python
fruits = ["apple", "banana", "cherry", "date"]
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
# Output:
# 0: apple
# 1: banana
# 2: cherry
# 3: dateLa sintaxis for index, fruit in enumerate(fruits) usa desempaquetado de tuplas(tuple unpacking) (como aprendimos en el Capítulo 15). En cada iteración, enumerate() proporciona una tupla como (0, "apple"), que se desempaqueta en las variables index y fruit.
Esto es lo que enumerate() produce realmente:
# Ver directamente la salida de enumerate
fruits = ["apple", "banana", "cherry"]
enumerated = list(enumerate(fruits))
print(enumerated)
# Output: [(0, 'apple'), (1, 'banana'), (2, 'cherry')]37.2.3) Empezar la enumeración desde un número distinto
De forma predeterminada, enumerate() empieza a contar desde 0. Puedes indicar un número de inicio diferente con el parámetro start:
# Empezar a contar desde 1 (útil para mostrar)
tasks = ["Write code", "Test code", "Deploy code"]
for number, task in enumerate(tasks, start=1):
print(f"Step {number}: {task}")
# Output:
# Step 1: Write code
# Step 2: Test code
# Step 3: Deploy codeEsto es especialmente útil al mostrar listas numeradas a usuarios, que normalmente esperan que el conteo comience en 1:
# Menú con opciones numeradas
menu_items = ["New Game", "Load Game", "Settings", "Quit"]
print("Main Menu:")
for number, item in enumerate(menu_items, start=1):
print(f"{number}. {item}")
# Output:
# Main Menu:
# 1. New Game
# 2. Load Game
# 3. Settings
# 4. Quit37.2.4) enumerate() con cadenas y otros iterables
La función enumerate() funciona con cualquier iterable, no solo con listas(list):
# Enumerar caracteres en una cadena
word = "Python"
for position, letter in enumerate(word):
print(f"Letter {position}: {letter}")
# Output:
# Letter 0: P
# Letter 1: y
# Letter 2: t
# Letter 3: h
# Letter 4: o
# Letter 5: n
# Enumerar una tupla
coordinates = (10, 20, 30, 40)
for index, value in enumerate(coordinates):
print(f"Coordinate {index}: {value}")
# Output:
# Coordinate 0: 10
# Coordinate 1: 20
# Coordinate 2: 30
# Coordinate 3: 40La función enumerate() hace el código más legible y menos propenso a errores. Siempre que necesites tanto la posición como el valor en un bucle, usa enumerate() en lugar de gestionar un contador manualmente.
37.3) Combinar secuencias con zip()
La función zip() combina múltiples iterables elemento por elemento, creando pares (o tuplas) de elementos correspondientes. Esto es muy valioso cuando necesitas procesar datos relacionados de secuencias separadas de forma simultánea.
37.3.1) Comprender cómo funciona zip()
La función zip() toma dos o más iterables y devuelve un iterador de tuplas, donde cada tupla contiene un elemento de cada iterable de entrada:
# Combinar dos listas
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
combined = list(zip(names, ages))
print(combined)
# Output: [('Alice', 25), ('Bob', 30), ('Charlie', 35)]El nombre "zip" viene de la cremallera de la ropa: combina dos lados separados en una estructura unida, elemento por elemento.
Aquí tienes una representación visual de cómo zip() empareja elementos:
37.3.2) Usar zip() en bucles
El uso más común de zip() es en bucles for, donde necesitas iterar sobre múltiples secuencias de forma simultánea:
# Procesar datos en paralelo
students = ["Alice", "Bob", "Charlie", "Diana"]
scores = [92, 85, 88, 95]
for student, score in zip(students, scores):
print(f"{student} scored {score}")
# Output:
# Alice scored 92
# Bob scored 85
# Charlie scored 88
# Diana scored 95Esto es mucho más limpio que usar índices:
# Sin zip() - más complejo y propenso a errores
students = ["Alice", "Bob", "Charlie", "Diana"]
scores = [92, 85, 88, 95]
for i in range(len(students)):
print(f"{students[i]} scored {scores[i]}")
# Same output, but more code and potential for index errors37.3.3) Manejar secuencias de longitudes diferentes
Cuando las secuencias de entrada tienen longitudes diferentes, zip() se detiene cuando se agota la secuencia más corta:
# Secuencias de longitudes desiguales
names = ["Alice", "Bob", "Charlie", "Diana"]
ages = [25, 30] # Solo 2 edades
for name, age in zip(names, ages):
print(f"{name} is {age} years old")
# Output:
# Alice is 25 years old
# Bob is 30 years old
# (Charlie and Diana are not processed)Este comportamiento evita errores, pero puede llevar a pérdida silenciosa de datos si no tienes cuidado. Verifica siempre que tus secuencias tengan las longitudes esperadas:
# Comprobar desajuste de longitudes
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30]
if len(names) != len(ages):
print(f"Warning: {len(names)} names but {len(ages)} ages")
print("Only processing the first", min(len(names), len(ages)), "entries")
# Output: Warning: 3 names but 2 ages
# Output: Only processing the first 2 entries
# Continuar con zip() - se detendrá en la más corta
for name, age in zip(names, ages):
print(f"{name} is {age} years old")37.3.4) Hacer zip de más de dos secuencias
La función zip() puede combinar cualquier número de iterables:
# Combinar tres secuencias
products = ["Laptop", "Mouse", "Keyboard"]
prices = [999.99, 24.99, 79.99]
quantities = [5, 20, 15]
print("Inventory Report:")
for product, price, quantity in zip(products, prices, quantities):
total_value = price * quantity
print(f"{product}: ${price} × {quantity} = ${total_value:.2f}")
# Output:
# Inventory Report:
# Laptop: $999.99 × 5 = $4999.95
# Mouse: $24.99 × 20 = $499.80
# Keyboard: $79.99 × 15 = $1199.8537.3.5) Crear diccionarios con zip()
Un patrón potente es usar zip() para crear diccionarios a partir de secuencias separadas de claves y valores:
# Crear un diccionario a partir de dos listas
keys = ["name", "age", "city"]
values = ["Alice", 25, "Boston"]
person = dict(zip(keys, values))
print(person)
# Output: {'name': 'Alice', 'age': 25, 'city': 'Boston'}37.4) Agregación booleana con any() y all()
Las funciones any() y all() prueban condiciones a lo largo de iterables completos, devolviendo un único resultado booleano. Son herramientas potentes para validación y toma de decisiones basadas en múltiples condiciones.
37.4.1) Comprender any(): True si al menos un elemento es True
La función any() devuelve True si al menos un elemento en un iterable es truthy (se evalúa como True). Si todos los elementos son falsy, devuelve False:
# Ejemplos básicos de any()
print(any([True, False, False])) # Output: True (at least one True)
print(any([False, False, False])) # Output: False (all False)
print(any([False, True, True])) # Output: True (multiple True values)
# Iterables vacíos
print(any([])) # Output: False (no elements to be True)La función any() usa las reglas de truthiness de Python (como aprendimos en el Capítulo 7). Los números distintos de cero, las cadenas no vacías y las colecciones no vacías son truthy:
# any() con diferentes valores truthy/falsy
print(any([0, 0, 1])) # Output: True (1 is truthy)
print(any([0, 0, 0])) # Output: False (all zeros are falsy)
print(any(["", "", "text"])) # Output: True ("text" is truthy)
print(any(["", "", ""])) # Output: False (empty strings are falsy)37.4.2) Usos prácticos de any()
Ejemplo: comprobar si se cumple alguna condición
# Comprobar si alguna puntuación es suspensa (por debajo de 60)
scores = [75, 82, 55, 90, 88]
has_failing_grade = any(score < 60 for score in scores)
if has_failing_grade:
print("Warning: At least one failing grade")
# Output: Warning: At least one failing grade
else:
print("All grades are passing")37.4.3) Comprender all(): True solo si todos los elementos son True
La función all() devuelve True solo si todos los elementos en un iterable son truthy. Si cualquier elemento es falsy, devuelve False:
# Ejemplos básicos de all()
print(all([True, True, True])) # Output: True (all True)
print(all([True, False, True])) # Output: False (one False)
print(all([True, True, False])) # Output: False (one False)
# Iterables vacíos
print(all([])) # Output: True (vacuous truth - no False elements)El comportamiento con iterables vacíos puede parecer sorprendente: all([]) devuelve True. A esto se le llama verdad vacía (vacuous truth): la afirmación "todos los elementos son True" es técnicamente verdadera cuando no hay elementos que la contradigan.
# all() con diferentes valores truthy/falsy
print(all([1, 2, 3])) # Output: True (all non-zero)
print(all([1, 0, 3])) # Output: False (0 is falsy)
print(all(["a", "b", "c"])) # Output: True (all non-empty)
print(all(["a", "", "c"])) # Output: False (empty string is falsy)37.4.4) Usos prácticos de all()
Ejemplo: validar que se cumplen todas las condiciones
# Comprobar si todas las puntuaciones son aprobatorias (60 o más)
scores = [75, 82, 68, 90, 88]
all_passing = all(score >= 60 for score in scores)
if all_passing:
print("Congratulations! All grades are passing")
# Output: Congratulations! All grades are passing
else:
print("Some grades need improvement")37.4.5) Evaluación de cortocircuito en any() y all()
Tanto any() como all() usan evaluación de cortocircuito(short-circuit evaluation) (como aprendimos en el Capítulo 9). Dejan de comprobar en cuanto se determina el resultado:
# Función que imprime cuando se llama (para mostrar la ejecución)
def is_positive(n):
print(f"Checking {n}")
return n > 0
# any() se detiene en el primer True
print("Testing any():")
numbers = [0, 0, 1, 2, 3]
result = any(is_positive(n) for n in numbers)
# Output:
# Testing any():
# Checking 0
# Checking 0
# Checking 1
# (Stops here - doesn't check 2 or 3)
print(f"Result: {result}") # Output: Result: True
print("\nTesting all():")
numbers = [1, 2, 0, 3, 4]
result = all(is_positive(n) for n in numbers)
# Output:
# Testing all():
# Checking 1
# Checking 2
# Checking 0
# (Stops here - doesn't check 3 or 4)
print(f"Result: {result}") # Output: Result: FalseEsto hace que any() y all() sean eficientes: no pierden tiempo comprobando elementos después de que el resultado ya se haya determinado.
37.5) Ordenar con sorted() y claves personalizadas
La función sorted() crea una nueva lista ordenada a partir de cualquier iterable. A diferencia del método .sort() (que solo funciona con listas y las modifica in place), sorted() funciona con cualquier iterable y siempre devuelve una lista nueva.
37.5.1) Ordenación básica con sorted()
La función sorted() ordena elementos en orden ascendente de forma predeterminada:
# Ordenar números
numbers = [42, 17, 93, 8, 55]
sorted_numbers = sorted(numbers)
print(sorted_numbers) # Output: [8, 17, 42, 55, 93]
# La lista original no cambia
print(numbers) # Output: [42, 17, 93, 8, 55]
# Ordenar cadenas (alfabéticamente)
names = ["Charlie", "Alice", "Bob", "Diana"]
sorted_names = sorted(names)
print(sorted_names) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']La función sorted() funciona con cualquier iterable, no solo con listas:
# Ordenar una tupla (devuelve una lista)
coordinates = (5, 2, 8, 1, 9)
sorted_coords = sorted(coordinates)
print(sorted_coords) # Output: [1, 2, 5, 8, 9]
# Ordenar una cadena (devuelve una lista de caracteres)
word = "python"
sorted_letters = sorted(word)
print(sorted_letters) # Output: ['h', 'n', 'o', 'p', 't', 'y']
# Ordenar un conjunto (devuelve una lista ordenada)
unique_numbers = {5, 8, 2, 1}
sorted_unique = sorted(unique_numbers)
print(sorted_unique) # Output: [1, 2, 5, 8]37.5.2) Ordenación inversa
Usa el parámetro reverse=True para ordenar en orden descendente:
# Orden descendente para números
scores = [85, 92, 78, 95, 88]
highest_first = sorted(scores, reverse=True)
print(highest_first) # Output: [95, 92, 88, 85, 78]
# Orden descendente para cadenas (alfabético inverso)
names = ["Charlie", "Alice", "Bob", "Diana"]
reverse_alpha = sorted(names, reverse=True)
print(reverse_alpha) # Output: ['Diana', 'Charlie', 'Bob', 'Alice']37.5.3) Comprender el parámetro key
El parámetro key es donde sorted() se vuelve realmente potente. Transforma cómo Python compara elementos durante la ordenación.
¿Qué es el parámetro key?
El parámetro key acepta una función. Python llama a esta función sobre cada elemento para extraer una "clave de comparación", y luego ordena basándose en esas claves en lugar de los elementos originales.
Cómo funciona paso a paso:
- Python llama a la función key en cada elemento
- Python recopila todas las claves
- Python ordena comparando estas claves
- Python devuelve los elementos originales en el nuevo orden
# Ejemplo: ordenar por longitud
words = ["python", "is", "awesome"]
# Paso 1: Python llama a len() para cada palabra
# len("python") → 6
# len("is") → 2
# len("awesome") → 7
# Paso 2: Python tiene estas claves: [6, 2, 7]
# Paso 3: Python ordena las claves: [2, 6, 7]
# Paso 4: Python devuelve words en ese orden: ["is", "python", "awesome"]
result = sorted(words, key=len)
print(result) # Output: ['is', 'python', 'awesome']Visualizar la función key:
¿Qué puede ser una función key?
La función key debe:
- Aceptar un argumento (el elemento que se está ordenando)
- Devolver un valor que Python pueda comparar (números, cadenas, tuplas, etc.)
# Las funciones integradas funcionan genial
sorted(numbers, key=abs) # Ordenar por valor absoluto
sorted(words, key=len) # Ordenar por longitud
sorted(names, key=str.lower) # Ordenar sin distinguir mayúsculas/minúsculas
# Tus propias funciones
def first_letter(word):
return word[0]
sorted(words, key=first_letter) # Ordenar por primera letra
# Funciones lambda (Capítulo 23)
sorted(words, key=lambda w: w[-1]) # Ordenar por última letraImportante: la función key se llama una vez por elemento
# Demostrar cuándo se llama a la función key
def show_key(word):
print(f"Getting key for: {word}")
return len(word)
words = ["cat", "elephant", "dog"]
result = sorted(words, key=show_key)
# Output:
# Getting key for: cat
# Getting key for: elephant
# Getting key for: dog
print(result) # Output: ['cat', 'dog', 'elephant']Importante: la función key se llama una vez por elemento
Observa que show_key se llama exactamente una vez para cada palabra, no repetidamente durante las comparaciones. Python es eficiente: extrae primero todas las claves, las cachea y después ordena usando las claves en caché.
Piensa en key como la respuesta a: "¿Qué aspecto debería comparar?"
key=len→ "Comparar por longitud"key=abs→ "Comparar por valor absoluto"key=str.lower→ "Comparar como si todo estuviera en minúsculas"key=lambda x: x[1]→ "Comparar por el segundo elemento"
El parámetro key te permite ordenar por cualquier propiedad de tus elementos, haciendo que sorted() sea increíblemente versátil.
37.5.4) Ordenar con funciones integradas como claves
Las funciones integradas de Python son excelentes funciones key:
# Ordenar por valor absoluto
numbers = [-5, 2, -8, 1, -3, 7]
sorted_by_magnitude = sorted(numbers, key=abs)
print(sorted_by_magnitude) # Output: [1, 2, -3, -5, 7, -8]
# Ordenar cadenas sin distinguir mayúsculas/minúsculas
names = ["alice", "Bob", "CHARLIE", "diana"]
sorted_case_insensitive = sorted(names, key=str.lower)
print(sorted_case_insensitive) # Output: ['alice', 'Bob', 'CHARLIE', 'diana']37.5.5) Ordenar estructuras de datos complejas
Al ordenar listas de tuplas o listas(list), puedes usar indexación para especificar por cuál elemento ordenar:
# Ordenar tuplas por el segundo elemento
students = [
("Alice", 92),
("Bob", 85),
("Charlie", 88),
("Diana", 95)
]
# Ordenar por puntuación (segundo elemento)
by_score = sorted(students, key=lambda student: student[1])
print(by_score)
# Output: [('Bob', 85), ('Charlie', 88), ('Alice', 92), ('Diana', 95)]
# Ordenar por puntuación de forma descendente
by_score_desc = sorted(students, key=lambda student: student[1], reverse=True)
print(by_score_desc)
# Output: [('Diana', 95), ('Alice', 92), ('Charlie', 88), ('Bob', 85)]Nota: Aquí estamos usando lambda (como aprendimos en el Capítulo 23). Una lambda es una función anónima pequeña. La expresión lambda student: student[1] crea una función que toma una tupla student y devuelve su segundo elemento (la puntuación).
37.5.6) Ordenación multinivel
Puedes ordenar por múltiples criterios devolviendo una tupla desde la función key. Python compara tuplas elemento por elemento, de izquierda a derecha:
Cómo funciona la comparación de tuplas:
Cuando Python compara dos tuplas, sigue estas reglas:
- Compara los primeros elementos. Si son diferentes, la comparación termina.
- Si los primeros elementos son iguales, compara los segundos elementos.
- Si los segundos elementos son iguales, compara los terceros elementos.
- Continúa hasta encontrar una diferencia o hasta quedarse sin elementos.
# Ejemplos de comparación de tuplas
print((1, 'a') < (2, 'z')) # Output: True (1 < 2, so True immediately)
print((1, 'z') < (1, 'a')) # Output: False (1 == 1, so compare 'z' < 'a')
print((1, 'a') < (1, 'a')) # Output: False (both tuples are equal)
print((1, 2, 9) < (1, 3, 1)) # Output: True (1 == 1, then 2 < 3)Esto hace que las tuplas sean perfectas para la ordenación multinivel: Python gestiona automáticamente la lógica de "comparar el primer criterio, luego el segundo, luego el tercero" por ti:
# Ordenar por múltiples criterios
students = [
("Alice", "Smith", 92),
("Bob", "Jones", 85),
("Alice", "Brown", 88),
("Charlie", "Smith", 85)
]
# Ordenar por nombre, luego por apellido
by_name = sorted(students, key=lambda s: (s[0], s[1]))
print("By name:")
for student in by_name:
print(f" {student}")
# Output:
# By name:
# ('Alice', 'Brown', 88)
# ('Alice', 'Smith', 92)
# ('Bob', 'Jones', 85)
# ('Charlie', 'Smith', 85)
# Ordenar por puntuación descendente, luego por nombre ascendente
by_score_then_name = sorted(students, key=lambda s: (-s[2], s[0]))
print("\nBy score (high to low), then name:")
for student in by_score_then_name:
print(f" {student}")
# Output:
# By score (high to low), then name:
# ('Alice', 'Smith', 92)
# ('Alice', 'Brown', 88)
# ('Bob', 'Jones', 85)
# ('Charlie', 'Smith', 85)Nota: Para ordenar un criterio de forma descendente y otro de forma ascendente, negamos el valor numérico (-s[2]). Esto funciona porque negar invierte el orden de ordenación para números. En el ejemplo anterior, -s[2] ordena las puntuaciones de mayor a menor, mientras que s[0] ordena los nombres de la A a la Z.
37.5.7) Usar funciones auxiliares para claves complejas
Cuando la lógica de ordenación se vuelve compleja, definir una función auxiliar hace que el código sea más legible y mantenible. Luego puedes usar esta función auxiliar dentro de tu función key.
Ejemplo: ordenar archivos por extensión
Supón que quieres agrupar archivos por su extensión (.csv, .jpg, .pdf, etc.) y, dentro de cada grupo, ordenarlos alfabéticamente por nombre de archivo. La función key necesita extraer la extensión del archivo, lo que requiere algo de manipulación de cadenas.
# Ordenar archivos por extensión y luego por nombre
files = [
"report.pdf",
"data.csv",
"image.jpg",
"notes.txt",
"backup.csv",
"photo.jpg"
]
# Extraer la extensión para ordenar
def get_extension(filename):
"""Extraer la extensión de archivo de un nombre de archivo."""
return filename.split(".")[-1] # Dividir por "." y obtener la última parte
# Usar la función auxiliar en la key
sorted_files = sorted(files, key=lambda f: (get_extension(f), f))
print("Files sorted by extension, then name:")
for file in sorted_files:
print(f" {file}")
# Output:
# Files sorted by extension, then name:
# backup.csv # csv files first (alphabetically)
# data.csv # csv files first (alphabetically)
# image.jpg # jpg files next
# photo.jpg # jpg files next
# report.pdf # pdf files next
# notes.txt # txt files lastCómo funciona:
- La función key
lambda f: (get_extension(f), f)devuelve una tupla para cada nombre de archivo - Para "report.pdf", devuelve
("pdf", "report.pdf") - Para "data.csv", devuelve
("csv", "data.csv") - Python ordena por el primer elemento de la tupla (extensión) y luego por el segundo elemento (nombre completo del archivo)
- Esto agrupa los archivos por extensión y ordena alfabéticamente dentro de cada grupo
¿Por qué usar una función auxiliar?
Compara la legibilidad:
# Sin función auxiliar - más difícil de entender
sorted_files = sorted(files, key=lambda f: (f.split(".")[-1], f))
# Con función auxiliar - intención más clara
sorted_files = sorted(files, key=lambda f: (get_extension(f), f))La función auxiliar hace que tu código sea autoexplicativo. Cualquiera que lea get_extension(f) entiende inmediatamente lo que ocurre, mientras que f.split(".")[-1] requiere análisis mental.
37.5.8) sorted() vs .sort(): cuándo usar cada uno
Python proporciona dos maneras de ordenar:
sorted()- Función que devuelve una lista ordenada nueva.sort()- Método de lista que ordena in place
# sorted() - crea una lista nueva, el original no cambia
numbers = [3, 1, 4, 1, 5]
sorted_numbers = sorted(numbers)
print(f"Original: {numbers}") # Output: Original: [3, 1, 4, 1, 5]
print(f"Sorted: {sorted_numbers}") # Output: Sorted: [1, 1, 3, 4, 5]
# .sort() - modifica la lista in place, devuelve None
numbers = [3, 1, 4, 1, 5]
result = numbers.sort()
print(f"Modified: {numbers}") # Output: Modified: [1, 1, 3, 4, 5]
print(f"Return value: {result}") # Output: Return value: NoneCuándo usar sorted():
- Necesitas mantener el orden original
- Estás ordenando algo que no es una lista (tupla, cadena, conjunto, etc.)
- Quieres ordenar y asignar en una sola expresión
Cuándo usar .sort():
- Tienes una lista y no necesitas el orden original
- Quieres ahorrar memoria (no se crea una lista nueva)
- Estás ordenando una lista grande in place por eficiencia
La función sorted() es una de las herramientas más versátiles de Python. Combinada con el parámetro key, puede manejar prácticamente cualquier requisito de ordenación, desde el ordenamiento simple de números hasta la ordenación compleja con múltiples criterios de estructuras de datos anidadas.
Este capítulo te ha equipado con las funciones integradas y herramientas esenciales de Python. Has aprendido a:
- Comprender la jerarquía de tipos de Python y predecir qué operaciones funcionan con qué tipos
- Usar funciones fundamentales como
len(),sum(),min(),max(),abs()yround()para operaciones comunes - Iterar con información de posición usando
enumerate() - Procesar secuencias en paralelo de forma simultánea usando
zip() - Tomar decisiones a través de colecciones usando
any()yall() - Ordenar datos de forma flexible con
sorted()y funciones key personalizadas
Estas herramientas forman la base del código idiomático de Python. Son eficientes, legibles y manejan correctamente los casos límite. A medida que sigas programando, verás que recurres a estas funciones constantemente: son los bloques de construcción que hacen que el código Python sea elegante y expresivo.
En el próximo capítulo, exploraremos los decoradores(decorators), que te permiten modificar y mejorar el comportamiento de las funciones de formas potentes, construyendo sobre los conceptos de funciones de primera clase que aprendimos en el Capítulo 23.