Serie Fundamentos de Python — Parte 6. Esta publicación continúa la Parte 1: Lo que Todo Principiante Debería Saber, la Parte 2: Variables y Tipos de Datos, la Parte 3: Listas y Diccionarios, la Parte 4: La Sentencia If y la Parte 5: Entrada del Usuario y Ciclos While. Cada publicación de la serie toma un tema y lo explica a fondo. Esta trata sobre las funciones.
Revisa los programas de las partes anteriores de esta serie y notarás algo: los mismos patrones aparecen una y otra vez. Hacer una pregunta, limpiar la respuesta, revisarla y volver a preguntar si está mal. Sumar una lista de precios. Dar formato de dólares a un número. Cada vez que necesitas uno de esos pasos otra vez, podrías copiar y pegar el código, pero entonces un error corregido en una copia sigue presente en todas las demás, y tus programas se vuelven más largos y difíciles de leer.
Las funciones resuelven esto. Una función es un bloque de código con nombre que hace un solo trabajo. La escribes una vez, le das un nombre claro y luego la llamas cada vez que necesitas ese trabajo. Has usado funciones desde la primera publicación: print(), input(), len(), sorted() y round() son funciones que vienen con Python. En esta publicación aprenderás a escribir las tuyas.
Como siempre, abre el intérprete de Python (python3 en tu terminal), o guarda los ejemplos en un archivo .py, y prueba cada uno por tu cuenta.
Definir y Llamar una Función
Una función se crea con la palabra clave def:
def greet():
"""Print a short welcome message."""
print("Welcome to Bright Bakery!")
print("Fresh bread every morning.")
Esto es lo que hace cada parte:
defle indica a Python que estás definiendo una función.greetes el nombre de la función. Los nombres de funciones siguen las mismas reglas que los de variables y se escriben ensnake_case. Los buenos nombres de funciones suelen empezar con un verbo:calculate_total,send_email,get_price.- Los paréntesis
()contienen las entradas de la función. Esta no necesita ninguna, así que están vacíos, pero siguen siendo obligatorios. - Los dos puntos y el bloque con sangría de abajo forman el cuerpo de la función: el código que se ejecuta cuando la llamas.
- El texto entre comillas triples es un docstring: una descripción breve de lo que hace la función. Es opcional, pero es un gran hábito. Los editores lo muestran cuando pasas el cursor sobre la función, y
help(greet)lo imprime.
Definir una función no la ejecuta. Para ejecutarla, llámala escribiendo su nombre seguido de paréntesis:
greet()
greet()
Welcome to Bright Bakery!
Fresh bread every morning.
Welcome to Bright Bakery!
Fresh bread every morning.
Python lee tu archivo de arriba hacia abajo, así que una función debe definirse antes de llamarla.
Parámetros y Argumentos
La mayoría de las funciones necesitan información para hacer su trabajo. Las entradas se escriben dentro de los paréntesis como parámetros:
def greet(name):
"""Greet a person by name."""
print(f"Welcome, {name}!")
greet("Maria")
greet("Carlos")
Welcome, Maria!
Welcome, Carlos!
Estas dos palabras se confunden a menudo, así que esta es la diferencia:
- Un parámetro es el nombre de variable en la definición de la función:
name. - Un argumento es el valor real que pasas al llamarla:
"Maria".
Varios Parámetros
Separa varios parámetros con comas. Cuando llamas a la función, los argumentos se asignan a los parámetros en orden. Se llaman argumentos posicionales:
def describe_project(client, pages):
print(f"{client} needs a {pages}-page website.")
describe_project("Bright Bakery", 5)
# Bright Bakery needs a 5-page website.
El orden importa. Si intercambias los argumentos, el resultado no tiene sentido:
describe_project(5, "Bright Bakery")
# 5 needs a Bright Bakery-page website.
Si pasas un número equivocado de argumentos, Python se detiene con un TypeError que te dice exactamente qué falta:
describe_project("Bright Bakery")
# TypeError: describe_project() missing 1 required positional argument: 'pages'
Argumentos con Nombre
También puedes pasar los argumentos por nombre. Se llaman argumentos con nombre (keyword arguments), y con ellos el orden no importa:
describe_project(pages=5, client="Bright Bakery")
# Bright Bakery needs a 5-page website.
Los argumentos con nombre hacen que las llamadas sean más fáciles de leer, sobre todo cuando una función recibe varios valores y no es obvio cuál es cuál. Puedes combinar ambos estilos, pero los argumentos posicionales deben ir primero.
Valores Predeterminados
Un parámetro puede tener un valor predeterminado, que se usa cuando quien llama a la función no da uno. Así el argumento se vuelve opcional:
def describe_project(client, pages, project_type="website"):
print(f"{client} needs a {pages}-page {project_type}.")
describe_project("Bright Bakery", 5)
describe_project("Valley Dental", 1, "landing page")
describe_project("Calexico Coffee", 12, project_type="online store")
Bright Bakery needs a 5-page website.
Valley Dental needs a 1-page landing page.
Calexico Coffee needs a 12-page online store.
Los parámetros con valor predeterminado deben ir después de los que no lo tienen. def describe_project(project_type="website", client, pages): es un SyntaxError.
Valores de Retorno
Hasta ahora, estas funciones han impreso sus resultados. Eso está bien para mostrar un mensaje, pero la mayoría de las veces quieres que una función devuelva un resultado para que el resto de tu programa lo pueda usar: guardarlo en una variable, hacer más cálculos con él o pasarlo a otra función. Para eso sirve return:
def add_tax(price, tax_rate=0.0775):
"""Return the price with sales tax added, rounded to cents."""
return round(price * (1 + tax_rate), 2)
total = add_tax(100)
print(total) # 107.75
print(add_tax(19.99)) # 21.54
print(add_tax(100) * 3) # 323.25
Cuando Python llega a return, la función termina de inmediato y el valor después de return se convierte en el resultado de la llamada. add_tax(100) se reemplaza por 107.75, igual que len([1, 2, 3]) se reemplaza por 3.
Return vs. Print
Esta es una de las confusiones más comunes entre principiantes, así que vale la pena verlas lado a lado:
def show_total(price):
print(price * 2) # muestra el valor y luego lo descarta
def get_total(price):
return price * 2 # devuelve el valor a quien llamó la función
a = show_total(10) # imprime 20
b = get_total(10) # no imprime nada
print(a) # None
print(b) # 20
show_total() muestra 20 en pantalla, pero el valor no se guarda en ningún lado. Una función que no llega a una sentencia return devuelve None automáticamente, así que a termina vacía. get_total() no imprime nada, pero b guarda el resultado real.
Una buena regla: haz que las funciones devuelvan valores, e imprime en el código que las llama. Las funciones que devuelven valores son más fáciles de reutilizar y de probar.
Salir Antes con Return
Como return termina la función, puedes usarlo para salir en cuanto conoces la respuesta. Esto suele simplificar las sentencias if:
def shipping_cost(order_total, is_member=False):
"""Return the shipping cost for an order."""
if is_member:
return 0
if order_total >= 75:
return 0
return 7.99
print(shipping_cost(120)) # 0
print(shipping_cost(40, is_member=True)) # 0
print(shipping_cost(40)) # 7.99
Devolver Más de un Valor
Una función puede devolver varios valores separados por comas. Python los agrupa en una tupla, que puedes desempacar en variables separadas, como viste en la Parte 2:
def price_range(prices):
"""Return the lowest and highest price in a list."""
return min(prices), max(prices)
low, high = price_range([49, 19, 99, 5])
print(f"Prices from ${low} to ${high}") # Prices from $5 to $99
Devolver Listas y Diccionarios
Una función puede devolver cualquier tipo de valor, incluidas las listas y los diccionarios de la Parte 3. Es una forma común de agrupar información relacionada:
def build_client(name, business, email=""):
"""Return a dictionary describing a client."""
client = {"name": name.strip().title(), "business": business.strip()}
if email:
client["email"] = email.strip().lower()
return client
print(build_client(" maria lopez ", "Bright Bakery"))
# {'name': 'Maria Lopez', 'business': 'Bright Bakery'}
print(build_client("carlos", "Calexico Coffee", "[email protected]"))
# {'name': 'Carlos', 'business': 'Calexico Coffee', 'email': '[email protected]'}
El valor predeterminado de cadena vacía para email lo vuelve opcional: la clave solo se agrega cuando se proporciona un correo.
Funciones y Listas: Cuidado con los Cambios
En la Parte 2 aprendiste que las listas y los diccionarios son mutables, y que dos nombres pueden apuntar a la misma lista. Lo mismo pasa cuando le pasas una lista a una función: la función recibe la lista original, no una copia. Si la función la cambia, el cambio se ve en todas partes:
def mark_all_done(tasks):
for i in range(len(tasks)):
tasks[i] = tasks[i] + " ✓"
my_tasks = ["Design", "Build", "Launch"]
mark_all_done(my_tasks)
print(my_tasks) # ['Design ✓', 'Build ✓', 'Launch ✓']
A veces eso es justo lo que quieres. Cuando no, haz que la función construya y devuelva una lista nueva, y deja la original sin cambios:
def with_checkmarks(tasks):
return [task + " ✓" for task in tasks]
my_tasks = ["Design", "Build", "Launch"]
done = with_checkmarks(my_tasks)
print(my_tasks) # ['Design', 'Build', 'Launch']
print(done) # ['Design ✓', 'Build ✓', 'Launch ✓']
Alcance de las Variables: Locales y Globales
Las variables creadas dentro de una función son locales: solo existen mientras la función se ejecuta, y el resto de tu programa no puede verlas:
def calculate_quote(pages):
hours = pages * 4 # variable local
return hours * 85
print(calculate_quote(5)) # 1700
print(hours) # NameError: name 'hours' is not defined
Esto es algo bueno. Significa que puedes usar nombres sencillos como total o count dentro de distintas funciones sin que se interfieran entre sí.
Las variables creadas fuera de cualquier función son globales, y las funciones pueden leerlas. Es la forma habitual de compartir constantes:
HOURLY_RATE = 85
def calculate_quote(pages):
return pages * 4 * HOURLY_RATE
print(calculate_quote(5)) # 1700
Leer una constante global está bien. Pero cambiar una variable global desde dentro de una función es un hábito que conviene evitar: hace que los programas sean difíciles de seguir, porque cualquier función podría estar cambiando datos compartidos sin que lo notes. Pasa los valores como argumentos y obtén los resultados con return.
Argumentos Flexibles: *args y **kwargs
A veces no sabes de antemano cuántos argumentos recibirá una función. Python tiene dos formas especiales de parámetros para eso.
*args reúne cualquier cantidad de argumentos posicionales adicionales en una tupla:
def total_cost(*prices):
"""Add up any number of prices."""
return sum(prices)
print(total_cost(19.99, 5.00)) # 24.99
print(total_cost(100, 250, 49, 12)) # 411
**kwargs reúne cualquier cantidad de argumentos con nombre adicionales en un diccionario:
def build_profile(name, **details):
"""Return a profile dictionary with any extra details provided."""
profile = {"name": name}
profile.update(details)
return profile
print(build_profile("Maria", city="El Centro", plan="Business"))
# {'name': 'Maria', 'city': 'El Centro', 'plan': 'Business'}
Los nombres args y kwargs son solo convenciones; lo que importa son el * y el **. Como principiante no los necesitarás seguido, pero los verás en el código de otras personas, así que conviene reconocerlos.
Funciones Lambda
En la Parte 3 usaste lambda para decirle a sorted() y a max() qué comparar. Ahora que conoces las funciones, esto es lo que realmente es: una lambda es una función pequeña y sin nombre escrita en una sola expresión. Estas dos son equivalentes:
def get_budget(project):
return project["budget"]
get_budget = lambda project: project["budget"]
Las lambdas son útiles cuando necesitas una función diminuta una sola vez, normalmente como argumento de otra función:
projects = [
{"client": "Bright Bakery", "budget": 2500},
{"client": "Calexico Coffee", "budget": 3200},
{"client": "Valley Dental", "budget": 300},
]
by_budget = sorted(projects, key=lambda p: p["budget"], reverse=True)
print([p["client"] for p in by_budget])
# ['Calexico Coffee', 'Bright Bakery', 'Valley Dental']
Para cualquier cosa que sea más que una expresión corta, escribe una función normal con def: es más fácil de leer y puede tener un docstring.
Organizar Funciones en Módulos
A medida que tu colección de funciones crece, puedes moverlas a su propio archivo, llamado módulo, e importarlas donde las necesites. Guarda estas funciones en un archivo llamado quote_tools.py:
# quote_tools.py
HOURLY_RATE = 85
def calculate_quote(pages, rush=False):
"""Return the price for a website with the given number of pages."""
total = pages * 4 * HOURLY_RATE
if rush:
total = total * 1.25
return total
def format_money(amount):
"""Return an amount formatted as dollars, like $1,700.00."""
return f"${amount:,.2f}"
Luego, en otro archivo de la misma carpeta, impórtalas y úsalas:
# main.py
import quote_tools
price = quote_tools.calculate_quote(5, rush=True)
print(quote_tools.format_money(price)) # $2,125.00
Hay varias formas de importar:
import quote_tools # se usa como quote_tools.calculate_quote()
from quote_tools import calculate_quote # se usa como calculate_quote()
from quote_tools import calculate_quote, format_money
import quote_tools as qt # se usa como qt.calculate_quote()
Así es exactamente como has usado los módulos integrados de Python, como json, copy y collections, y como usarás bibliotecas como pandas más adelante.
Todo Junto: Una Calculadora de Cotizaciones Reutilizable
En la Parte 5, la herramienta para recibir proyectos tenía varios ciclos while True que hacían casi lo mismo: hacer una pregunta, revisar la respuesta y volver a preguntar si estaba mal. Las funciones te permiten escribir esa lógica una sola vez y reutilizarla:
HOURLY_RATE = 85
HOURS_PER_PAGE = 4
RUSH_FEE = 0.25
def ask_text(prompt):
"""Ask until the user types something that isn't empty."""
while True:
answer = input(prompt).strip()
if answer:
return answer
print("This can't be empty.")
def ask_number(prompt, minimum=1):
"""Ask until the user types a whole number of at least `minimum`."""
while True:
answer = input(prompt).strip()
if answer.isdigit() and int(answer) >= minimum:
return int(answer)
print(f"Please enter a whole number of at least {minimum}.")
def ask_yes_no(prompt):
"""Ask a yes/no question and return True for yes, False for no."""
while True:
answer = input(f"{prompt} (yes/no) ").strip().lower()
if answer in ("yes", "y"):
return True
if answer in ("no", "n"):
return False
print("Please answer yes or no.")
def calculate_quote(pages, rush=False):
"""Return a dictionary with the hours, fee, and total for a project."""
hours = pages * HOURS_PER_PAGE
subtotal = hours * HOURLY_RATE
rush_fee = subtotal * RUSH_FEE if rush else 0
return {"hours": hours, "subtotal": subtotal, "rush_fee": rush_fee,
"total": subtotal + rush_fee}
def format_money(amount):
"""Return an amount formatted as dollars."""
return f"${amount:,.2f}"
def print_quote(business, quote):
"""Print a formatted quote."""
print(f"\nQuote for {business}")
print(f" Hours: {quote['hours']}")
print(f" Subtotal: {format_money(quote['subtotal'])}")
if quote["rush_fee"]:
print(f" Rush fee: {format_money(quote['rush_fee'])}")
print(f" Total: {format_money(quote['total'])}")
business = ask_text("Business name: ")
pages = ask_number("How many pages? ")
rush = ask_yes_no("Do you need it within 2 weeks?")
quote = calculate_quote(pages, rush)
print_quote(business, quote)
Una sesión de ejemplo:
Business name: Bright Bakery
How many pages? 0
Please enter a whole number of at least 1.
How many pages? 5
Do you need it within 2 weeks? (yes/no) y
Quote for Bright Bakery
Hours: 20
Subtotal: $1,700.00
Rush fee: $425.00
Total: $2,125.00
Mira las últimas cinco líneas del código: se leen casi como lenguaje natural. Cada función hace un solo trabajo y tiene un nombre que dice cuál es. Para hacer otra pregunta de sí o no, llamas otra vez a ask_yes_no() en lugar de escribir otro ciclo. Para cambiar cómo se muestra el dinero, editas format_money() en un solo lugar. Y como calculate_quote() devuelve un diccionario en lugar de imprimir, podrías reutilizarla en una página web, un correo o una exportación a hoja de cálculo sin cambiar una sola línea.
Errores Comunes de Principiantes
Olvidar los paréntesis al llamar. greet se refiere a la función en sí; greet() la ejecuta. print(greet) muestra algo como <function greet at 0x...> en lugar de llamarla.
Imprimir en lugar de devolver. Si una función imprime su resultado, no puedes usarlo en ningún otro lugar, y la llamada te da None. Devuelve el valor e imprímelo afuera.
Olvidar la sentencia return. Una función que calcula un valor pero nunca lo devuelve te entrega None. Si ves None donde esperabas un resultado, busca un return que falta.
Pasar los argumentos en el orden equivocado. Los argumentos posicionales se asignan a los parámetros según su posición. Usa argumentos con nombre cuando el orden no sea obvio.
Llamar una función antes de definirla. Python ejecuta los archivos de arriba hacia abajo, así que el def debe ir antes de la primera llamada, o tendrás un NameError.
Usar una lista o un diccionario como valor predeterminado. Los valores predeterminados se crean una sola vez, cuando se define la función, no cada vez que se llama. Un valor predeterminado mutable como [] se comparte entre llamadas:
def add_task(task, tasks=[]):
tasks.append(task)
return tasks
print(add_task("Design")) # ['Design']
print(add_task("Build")) # ['Design', 'Build'] ← ¡la primera tarea sigue ahí!
Usa None como valor predeterminado y crea una lista nueva dentro de la función:
def add_task(task, tasks=None):
if tasks is None:
tasks = []
tasks.append(task)
return tasks
print(add_task("Design")) # ['Design']
print(add_task("Build")) # ['Build']
Hacer que una función lo haga todo. Una función llamada process_everything() que hace preguntas, calcula, da formato e imprime es difícil de reutilizar y de corregir. Si te cuesta ponerle nombre a una función, probablemente hace demasiado: divídela en funciones más pequeñas que hagan un solo trabajo cada una.
Ejercicios de Práctica
- Escribe una función
celsius_to_fahrenheit(c)que devuelva la temperatura convertida (c * 9 / 5 + 32). Llámala con algunos valores e imprime los resultados. - Escribe una función
full_name(first, last, middle="")que devuelva un nombre completo con las mayúsculas correctas, incluyendo el segundo nombre solo cuando se proporcione. - Escribe una función
is_valid_email(email)que devuelvaTruesi el correo contiene una@y un.después de ella, yFalseen caso contrario. Úsala en un ciclowhileque pida un correo hasta que sea válido. - Escribe una función
average(numbers)que devuelva el promedio de una lista, o0si la lista está vacía. - Escribe una función
count_words(text)que devuelva un diccionario con cuántas veces aparece cada palabra. (Pista: combina.lower(),.split()y el patrón para contar de la Parte 3.) - Toma un programa que hayas escrito para un ejercicio anterior de esta serie y reorganízalo en funciones; luego muévelas a un módulo e impórtalo.
Para practicar más, los proyectos del Reto de 100 Días de Python se apoyan cada vez más en funciones a medida que crecen.
Conclusión
Las funciones son la forma de convertir un script largo en un programa organizado. Una función agrupa un trabajo detrás de un nombre claro. Los parámetros le permiten trabajar con datos distintos, los valores predeterminados hacen que algunas de esas entradas sean opcionales, y return devuelve el resultado para que el resto de tu programa lo pueda usar. El alcance local mantiene las variables de cada función para sí misma, y los módulos te permiten compartir funciones entre archivos.
Con las funciones ya tienes todos los bloques básicos de la Parte 1: variables y tipos de datos, listas y diccionarios, decisiones con if, repetición con ciclos, entrada del usuario y ahora funciones reutilizables. Juntos bastan para construir programas reales que resuelven problemas reales. Elige una tarea pequeña y molesta de tu propio trabajo y escribe un programa que se encargue de ella.
Siguiente en la serie Fundamentos de Python: Clases, cómo agrupar los datos y las funciones que trabajan con ellos en tus propios tipos.
Serie Fundamentos de Python: Parte 1: Lo que Todo Principiante Debería Saber · Parte 2: Variables y Tipos de Datos · Parte 3: Listas y Diccionarios · Parte 4: La Sentencia If · Parte 5: Entrada del Usuario y Ciclos While · Parte 6: Funciones · Parte 7: Clases · Parte 8: Archivos y Excepciones · Parte 9: Cómo Probar tu Código
