Saltar al contenido
funciones.py · devschool

Funciones en Python

Lección 11 de 19 · 9 min de lectura · Actualizado el

En esta lección
  1. Definir y llamar a una función
  2. Parámetros y argumentos
  3. return: devolver un resultado
  4. Argumentos posicionales y con nombre
  5. Valores por defecto
  6. Devolver varios valores
  7. Ámbito: variables dentro y fuera
  8. Documenta tus funciones: docstrings
  9. Type hints: indicar los tipos
  10. Cómo diseñar buenas funciones
  11. Funciones lambda
  12. Errores frecuentes
  13. Resumen

Una función agrupa código con un nombre para reutilizarlo. En lugar de copiar las mismas cinco líneas en diez sitios, las escribes una vez y las llamas cuando las necesites. Si mañana hay que cambiarlas, las cambias en un único lugar. En esta lección aprenderás a definir funciones, pasarles datos, devolver resultados, documentarlas y, sobre todo, a diseñarlas bien.

Definir y llamar a una función

Las funciones se definen con def:

def saludar(nombre):
    print(f"¡Hola, {nombre}!")

saludar("Ana")
saludar("Luis")
# ¡Hola, Ana!
# ¡Hola, Luis!
  • def, el nombre, los parámetros entre paréntesis y dos puntos.
  • El cuerpo va con sangría.
  • Los nombres de funciones van en snake_case y suelen ser verbos: calcular_total, enviar_correo, es_par.

Definir una función no la ejecuta: solo le da un nombre al código. Se ejecuta cada vez que la llamas escribiendo su nombre con paréntesis. Por eso la definición tiene que aparecer antes de la primera llamada; si no, obtendrás NameError.

Una función puede no tener parámetros, pero los paréntesis son obligatorios:

def mostrar_menu():
    print("1) Nueva partida")
    print("2) Salir")

mostrar_menu()
# 1) Nueva partida
# 2) Salir

Parámetros y argumentos

Dos palabras que se confunden a menudo:

  • Los parámetros son los nombres que escribes en la definición: nombre en def saludar(nombre).
  • Los argumentos son los valores concretos que pasas al llamarla: "Ana" en saludar("Ana").

Al llamar a la función, cada parámetro toma el valor de su argumento, como si hicieras nombre = "Ana" al principio del cuerpo. Si pasas menos o más argumentos de los que espera, obtienes un error:

saludar()
# TypeError: saludar() missing 1 required positional argument: 'nombre'

return: devolver un resultado

Para que la función devuelva un resultado en lugar de mostrarlo, usa return:

def area_rectangulo(base, altura):
    return base * altura

area = area_rectangulo(5, 3)
print(area * 2)
# 30

¿Por qué devolver en lugar de imprimir? Porque un valor devuelto se puede reutilizar: guardarlo, operar con él, pasarlo a otra función o mostrarlo con el formato que quieras. Un print dentro de la función solo sirve para mirar.

return termina la función en ese momento. Esto permite salir pronto:

def clasificar_nota(nota):
    if nota < 0 or nota > 10:
        return "Nota no válida"
    if nota >= 5:
        return "Aprobado"
    return "Suspenso"

print(clasificar_nota(7))
print(clasificar_nota(12))
# Aprobado
# Nota no válida

Una función sin return (o con return solo) devuelve None:

def doble(n):
    n * 2          # calcula, pero no devuelve nada

print(doble(4))
# None

Es un error muy común: si la función debe dar un resultado, comprueba que lleva return.

Argumentos posicionales y con nombre

Al llamar a una función puedes pasar los argumentos de dos formas:

  • Posicionales: por orden. El primero va al primer parámetro, el segundo al segundo…
  • Con nombre (keyword arguments): indicando a qué parámetro va cada valor. Así el orden da igual y el código se lee mejor.
def crear_usuario(nombre, edad, ciudad):
    return f"{nombre} ({edad}) - {ciudad}"

print(crear_usuario("Eva", 30, "Santander"))                  # posicionales
print(crear_usuario(ciudad="Santander", nombre="Eva", edad=30))  # con nombre
print(crear_usuario("Eva", ciudad="Santander", edad=30))      # mezcla
# Eva (30) - Santander
# Eva (30) - Santander
# Eva (30) - Santander

Al mezclar, los posicionales van siempre primero. crear_usuario(nombre="Eva", 30, "Santander") da SyntaxError.

Los argumentos con nombre son especialmente útiles cuando hay valores sueltos que no se entienden solos: enviar(correo, True, False) no dice nada; enviar(correo, urgente=True, copia=False) sí.

Valores por defecto

Un parámetro puede tener un valor que se usa si no se pasa el argumento:

def precio_final(precio, iva=0.21):
    return precio * (1 + iva)

print(precio_final(100))         # 121.0
print(precio_final(100, 0.10))   # 110.00000000000001
print(precio_final(100, iva=0))  # 100

(El 110.00000000000001 es el efecto de la coma flotante que se explica en Operadores; al mostrarlo usarías f"{valor:.2f}".)

Los parámetros con valor por defecto van siempre después de los que no lo tienen: def f(iva=0.21, precio) da SyntaxError.

Cuidado: no uses una lista o un diccionario como valor por defecto (def agregar(item, lista=[])). Ese objeto se crea una sola vez, al definir la función, y se comparte entre todas las llamadas. Usa None y créalo dentro:

def agregar(item, lista=None):
    if lista is None:
        lista = []
    lista.append(item)
    return lista

Devolver varios valores

def estadisticas(numeros):
    return min(numeros), max(numeros), sum(numeros) / len(numeros)

minimo, maximo, media = estadisticas([4, 8, 6, 10])
print(minimo, maximo, media)
# 4 10 7.0

En realidad devuelve una tupla, que se desempaqueta entre las tres variables. Lo tienes explicado en Tuplas y conjuntos. Si devuelves muchos valores (cuatro o más), plantéate devolver un diccionario: resultado["media"] se entiende mejor que resultado[2].

Ámbito: variables dentro y fuera

Las variables creadas dentro de una función solo existen dentro (son locales):

def calcular():
    resultado = 42

calcular()
print(resultado)
# NameError: name 'resultado' is not defined

Cada llamada tiene sus propias variables locales, que desaparecen al terminar. La forma correcta de sacar un valor de una función es con return, no con variables globales. El ámbito, global y nonlocal se explican a fondo en *args, **kwargs, lambda y ámbito.

Documenta tus funciones: docstrings

La primera línea del cuerpo puede ser un texto entre triples comillas que explica qué hace la función. Se llama docstring y los editores lo muestran al pasar el ratón sobre la función:

def es_par(numero):
    """Devuelve True si el número es par y False si es impar."""
    return numero % 2 == 0

Para funciones más complejas, el docstring puede ocupar varias líneas y describir los parámetros y lo que devuelve:

def calcular_descuento(precio, porcentaje):
    """Calcula el precio tras aplicar un descuento.

    precio: precio original en euros.
    porcentaje: descuento entre 0 y 100.
    Devuelve el precio final redondeado a 2 decimales.
    """
    return round(precio * (1 - porcentaje / 100), 2)

print(calcular_descuento(80, 15))
# 68.0

Puedes leer el docstring de cualquier función con help(calcular_descuento) o calcular_descuento.__doc__.

Type hints: indicar los tipos

Python no obliga a declarar tipos, pero puedes anotarlos para que quede claro qué espera y qué devuelve la función:

def calcular_media(notas: list[float]) -> float:
    return sum(notas) / len(notas)

def saludar(nombre: str, veces: int = 1) -> None:
    for _ in range(veces):
        print(f"Hola, {nombre}")

print(calcular_media([7.5, 8, 6]))
saludar("Eva", veces=2)
# 7.166666666666667
# Hola, Eva
# Hola, Eva
  • : tipo tras cada parámetro y -> tipo antes de los dos puntos para el valor devuelto.
  • -> None indica que la función no devuelve nada.

Las anotaciones no se comprueban al ejecutar: calcular_media("hola") no daría error por el tipo, sino por lo que pase dentro. Entonces, ¿para qué sirven? Documentan el código y permiten que el editor (VS Code, PyCharm) o herramientas como mypy te avisen de errores antes de ejecutar.

Cómo diseñar buenas funciones

Escribir funciones es fácil; escribir funciones buenas requiere algunas reglas:

  1. Una sola tarea. Si para describirla necesitas la palabra “y” (“lee el fichero y calcula la media y la imprime”), probablemente son tres funciones.
  2. Nombre descriptivo. calcular_iva(precio) se entiende sin leer el código; proc(p) no.
  3. Recibe los datos por parámetros y devuelve el resultado. Evita que dependa de variables globales o que imprima en lugar de devolver: así podrás reutilizarla y probarla.
  4. Pocas líneas y pocos parámetros. Si pasa de unas 20-30 líneas o de 4-5 parámetros, plantéate dividirla.
  5. Sin efectos sorpresa. Si una función se llama calcular_total, no debería también vaciar el carrito.

Compara estas dos versiones:

# Mal: mezcla cálculo y presentación, y depende de una global
carrito = [12.5, 3.2, 40]
def total():
    suma = 0
    for p in carrito:
        suma += p
    print("Total:", suma)

# Bien: recibe los datos, calcula y devuelve
def calcular_total(precios: list[float]) -> float:
    """Suma los precios de una lista."""
    return sum(precios)

print(f"Total: {calcular_total(carrito):.2f} €")
# Total: 55.70 €

La segunda sirve para cualquier lista, se puede usar dentro de otros cálculos y muestra el resultado con el formato que prefiera quien la llama.

Funciones lambda

Python también permite funciones muy cortas y sin nombre, las lambda, que se usan sobre todo como argumento de sorted, max o min: sorted(alumnos, key=lambda a: a["nota"]). Las verás, junto con *args y **kwargs, en Funciones avanzadas.

Errores frecuentes

  • Olvidar el return: la función devuelve None y el resto del programa falla después.
  • Llamar sin paréntesis: mostrar_menu (sin ()) no ejecuta nada; solo nombra la función.
  • Número incorrecto de argumentos: TypeError: ... missing 1 required positional argument.
  • Posicionales después de con nombre: f(a=1, 2) es SyntaxError.
  • Parámetro por defecto antes de uno obligatorio: def f(a=1, b) es SyntaxError.
  • Lista como valor por defecto: se comparte entre llamadas. Usa None.
  • Usar dentro variables creadas en otra función: NameError. Pásalas como argumento.

Resumen

ConceptoEjemplo
Definirdef calcular_total(precios):
Llamarcalcular_total([1, 2])
Devolverreturn resultado (sin return → None)
Valor por defectodef precio_final(precio, iva=0.21):
Argumento con nombreprecio_final(100, iva=0.10)
Varios valoresreturn a, b y x, y = f()
Docstring"""Qué hace la función."""
Type hintsdef media(notas: list[float]) -> float:
ÁmbitoLas variables locales solo existen dentro

Sigue con Funciones avanzadas para aprender *args, **kwargs, lambda y el ámbito de las variables.

Pon a prueba lo que has aprendido

[Python] ¿Qué muestra este código?
def doble(n):
    n * 2

print(doble(4))

[Python] ¿Qué muestra este código?
def agregar(item, lista=[]):
    lista.append(item)
    return lista

agregar("a")
print(agregar("b"))

[Python] ¿Qué muestra este código?
def precio_final(precio, iva=0.21):
    return round(precio * (1 + iva), 2)

print(precio_final(100, iva=0.10), precio_final(iva=0, precio=50))

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.