Funciones en Python: def, return y objetos de primera clase

Cuando escribes def, no estás declarando una función como lo harías en un lenguaje con tipos estáticos. Estás ejecutando una instrucción que crea un objeto de tipo función y lo enlaza a un nombre en el espacio de nombres actual. Eso no es un detalle menor: es la razón por la que en Python las funciones se comportan como cualquier otro valor.

Vamos a ver qué significa eso en la práctica antes de tocar código.

El mecanismo detrás de def

def es una instrucción ejecutable. Cuando Python la encuentra, construye un objeto función en memoria —con su código, sus valores por defecto, su docstring— y asigna ese objeto al nombre que escribiste. Nada más. No hay declaración previa de tipos de retorno, no hay firma en un header, solo un objeto recién creado viviendo en el namespace.

El cuerpo de la función no se ejecuta en ese momento. Se ejecuta cada vez que llamas al objeto con ().

El valor de retorno lo controla return. Si llegas al final del cuerpo sin ejecutar ningún return, Python devuelve None de forma implícita. Siempre. No hay error, no hay valor indefinido: es None, el objeto que Python usa para representar “ausencia de valor”.

Cuando escribes return a, b, no estás devolviendo dos cosas: estás devolviendo un solo objeto, una tuple creada implícitamente por la coma. El que la reciba puede desempaquetarla o no.

Y como una función es un objeto, puedes asignarla a otra variable, meterla en una lista, pasarla como argumento. No necesitas sintaxis especial: es la consecuencia natural de que todo en Python es un objeto.

# functions_basics.py

def add(x, y):
    """Returns the sum of x and y."""
    return x + y


def greet(name):
    """Prints a greeting. No explicit return."""
    print(f"Hello, {name}!")
    # Implicitly returns None here


def min_max(numbers):
    """Returns both the minimum and maximum of a list."""
    return min(numbers), max(numbers)   # packs into a tuple automatically


def apply(func, value):
    """Calls func with value and returns the result."""
    return func(value)


# ── Funciones como objetos ────────────────────────────────────────────

# Asignar la función a otra variable: solo copiamos la referencia al objeto.
# 'add' y 'also_add' apuntan al mismo objeto función.
also_add = add
print(also_add(3, 4))        # 7

# Guardar funciones en una estructura de datos
operations = [add, also_add]
print(operations[0](10, 5))  # 15

# Pasar una función como argumento
import math
print(apply(math.sqrt, 16))  # 4.0

# ── Return implícito y explícito ──────────────────────────────────────

result = greet("Ana")
print(result)                # None  ← greet no tiene return

lo, hi = min_max([3, 1, 4, 1, 5, 9])
print(lo, hi)                # 1 9

# min_max devuelve una sola tuple; desempaquetar es opcional
both = min_max([3, 1, 4, 1, 5, 9])
print(type(both), both)      # <class 'tuple'> (1, 9)

# ── def crea el objeto en el namespace actual ─────────────────────────

print(type(add))             # <class 'function'>
print(add.__name__)          # add
print(add.__doc__)           # Returns the sum of x and y.

Lo que importa de cada decisión

add y greet ilustran los dos extremos: una devuelve un valor calculado, la otra produce un efecto (imprimir) y no necesita retornar nada útil. Asignar greet() a una variable y obtener None no es un fallo; es Python siendo honesto contigo sobre lo que la función produce.

min_max muestra que return min(numbers), max(numbers) es azúcar sintáctica para return (min(numbers), max(numbers)). La coma crea la tuple, no los paréntesis. Cuando haces lo, hi = min_max(...) estás desempaquetando esa tuple en dos nombres, lo que es idiomático y limpio. Si no desempaquetas, simplemente recibes la tuple entera, como muestra la variable both.

apply es el patrón más importante para entender las funciones como objetos de primera clase. El parámetro func no tiene ningún tipo especial: acepta cualquier objeto llamable. Por eso apply(math.sqrt, 16) funciona igual que apply(add, 5) funcionaría si ajustaras los argumentos. Este patrón es la base de map, filter, sorted(key=...) y cualquier callback que uses en Python.

La línea also_add = add no copia el código de la función: copia la referencia al objeto. Ambos nombres apuntan al mismo objeto en memoria. Puedes comprobarlo con also_add is add, que devuelve True.

Los atributos __name__ y __doc__ existen porque las funciones son objetos con estado. Python los establece cuando ejecuta def. Son útiles para depuración, logging y herramientas como help().

Errores que debes conocer

Error: usar el valor de retorno de una función que no tiene return, olvidando que el resultado será siempre None.

# ❌ Wrong
def double(x):
    x * 2   # se calcula pero no se retorna

result = double(5)
print(result + 1)   # TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

# ✅ Right
def double(x):
    return x * 2

result = double(5)
print(result + 1)   # 11

Olvidar return es silencioso: la función no lanza ningún error. El problema explota después, cuando intentas usar el None que recibiste.


Error: llamar a la función cuando quieres pasar el objeto función, o al revés.

# ❌ Wrong
# Esto ejecuta add(3,4) y guarda 7 en operations, no la función
operations = [add(3, 4)]

# ✅ Right
# Guardamos el objeto función para llamarlo después
operations = [add]
print(operations[0](3, 4))   # 7

Los paréntesis son el operador de llamada. Sin ellos, add es solo el objeto; con ellos, add(3, 4) lo ejecuta y produce el resultado.

53

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio