# Funciones en Python: def, return, argumentos, *args y **kwargs, lambda y anotaciones de tipos

URL: https://skillaria.top/es/guides/python/funciones-python

Una función en Python es un bloque de código con nombre que se declara con def, se llama por su nombre con paréntesis y devuelve un resultado con return; si no hay return, devuelve None. Los argumentos se pasan por posición o por nombre, *args reúne los valores posicionales sobrantes en una tupla, **kwargs reúne los argumentos con nombre en un diccionario, y lambda crea una función anónima corta de una sola expresión.

Una función en Python es un fragmento de código con nombre que puede llamarse tantas veces como haga falta con datos de entrada distintos. Se declara con la palabra `def`, recibe valores mediante parámetros y devuelve el resultado con `return`. Las funciones son la forma principal de convertir un script largo en pasos claros y comprobables.

## Cómo declarar una función: def en Python

La declaración de una función empieza con `def`, seguido del nombre, los parámetros entre paréntesis y dos puntos. El cuerpo se escribe con una sangría de cuatro espacios: en Python la sangría no es decoración, forma parte de la sintaxis.

```
def saludar(nombre):
    """Devuelve un saludo para el usuario."""
    return f"¡Hola, {nombre}!"

print(saludar("Ana"))   # ¡Hola, Ana!
```

La cadena entre comillas triples justo después del encabezado es el docstring, la documentación de la función. La muestra `help(saludar)` y los consejos del editor. Según la convención PEP 8, los nombres de las funciones se escriben en snake_case: `añadir_gasto`, `obtener_tasa`.

La línea `def saludar(nombre):` se llama firma de la función: el nombre más la lista de parámetros (y las anotaciones, si las hay). Con la firma, quien lee el código entiende cómo llamar a la función sin mirar su cuerpo.

## Qué hace return en Python

`return` termina la función y entrega el valor a quien la llamó. El código que hay después de un `return` ejecutado no se ejecuta. Si no hay `return`, o si aparece sin valor, la función devuelve `None`.

```
def area(a, b):
    a * b          # el resultado se calcula y se descarta

print(area(3, 4))  # None

def area(a, b):
    return a * b

print(area(3, 4))  # 12
```

Olvidar el `return` es uno de los errores más frecuentes de los principiantes: la función «funciona», pero devuelve `None` y el fallo aparece una línea más abajo. Se pueden devolver varios valores; en realidad es una sola tupla, que es cómodo desempaquetar:

```
def minimo_maximo(numeros):
    return min(numeros), max(numeros)

bajo, alto = minimo_maximo([4, 1, 9])
print(bajo, alto)   # 1 9
```

Un `return` anticipado ayuda a evitar anidamientos innecesarios: primero se descartan los casos especiales y después se escribe la lógica principal.

```
def descuento(precio, es_socio):
    if precio  limite     # leer una global: correcto

def subir_limite():
    global limite
    limite = 2000             # sin global se crearía una variable local
```

En la práctica conviene evitar `global`: una función que cambia el estado externo es difícil de probar. Es mejor recibir el valor como argumento y devolver el resultado.

## Funciones anidadas y clausuras

Una función puede declararse dentro de otra. La función interna ve las variables de la externa incluso después de que esta haya terminado; a eso se le llama clausura. Para modificar una variable de ese tipo se usa `nonlocal`.

```
def crear_contador():
    cuenta = 0
    def contador():
        nonlocal cuenta
        cuenta += 1
        return cuenta
    return contador

c = crear_contador()
print(c(), c(), c())   # 1 2 3
```

Las clausuras están en la base de los decoradores y de las fábricas de funciones.

## La función también es un objeto

En Python una función es un objeto normal: se puede guardar en una variable, pasar a otra función, devolver desde una función o almacenar en un diccionario. Tiene atributos como `__name__`, `__doc__`, `__defaults__` y `__annotations__`.

```
def doble(x):
    return x * 2

f = doble
print(f(5), f.__name__)                # 10 doble
print(list(map(doble, [1, 2, 3])))    # [2, 4, 6]
```

## Lambda en Python: funciones anónimas

Una función lambda es una función corta sin nombre cuyo cuerpo es una sola expresión. El resultado de la expresión se devuelve automáticamente; no se escribe `return`.

```
cuadrado = lambda x: x ** 2
print(cuadrado(4))   # 16

gastos = [("Café", 250), ("Taxi", 600), ("Almuerzo", 450)]
gastos.sort(key=lambda g: g[1], reverse=True)
print(gastos)  # [('Taxi', 600), ('Almuerzo', 450), ('Café', 250)]

print(list(filter(lambda n: n % 2 == 0, range(10))))  # [0, 2, 4, 6, 8]
```

Las lambdas son útiles como argumento corto para `sorted`, `max`, `map` o `filter`. Asignar una lambda a una variable, como `cuadrado` arriba, no lo recomienda PEP 8: si una función necesita nombre, escríbala con `def`; así tendrá un `__name__` claro en el traceback y un lugar para el docstring.

|  | def | lambda |
| --- | --- | --- |

| Nombre | sí | no (`<lambda>`) |

| Cuerpo | cualquier número de instrucciones | una sola expresión |

| return | explícito | implícito |

| Docstring, anotaciones | sí | no |

| Cuándo conviene | siempre | un key o un callback corto |

## Anotaciones de tipos: pistas, no comprobaciones

Las anotaciones describen los tipos esperados de los parámetros y del resultado. Python no las comprueba al ejecutar: una función con `x: int` aceptará sin problemas una cadena. Las comprueban herramientas externas, como mypy, pyright o ty, y el editor, que señala el error antes de ejecutar nada.

```
def añadir_gasto(concepto: str, importe: float, categoria: str = "otros") -> dict[str, object]:
    return {"concepto": concepto, "importe": importe, "categoria": categoria}

def area(a: float, b: float) -> float:
    a * b   # mypy: Missing return statement
```

La comprobación se puede lanzar sin instalar nada con `uvx mypy .`. El ejemplo anterior muestra la utilidad de las anotaciones: un `return` olvidado con `-> float` lo detecta el analizador de inmediato.

Desde Python 3.12 (PEP 695), las funciones genéricas se escriben sin `TypeVar`: el parámetro de tipo va entre corchetes y un alias de tipo se declara con la palabra `type`.

```
def primero[T](xs: list[T]) -> T:
    return xs[0]

type Dinero = int | float
```

En Python 3.14 (PEP 649), las anotaciones pasaron a ser perezosas: se evalúan en el primer acceso, así que se puede hacer referencia a una clase declarada más abajo sin comillas, y `from __future__ import annotations` ya no hace falta.

## «No se pasó» o «se pasó None»: sentinela en 3.15

El valor por defecto `None` no permite distinguir entre «no se pasó el argumento» y «se pasó None a propósito». Durante años se resolvió con un marcador hecho a mano:

```
_AUSENTE = object()

def obtener_tasa(moneda, por_defecto=_AUSENTE):
    tasas = {"USD": 90.5}
    if moneda in tasas:
        return tasas[moneda]
    if por_defecto is _AUSENTE:
        raise KeyError(moneda)
    return por_defecto
```

En Python 3.15 apareció la función integrada `sentinel` (PEP 661): `AUSENTE = sentinel("AUSENTE")` crea un marcador único con una representación clara, que al copiarse sigue siendo el mismo, se serializa con pickle y puede usarse en una anotación como `float | AUSENTE`. En versiones anteriores sigue funcionando el recurso con `object()`.

## Errores típicos con funciones

- **Olvidar return**: la función devuelve `None`.
- **Olvidar los paréntesis**: `print(saludar)` muestra el objeto función, no el resultado de la llamada.
- **Valor por defecto mutable**: una lista compartida por todas las llamadas.
- **Número incorrecto de argumentos**: `TypeError: saludar() missing 1 required positional argument: 'nombre'`.
- **Nombre que coincide con uno integrado**: una función llamada `list` o `sum` oculta la función integrada.

## Resumen

Las funciones en Python se declaran con `def`, devuelven el resultado con `return` y reciben argumentos por posición o por nombre. `*args` y `**kwargs` aportan flexibilidad, `/` y `*` dan rigor a las llamadas, lambda sirve para funciones de un solo uso y cortas, y las anotaciones de tipos junto con mypy detectan errores antes de ejecutar el programa. En el curso de Python este tema se practica con un registro de gastos dividido en funciones con anotaciones que se comprueban con mypy.

## FAQ

**¿En qué se diferencia un parámetro de un argumento?**

El parámetro es el nombre que aparece en la declaración de la función; el argumento es el valor concreto que se pasa al llamarla. En def saludar(nombre), nombre es un parámetro; en saludar("Ana"), la cadena "Ana" es un argumento.

**¿Se puede llamar a una función antes de declararla?**

A nivel de módulo, no: la línea def debe ejecutarse antes de la llamada, o aparecerá NameError. Dentro de otra función sí se puede llamar a una función declarada más abajo, siempre que la llamada ocurra después de su declaración.

**¿Cuántos valores puede devolver una función?**

Formalmente, uno, pero ese valor puede ser una tupla: return a, b devuelve una tupla de dos elementos, que se desempaqueta como x, y = f().

**¿Son obligatorias las anotaciones de tipos?**

No; Python funciona sin ellas. Pero en el código que leen otras personas, las anotaciones facilitan el mantenimiento y permiten que mypy o pyright detecten errores antes de ejecutar.

**¿Se puede usar if dentro de una lambda?**

Solo una expresión condicional: lambda x: "par" if x % 2 == 0 else "impar". Las instrucciones, los bucles y las asignaciones no se permiten dentro de una lambda.


---
Source: Skillaria.Top (https://skillaria.top)
