Skip to content
This page has been auto-translated and may contain errors.View in English

Funciones

A medida que tus programas crecen, escribirás la misma lógica en más de un lugar. Las funciones te permiten escribir la lógica una vez, ponerle un nombre y usarla en todas partes. Arréglala en un solo lugar y cada llamada recibe la corrección automáticamente.

Las funciones son la unidad principal de reutilización y abstracción de código. Encapsulan un comportamiento, le dan un nombre, definen una interfaz clara (parámetros y valor de retorno) y la hacen invocable desde cualquier lugar. Las funciones con nombres bien elegidos también sirven como documentación: validate_email() te dice lo que hace un bloque de código sin necesidad de leerlo.

En Python, def crea un objeto función y lo enlaza a un nombre en el alcance actual. Las funciones son objetos de primera clase: pueden asignarse a variables, almacenarse en colecciones, pasarse como argumentos y retornarse desde otras funciones. Las clausuras capturan variables libres del alcance que las contiene. Entender esto hace que la programación de orden superior sea natural.

python
def greet(name):
    return f"Hello, {name}!"

print(greet("Sofía"))   # "Hello, Sofía!"
print(greet("Mateo"))   # "Hello, Mateo!"

Escríbela una vez, úsala en todas partes, arréglala en un solo lugar.

Definir una función

La palabra clave def inicia una definición de función, seguida por el nombre, paréntesis, dos puntos y un cuerpo indentado. Una función no hace nada hasta que la llamas. Defínela con def, luego llámala por su nombre con ().

def es una sentencia que crea un objeto función y lo enlaza al nombre dado en el alcance actual. El cuerpo no se ejecuta en el momento de la definición; se ejecuta solo cuando se llama a la función. Las funciones sin una sentencia return retornan implícitamente None.

def es una sentencia compuesta que compila el cuerpo de la función en un objeto de código y enlaza un nuevo objeto función al nombre en el espacio de nombres actual. El objeto función almacena una referencia a su objeto de código, sus valores de argumentos por defecto y una referencia al alcance que lo contiene (para clausuras). El cuerpo se ejecuta solo al invocarse.

python
def say_hello():
    print("Hello!")

say_hello()   # llamar a la función

Parámetros y argumentos

Los parámetros son las entradas que tu función espera. Lístalos dentro de los paréntesis. Cuando llamas a la función, los valores que pasas se asocian a los parámetros en orden.

Los parámetros definen la interfaz de una función. Los argumentos son los valores concretos que se pasan al momento de la llamada. Los argumentos posicionales se asocian por posición; los argumentos con nombre (keyword) se asocian por nombre. Los valores por defecto hacen que los parámetros sean opcionales.

Python tiene cuatro tipos de parámetros: posicional-o-con-nombre (el predeterminado), solo-con-nombre (después de *), solo-posicional (antes de /, Python 3.8+) y variádico (*args, **kwargs). Al momento de la llamada, los argumentos posicionales se enlazan de izquierda a derecha; los argumentos con nombre se enlazan por nombre. Los conflictos generan TypeError. Los valores por defecto se evalúan una vez al momento de definir la función, no en cada llamada.

python
def greet(name, greeting):
    print(f"{greeting}, {name}!")

greet("Sofía", "Hello")    # "Hello, Sofía!"
greet("Mateo", "Hi")       # "Hi, Mateo!"

Parámetros vs argumentos

Parámetro es el nombre en la definición de la función. Argumento es el valor real que pasas cuando llamas a la función. En la práctica, las personas usan estas palabras indistintamente. Solo ten en cuenta la distinción al leer documentación.

Valores por defecto

Puedes darles a los parámetros un valor por defecto. Si quien llama no proporciona ese argumento, se usa el valor por defecto. Los parámetros con valores por defecto deben ir después de los parámetros sin valores por defecto.

Los valores por defecto hacen que los parámetros sean opcionales. Se evalúan una vez al momento de la definición, no en cada llamada. Esto importa para los valores por defecto mutables: def f(items=[]) comparte la misma lista entre todas las llamadas. La solución es usar None como valor por defecto y crear la lista dentro del cuerpo de la función.

Los valores por defecto se almacenan en el objeto función como f.__defaults__ (posicionales) y f.__kwdefaults__ (solo-con-nombre). Se evalúan una vez cuando se ejecuta def, no por llamada. La trampa del valor por defecto mutable (def f(x=[])) es un clásico tropiezo en Python: la lista se crea una vez y se muta en su lugar a través de las llamadas. La solución idiomática: def f(x=None): if x is None: x = [].

python
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Sofía")           # "Hello, Sofía!"
greet("Sofía", "Hi")     # "Hi, Sofía!"

Los parámetros con valores por defecto deben ir después de los parámetros sin valores por defecto.

Argumentos con nombre

Al llamar a una función, puedes nombrar los argumentos. Esto hace que las llamadas sean legibles, especialmente para funciones con muchos parámetros, y te permite pasarlos en cualquier orden.

Los argumentos con nombre hacen que las llamadas a funciones sean autodescriptivas. Puedes mezclar posicionales y con nombre: los argumentos posicionales deben ir primero. Para funciones con banderas booleanas o muchos parámetros de tipos similares, los argumentos con nombre previenen errores silenciosos por pasar argumentos en el orden equivocado.

Los argumentos con nombre se enlazan por nombre, no por posición. Los argumentos posicionales deben preceder a los argumentos con nombre al momento de la llamada. Pasar el mismo argumento tanto posicionalmente como por nombre genera TypeError. Para forzar argumentos solo-con-nombre, colócalos después de un * solitario en la lista de parámetros: def f(a, *, b) hace que b sea solo-con-nombre.

python
def describe_player(name, score, level):
    print(f"{name} | Score: {score} | Level: {level}")

describe_player("Sofía", 87, 5)                        # posicional
describe_player(name="Sofía", level=5, score=87)       # con nombre, cualquier orden
describe_player("Sofía", level=5, score=87)            # mezcla: posicional primero

Valores de retorno

return envía un valor de vuelta a quien hizo la llamada. Sin return, una función devuelve None. Una vez que return se ejecuta, la función sale inmediatamente. Cualquier código después de él en ese bloque se omite.

return sale de la función y pasa un valor a quien la llamó. Una función sin un return explícito retorna implícitamente None. return puede aparecer en cualquier parte del cuerpo de la función y puede usarse múltiples veces; el primero alcanzado termina la función. Esto hace útiles los retornos tempranos para cláusulas de guarda.

return emite un bytecode RETURN_VALUE que extrae la cima de la pila y la entrega al marco de quien llama. Una función que llega al final retorna implícitamente None. Múltiples sentencias return están bien y a menudo son más limpias que un único retorno con condiciones complejas (el patrón de "retorno temprano"). return dentro de un bloque try aún ejecuta cualquier cláusula finally asociada.

python
def add(a, b):
    return a + b

result = add(3, 4)   # result = 7
print(result)

return también sale de la función inmediatamente. Cualquier código después de él en ese bloque no se ejecuta.

Retornar múltiples valores

Python te permite retornar múltiples valores separándolos con comas. Quien llama los recibe como una tupla y puede desempacarlos en nombres separados en una sola línea.

Retornar múltiples valores con una coma los empaqueta en una tupla. Quien llama los desempaca con nombres correspondientes. Esto es Python idiomático para funciones que naturalmente producen más de un resultado. No es una característica especial; es empaquetado y desempaquetado de tuplas.

return a, b empaqueta los valores en una tupla mediante empaquetado implícito. Quien llama desempaca con x, y = f(), lo cual invoca __iter__ sobre la tupla retornada. Para claridad en las anotaciones de tipo, anota el tipo de retorno como tuple[int, str] o usa una tupla con nombre. Retornar una tupla simple está bien para pocas cantidades; para más de dos o tres valores, considera una tupla con nombre o una dataclass.

python
def min_max(numbers):
    return min(numbers), max(numbers)

low, high = min_max([3, 7, 1, 9, 4])
print(low, high)   # 1 9

La sintaxis low, high = ... es desempaquetado: Python asigna cada valor retornado al nombre correspondiente.

Alcance

Las variables creadas dentro de una función existen solo dentro de esa función. No puedes verlas desde afuera. Las variables definidas fuera de todas las funciones son visibles en todas partes, pero no puedes modificarlas desde dentro de una función sin una declaración explícita.

Python tiene alcance local (dentro de una función), alcance que la contiene (en una función externa para funciones anidadas), alcance global (el nivel del módulo) y alcance integrado (built-in). La búsqueda de nombres sigue la regla LEGB en ese orden. Las variables locales ocultan a las externas con el mismo nombre. global declara que un nombre se refiere al enlace a nivel de módulo.

La resolución de nombres LEGB de Python: Local, Enclosing (clausura), Global (módulo), Built-in. Cada def crea un nuevo espacio de nombres local. Leer una variable global funciona automáticamente; escribirla requiere global x para evitar crear una sombra local. nonlocal x accede al alcance no global más cercano que contiene, permitiendo que las clausuras muten variables capturadas. Abusar de global hace difícil razonar sobre el código; prefiere parámetros y valores de retorno.

python
def calculate():
    result = 42   # local a esta función
    return result

calculate()
print(result)   # NameError, result no existe acá afuera
python
count = 0

def increment():
    global count    # declara que quieres modificar la global
    count += 1

increment()
print(count)   # 1

Usar global debería ser un último recurso. Hace que el código sea más difícil de razonar. Prefiere pasar valores como entrada y retornarlos como salida.

*args y **kwargs

A veces no sabes cuántos argumentos recibirá una función. *args recolecta cualquier número de argumentos posicionales en una tupla. **kwargs recolecta cualquier número de argumentos con nombre en un diccionario. Los nombres args y kwargs son convenciones; lo importante son los asteriscos.

*args recolecta los argumentos posicionales sobrantes en una tupla. **kwargs recolecta los argumentos con nombre sobrantes en un dict. Ambos pueden combinarse con parámetros regulares. Los parámetros regulares van primero, luego *args, luego los parámetros solo-con-nombre, luego **kwargs. Son útiles para funciones envoltorio que pasan argumentos a otra función.

*args crea una tuple a partir de los argumentos posicionales restantes. **kwargs crea un dict a partir de los argumentos con nombre restantes. Orden de parámetros: posicional-o-con-nombre, *args, solo-con-nombre, **kwargs. En los sitios de llamada, *iterable desempaca argumentos posicionales y **mapping desempaca argumentos con nombre. Son simétricos: * en una firma recolecta; * en un sitio de llamada desempaca.

python
def total(*args):
    return sum(args)

total(1, 2, 3)          # 6
total(1, 2, 3, 4, 5)   # 15
python
def display(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

display(name="Sofía", score=87, level=5)

Puedes mezclarlos con parámetros regulares. Los parámetros regulares van primero:

python
def describe(title, *tags, **metadata):
    print(f"{title} | tags: {tags} | meta: {metadata}")

describe("Python intro", "beginner", "python", author="Sofía", year=2024)

Docstrings

Un docstring es una cadena al inicio de una función que describe lo que hace. Los editores y herramientas de Python lo usan para mostrar ayuda cuando pasas el cursor sobre una llamada a función. Usa comillas triples y escribe una línea para funciones simples.

Los docstrings se almacenan como f.__doc__ y se muestran con help(). La convención es una línea de resumen, opcionalmente seguida por una línea en blanco y más detalles. Para funciones de cara al público, un docstring es documentación que las herramientas pueden mostrar; no es opcional en nada que se llame desde múltiples lugares.

Los docstrings son literales de cadena colocados como la primera sentencia del cuerpo de una función, clase o módulo. Se almacenan como __doc__ en el objeto. PEP 257 define las convenciones. Herramientas como Sphinx, pydoc y los IDEs dependen de __doc__. Para información de tipos en docstrings, usa el estilo Google, NumPy o Sphinx; las anotaciones de tipo en la firma son preferibles a las anotaciones de tipo en el docstring para código moderno.

python
def normalise(value, min_val, max_val):
    """Scale a value to the 0-1 range given the known min and max."""
    return (value - min_val) / (max_val - min_val)
python
def build_url(base, version, resource, *, secure=True):
    """
    Build an API endpoint URL.

    Returns a fully-qualified URL string. If secure is False,
    the URL will use http instead of https.
    """
    scheme = "https" if secure else "http"
    base   = base.replace("https://", "").replace("http://", "")
    return f"{scheme}://{base}/{version}/{resource}"

Escribe un docstring para cualquier función que no sea obviamente autoexplicativa por su nombre y firma.

Anotaciones de tipo

Las anotaciones de tipo te permiten indicar qué tipos espera y retorna una función. Python no las hace cumplir en tiempo de ejecución, pero los editores las usan para detectar errores antes de que ejecutes algo. El -> antes de los dos puntos especifica el tipo de retorno.

Las anotaciones de tipo son documentación que las herramientas verifican. Los editores y verificadores de tipos (mypy, pyright) las usan para detectar incompatibilidades de tipos antes del tiempo de ejecución. No tienen efecto en tiempo de ejecución en Python estándar. -> None es la anotación correcta para funciones sin valor de retorno. Para contenedores genéricos, usa list[int], dict[str, int] (Python 3.9+).

Las anotaciones de tipo son procesadas por typing.get_type_hints() en tiempo de ejecución pero no tienen efecto directo sobre la ejecución. Los verificadores de tipos estáticos las analizan antes del tiempo de ejecución. PEP 484 introdujo el sistema de anotaciones; PEP 585 permitió genéricos integrados como list[int] sin importaciones de typing en Python 3.9+. -> None en una firma de función indica tanto "no retorna nada" como "no debería usarse en un contexto de expresión". Para tipos complejos, typing.Protocol, typing.TypeVar y typing.overload brindan todo el poder del tipado estático.

python
def greet(name: str, score: int) -> str:
    return f"{name} scored {score}"
python
def log(message: str) -> None:
    print(f"[LOG] {message}")
python
def top_scores(scores: list[int], n: int) -> list[int]:
    return sorted(scores, reverse=True)[:n]

Las anotaciones de tipo son opcionales pero valiosas en cualquier función que se vaya a llamar desde múltiples lugares. Son documentación que las herramientas pueden verificar.

Funciones como valores

Las funciones en Python son valores, igual que las cadenas o los números. Puedes asignarlas a variables y pasarlas a otras funciones. Así es como sorted() acepta una función key=.

Las funciones son objetos de primera clase: tienen un tipo (function), pueden almacenarse en variables y colecciones, y pueden pasarse como argumentos o retornarse como valores. Esta es la base de funciones de orden superior como sorted(key=...), map() y filter().

Las funciones son objetos del tipo function con atributos: __name__, __doc__, __annotations__, __defaults__, __code__ y __closure__. Se pasan por referencia, no se copian. Retornar una función desde una función crea una clausura si la función interna referencia variables del alcance externo: esas variables se almacenan en f.__closure__.

python
def double(x):
    return x * 2

def apply(func, value):
    return func(value)

apply(double, 5)   # 10

Pasar funciones como argumentos aparece constantemente con sorted(), map() y filter(). También lo verás en el capítulo Lambda, comprensiones y zip.

En la práctica

Dos funciones que trabajan juntas: letter_grade convierte una nota a una letra, y summarise la llama por cada nota en una lista:

python
def letter_grade(score: int) -> str:
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    else:
        return "F"

def summarise(scores: list[int]) -> None:
    total  = sum(scores)
    avg    = total / len(scores)
    grades = [letter_grade(s) for s in scores]
    print(f"Average: {avg:.1f}")
    print(f"Grades: {', '.join(grades)}")

summarise([87, 92, 74, 65, 91])

Un formateador de registros y un procesador de archivos que lo usa, con un parámetro por defecto dry_run que previene efectos secundarios a menos que se desactive explícitamente:

python
def format_log(level: str, message: str) -> str:
    return f"[{level.upper():5}] {message}"

def process_file(path: str, dry_run: bool = True) -> bool:
    print(format_log("info", f"Processing {path}"))
    if dry_run:
        print(format_log("info", "Dry run, no changes made"))
        return True
    return True

process_file("report.csv")
process_file("report.csv", dry_run=False)

Un normalizador de un solo valor y un normalizador de columna construido sobre él, con anotaciones de tipo y un docstring. La función de columna calcula el rango una sola vez y reutiliza la función escalar para cada elemento:

python
def normalise(value: float, min_val: float, max_val: float) -> float:
    """Scale value to the 0-1 range given known min and max."""
    if max_val == min_val:
        return 0.0
    return (value - min_val) / (max_val - min_val)

def normalise_column(values: list[float]) -> list[float]:
    """Normalise an entire column of values."""
    lo, hi = min(values), max(values)
    return [normalise(v, lo, hi) for v in values]

raw = [10.0, 25.0, 5.0, 40.0, 15.0]
print(normalise_column(raw))

Las anotaciones de tipo aquí cumplen dos propósitos: documentan lo que la función espera, y permiten que un verificador de tipos detecte a quienes la llaman pasando una lista de cadenas por error.