0%
Funciones en Python: organización y reutilización del código

Funciones

Estructura y reutiliza tu código de forma eficaz

10-15 min

Funciones en Python: organización y reutilización del código

Las funciones son uno de los conceptos fundamentales de la programación. Permiten agrupar secuencias de instrucciones en bloques reutilizables, organizar el código en componentes lógicos y evitar repeticiones. En este tutorial exploraremos en profundidad las funciones en Python, desde la sintaxis básica hasta los conceptos avanzados.

¿Qué es una función?

Una función es un bloque de código con nombre que realiza una tarea específica. Las funciones pueden aceptar datos de entrada (parámetros), realizar operaciones con esos datos y devolver un resultado. Permiten:

  • Reutilizar código sin tener que reescribirlo
  • Simplificar el mantenimiento compartimentando la lógica
  • Mejorar la legibilidad dividiendo un problema complejo en subproblemas
  • Facilitar las pruebas aislando comportamientos específicos

Definir una función simple

En Python, las funciones se definen con la palabra clave def, seguida del nombre de la función y de paréntesis que, opcionalmente, contienen parámetros.

def saluer():
    """Cette fonction affiche un simple message de salutation."""
    print("Bonjour, monde !")

# Appel de la fonction
saluer()  # Affiche: Bonjour, monde !

Anatomía de una función

  • Definición: empieza por def, seguido del nombre de la función y de paréntesis
  • Docstring: cadena de documentación (opcional pero recomendada) entre triples comillas
  • Cuerpo: código indentado que constituye la función
  • Instrucción return: permite devolver un valor (opcional)

Parámetros y argumentos

Los parámetros permiten a las funciones aceptar datos de entrada, haciéndolas más flexibles y reutilizables.

Parámetros posicionales

def saluer_personne(prenom, nom):
    """Salue une personne par son nom complet."""
    print(f"Bonjour, {prenom} {nom} !")

# Appel avec des arguments positionnels
saluer_personne("Marie", "Dupont")  # Affiche: Bonjour, Marie Dupont !

Parámetros por defecto

Los parámetros por defecto definen un valor que se utilizará si no se proporciona ningún argumento.

def saluer_avec_formule(nom, formule="Bonjour"):
    """Salue avec une formule personnalisable."""
    print(f"{formule}, {nom} !")

# Appels de la fonction
saluer_avec_formule("Jean")  # Utilise la formule par défaut
# Affiche: Bonjour, Jean !

saluer_avec_formule("Pierre", "Salut")  # Remplace la formule par défaut
# Affiche: Salut, Pierre !

Argumentos con nombre

Los argumentos con nombre permiten especificar explícitamente qué parámetro recibe cada valor.

def decrire_personne(nom, age, profession, ville):
    """Affiche une description d'une personne."""
    print(f"{nom}, {age} ans, est {profession} à {ville}.")

# Appel avec arguments nommés (ordre différent de la définition)
decrire_personne(
    ville="Paris", 
    profession="ingénieure", 
    nom="Sophie", 
    age=32
)
# Affiche: Sophie, 32 ans, est ingénieure à Paris.

Número variable de argumentos

Python permite definir funciones que aceptan un número variable de argumentos.

Argumentos posicionales variables (*args)

def calculer_somme(*nombres):
    """Calcule la somme de tous les nombres fournis."""
    resultat = 0
    for nombre in nombres:
        resultat += nombre
    return resultat

# Appels avec différents nombres d'arguments
print(calculer_somme(1, 2))  # 3
print(calculer_somme(1, 2, 3, 4, 5))  # 15
print(calculer_somme())  # 0

Argumentos con nombre variables (**kwargs)

def afficher_informations(**infos):
    """Affiche toutes les informations fournies sous forme de paires clé-valeur."""
    for cle, valeur in infos.items():
        print(f"{cle}: {valeur}")

# Appel avec plusieurs arguments nommés
afficher_informations(
    nom="Alice", 
    age=28, 
    email="alice@example.com"
)
# Affiche:
# nom: Alice
# age: 28
# email: alice@example.com

Combinación de tipos de argumentos

def fonction_complete(arg1, arg2, *args, kwarg1="défaut", **kwargs):
    """Démontre tous les types d'arguments possibles."""
    print(f"arg1: {arg1}")
    print(f"arg2: {arg2}")
    print(f"args: {args}")
    print(f"kwarg1: {kwarg1}")
    print(f"kwargs: {kwargs}")

fonction_complete(1, 2, 3, 4, 5, kwarg1="personnalisé", x=10, y=20)
# Affiche:
# arg1: 1
# arg2: 2
# args: (3, 4, 5)
# kwarg1: personnalisé
# kwargs: {'x': 10, 'y': 20}

Valores de retorno

Una función puede devolver un valor mediante la instrucción return. Si no hay ninguna instrucción return, la función devuelve implícitamente None.

Devolver un valor simple

def carre(nombre):
    """Retourne le carré d'un nombre."""
    return nombre ** 2

resultat = carre(4)
print(resultat)  # 16

Devolver varios valores

def obtenir_dimensions():
    """Retourne la largeur et la hauteur d'un écran."""
    largeur = 1920
    hauteur = 1080
    return largeur, hauteur  # Retourne un tuple

dimensions = obtenir_dimensions()
print(dimensions)  # (1920, 1080)

# Déballage du tuple retourné
largeur, hauteur = obtenir_dimensions()
print(f"Largeur: {largeur}, Hauteur: {hauteur}")  # Largeur: 1920, Hauteur: 1080

Devolver distintos valores según las condiciones

def analyser_nombre(n):
    """Analyse un nombre et retourne différentes informations selon sa valeur."""
    if n < 0:
        return "négatif"
    elif n == 0:
        return "zéro"
    else:
        return "positif", "pair" if n % 2 == 0 else "impair"

# Appels avec différentes valeurs
print(analyser_nombre(-5))  # négatif
print(analyser_nombre(0))   # zéro
print(analyser_nombre(7))   # ('positif', 'impair')
print(analyser_nombre(8))   # ('positif', 'pair')

Salida anticipada con return

def diviser_securise(a, b):
    """Effectue une division sécurisée."""
    if b == 0:
        print("Division par zéro impossible")
        return None  # Sortie anticipée
    return a / b

print(diviser_securise(10, 2))  # 5.0
print(diviser_securise(10, 0))  # None (après avoir affiché le message d'erreur)

Ámbito de las variables y espacios de nombres

El ámbito de una variable determina dónde es accesible esa variable en tu código.

Variables locales y globales

# Variable globale
x = 10

def modifier_variable():
    # Variable locale
    y = 5
    print(f"Dans la fonction, x = {x}")  # Accès à la variable globale x
    print(f"Dans la fonction, y = {y}")

modifier_variable()
print(f"Hors fonction, x = {x}")
# print(f"Hors fonction, y = {y}")  # Erreur : y n'est pas défini

Modificar una variable global

Para modificar una variable global dentro de una función, hay que utilizar la palabra clave global.

compteur = 0

def incrementer():
    global compteur  # Déclare que nous utilisons la variable globale
    compteur += 1
    print(f"Compteur = {compteur}")

incrementer()  # Compteur = 1
incrementer()  # Compteur = 2

Variables no locales (closures)

En las funciones anidadas, la palabra clave nonlocal permite acceder a variables de la función envolvente.

def creer_compteur():
    """Crée et retourne une fonction de comptage."""
    count = 0
    
    def incrementer():
        nonlocal count  # Accès à la variable de la fonction englobante
        count += 1
        return count
    
    return incrementer

mon_compteur = creer_compteur()
print(mon_compteur())  # 1
print(mon_compteur())  # 2
print(mon_compteur())  # 3

Funciones lambda (funciones anónimas)

Las funciones lambda son funciones anónimas, definidas en una sola línea con una sintaxis concisa.

Sintaxis de las funciones lambda

# Fonction standard
def carre(x):
    return x ** 2

# Équivalent avec lambda
carre_lambda = lambda x: x ** 2

print(carre(5))        # 25
print(carre_lambda(5)) # 25

Uso típico con funciones de orden superior

Las lambdas se utilizan a menudo con funciones como map(), filter() y sorted().

# Utilisation avec map() pour transformer une liste
nombres = [1, 2, 3, 4, 5]
carres = list(map(lambda x: x ** 2, nombres))
print(carres)  # [1, 4, 9, 16, 25]

# Utilisation avec filter() pour filtrer une liste
nombres = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pairs = list(filter(lambda x: x % 2 == 0, nombres))
print(pairs)  # [2, 4, 6, 8, 10]

# Utilisation avec sorted() pour trier une liste
etudiants = [
    {"nom": "Alice", "note": 85},
    {"nom": "Bob", "note": 92},
    {"nom": "Charlie", "note": 78}
]
etudiants_tries = sorted(etudiants, key=lambda etudiant: etudiant["note"], reverse=True)
for etudiant in etudiants_tries:
    print(f"{etudiant['nom']}: {etudiant['note']}")
# Bob: 92
# Alice: 85
# Charlie: 78

Limitaciones de las funciones lambda

Las funciones lambda se limitan a una sola expresión y no pueden contener instrucciones múltiples ni estructuras de control complejas. Para funciones más complejas, es preferible utilizar la sintaxis def.

Funciones recursivas

Una función recursiva es una función que se llama a sí misma.

Ejemplo básico: factorial

def factorielle(n):
    """Calcule la factorielle de n de manière récursive."""
    if n <= 1:  # Cas de base
        return 1
    else:  # Appel récursif
        return n * factorielle(n - 1)

print(factorielle(5))  # 120 (5 * 4 * 3 * 2 * 1)

Ejemplo avanzado: recorrido de una estructura anidada

def parcourir_structure(structure, niveau=0):
    """Parcourt récursivement une structure de données imbriquée."""
    # Afficher la structure actuelle avec indentation selon le niveau
    indentation = "  " * niveau
    print(f"{indentation}Niveau {niveau}: {structure}")
    
    # Si la structure est un dictionnaire ou une liste, parcourir récursivement
    if isinstance(structure, dict):
        for cle, valeur in structure.items():
            print(f"{indentation}  Clé: {cle}")
            parcourir_structure(valeur, niveau + 1)
    elif isinstance(structure, list):
        for i, item in enumerate(structure):
            print(f"{indentation}  Index: {i}")
            parcourir_structure(item, niveau + 1)

# Exemple d'utilisation
structure_exemple = {
    "personne": {
        "nom": "Dupont",
        "adresses": [
            {"type": "domicile", "ville": "Paris"},
            {"type": "travail", "ville": "Lyon"}
        ]
    }
}

parcourir_structure(structure_exemple)

Límites de la recursión

Python limita la profundidad de recursión para evitar desbordamientos de pila. Por defecto, este límite suele ser de 1000 llamadas.

import sys
print(sys.getrecursionlimit())  # Affiche la limite actuelle (généralement 1000)

# Pour modifier la limite (à utiliser avec précaution)
# sys.setrecursionlimit(2000)

Para evitar los problemas de recursión profunda, algunos algoritmos recursivos pueden reescribirse de forma iterativa o utilizando técnicas como la memoización.

Decoradores de funciones

Los decoradores son un concepto avanzado que permite modificar o ampliar el comportamiento de las funciones.

Concepto básico

def mon_decorateur(fonction):
    def wrapper():
        print("Avant l'appel de la fonction")
        fonction()
        print("Après l'appel de la fonction")
    return wrapper

@mon_decorateur
def dire_bonjour():
    print("Bonjour !")

dire_bonjour()
# Affiche:
# Avant l'appel de la fonction
# Bonjour !
# Après l'appel de la fonction

Decorador que conserva los argumentos

def decorateur_avec_arguments(fonction):
    def wrapper(*args, **kwargs):
        print(f"Arguments: {args}, Kwargs: {kwargs}")
        resultat = fonction(*args, **kwargs)
        print(f"Résultat: {resultat}")
        return resultat
    return wrapper

@decorateur_avec_arguments
def addition(a, b):
    return a + b

addition(3, 5)
# Affiche:
# Arguments: (3, 5), Kwargs: {}
# Résultat: 8

Decoradores con parámetros

def repeter(n):
    def decorateur(fonction):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                resultat = fonction(*args, **kwargs)
            return resultat
        return wrapper
    return decorateur

@repeter(3)
def dire_bonjour(nom):
    print(f"Bonjour, {nom} !")
    return nom

dire_bonjour("Alice")
# Affiche 3 fois:
# Bonjour, Alice !

Documentación y anotaciones de tipo

Docstrings

Los docstrings (cadenas de documentación) permiten documentar tus funciones.

def calculer_moyenne(nombres):
    """
    Calcule la moyenne arithmétique d'une liste de nombres.
    
    Args:
        nombres (list): Une liste de nombres (int ou float)
        
    Returns:
        float: La moyenne des nombres
        
    Raises:
        ZeroDivisionError: Si la liste est vide
        
    Examples:
        >>> calculer_moyenne([1, 2, 3, 4, 5])
        3.0
    """
    return sum(nombres) / len(nombres)

Los docstrings se pueden consultar con la función help():

help(calculer_moyenne)

Anotaciones de tipo (Python 3.5+)

Las anotaciones de tipo mejoran la legibilidad y permiten la verificación estática del código.

def saluer(nom: str, age: int = 30) -> str:
    """Retourne un message de salutation personnalisé."""
    return f"Bonjour {nom}, vous avez {age} ans !"

# Les annotations sont stockées dans l'attribut __annotations__
print(saluer.__annotations__)
# {'nom': <class 'str'>, 'age': <class 'int'>, 'return': <class 'str'>}

Para los tipos más complejos, utiliza el módulo typing:

from typing import List, Dict, Tuple, Optional, Union, Callable

def traiter_donnees(
    valeurs: List[int],
    config: Dict[str, str],
    limites: Tuple[int, int],
    callback: Optional[Callable[[int], bool]] = None
) -> Union[int, str]:
    """Traite des données avec des types complexes."""
    # Implémentation...
    return 0

Buenas prácticas para las funciones

Principios generales

  1. Una función, una responsabilidad: cada función debería tener un objetivo claro y único.

  2. Nombres descriptivos: elige nombres que expliquen qué hace la función, no cómo lo hace.

    # Bon
    def calculer_moyenne(nombres):
        return sum(nombres) / len(nombres)
    
    # À éviter
    def fonction1(liste):
        return sum(liste) / len(liste)
  3. Tamaño razonable: las funciones deberían ser relativamente cortas (por lo general, menos de 20-30 líneas).

  4. Parámetros por defecto inmutables: Evita usar objetos mutables como valores por defecto.

    # Problemático
    def ajouter_element(element, liste=[]):
        liste.append(element)
        return liste
    
    # Mejor enfoque
    def ajouter_element(element, liste=None):
        if liste is None:
            liste = []
        liste.append(element)
        return liste
  5. Documentación clara: Utiliza docstrings para explicar el propósito, los parámetros y los valores de retorno.

Convención de nomenclatura

Sigue la PEP 8 para las convenciones de nomenclatura:

  • Utiliza nombres en minúsculas separados por guiones bajos (snake_case)
  • Prefiere verbos para las funciones que realizan acciones
  • Sé coherente con tus convenciones
# Bien
def calculer_moyenne(nombres):
    pass

def obtenir_utilisateur_par_id(user_id):
    pass

def est_valide(donnee):
    pass

Ejemplo completo: aplicación de las funciones

Este es un ejemplo que combina varios conceptos de funciones para crear un sistema sencillo de gestión de tareas:

from typing import List, Dict, Optional, Tuple, Callable
import time

# Tipos personalizados
Tache = Dict[str, any]
ListeTaches = List[Tache]

def creer_tache(
    titre: str, 
    description: str = "", 
    priorite: int = 1
) -> Tache:
    """
    Crée une nouvelle tâche avec un horodatage automatique.
    
    Args:
        titre: Le titre de la tâche
        description: Une description détaillée (optionnelle)
        priorite: Niveau de priorité (1-5, 5 étant le plus important)
        
    Returns:
        Un dictionnaire représentant la tâche
    """
    return {
        "id": int(time.time()),
        "titre": titre,
        "description": description,
        "priorite": max(1, min(5, priorite)),  # Garantit que la priorité est entre 1 et 5
        "termine": False,
        "date_creation": time.strftime("%Y-%m-%d %H:%M:%S")
    }

def ajouter_tache(
    liste_taches: ListeTaches, 
    tache: Tache
) -> ListeTaches:
    """Ajoute une tâche à la liste et retourne la liste mise à jour."""
    liste_taches.append(tache)
    return liste_taches

def marquer_comme_termine(
    liste_taches: ListeTaches, 
    tache_id: int
) -> Tuple[bool, str]:
    """
    Marque une tâche comme terminée.
    
    Returns:
        Un tuple (succès, message)
    """
    for tache in liste_taches:
        if tache["id"] == tache_id:
            tache["termine"] = True
            return True, f"Tâche '{tache['titre']}' marquée comme terminée"
    return False, "Tâche non trouvée"

def filtrer_taches(
    liste_taches: ListeTaches, 
    termine: Optional[bool] = None, 
    priorite_min: int = 0
) -> ListeTaches:
    """
    Filtre les tâches selon plusieurs critères.
    
    Args:
        liste_taches: Liste des tâches à filtrer
        termine: Si spécifié, filtre les tâches selon leur état
        priorite_min: Filtre les tâches avec une priorité supérieure ou égale
        
    Returns:
        Une nouvelle liste contenant les tâches filtrées
    """
    resultat = []
    for tache in liste_taches:
        # Vérifier l'état de la tâche si le paramètre est spécifié
        if termine is not None and tache["termine"] != termine:
            continue
        
        # Vérifier la priorité minimale
        if tache["priorite"] < priorite_min:
            continue
            
        resultat.append(tache)
    return resultat

def afficher_taches(
    liste_taches: ListeTaches,
    formatter: Callable[[Tache], str] = None
) -> None:
    """
    Affiche une liste de tâches.
    
    Args:
        liste_taches: Liste des tâches à afficher
        formatter: Fonction optionnelle pour formater chaque tâche
    """
    if not liste_taches:
        print("Aucune tâche à afficher.")
        return
    
    # Formatter par défaut
    if formatter is None:
        def formatter(tache):
            statut = "✓" if tache["termine"] else "☐"
            return f"{statut} [{tache['priorite']}] {tache['titre']}"
    
    # Afficher chaque tâche
    for i, tache in enumerate(liste_taches, 1):
        print(f"{i}. {formatter(tache)}")

# Utilisation de nos fonctions
if __name__ == "__main__":
    mes_taches = []
    
    # Ajouter quelques tâches
    mes_taches = ajouter_tache(
        mes_taches, 
        creer_tache("Apprendre les fonctions Python", "Tutoriel complet", 4)
    )
    mes_taches = ajouter_tache(
        mes_taches, 
        creer_tache("Faire les courses")
    )
    mes_taches = ajouter_tache(
        mes_taches, 
        creer_tache("Répondre aux emails", priorite=5)
    )
    
    # Afficher les tâches
    print("Toutes les tâches:")
    afficher_taches(mes_taches)
    
    # Marquer une tâche comme terminée
    succes, message = marquer_comme_termine(mes_taches, mes_taches[1]["id"])
    print(f"\nRésultat: {message}")
    
    # Filtrer et afficher les tâches non terminées avec priorité >= 4
    taches_importantes = filtrer_taches(mes_taches, termine=False, priorite_min=4)
    print("\nTâches importantes non terminées:")
    afficher_taches(taches_importantes)
    
    # Utiliser un formateur personnalisé
    print("\nAffichage détaillé:")
    afficher_taches(mes_taches, lambda t: f"{t['titre']} - Priorité: {t['priorite']} - Créée le: {t['date_creation']}")

Conclusión

Las funciones son uno de los pilares de la programación en Python. Permiten estructurar tu código y hacerlo más modular, más legible y más fácil de mantener. Si dominas las distintas técnicas presentadas en este tutorial, podrás:

  • Dividir problemas complejos en subproblemas manejables
  • Evitar la duplicación de código
  • Crear bibliotecas de funciones reutilizables
  • Escribir código más expresivo y mantenible

En el próximo tutorial exploraremos los módulos y paquetes en Python, que te permitirán organizar tus funciones y clases en unidades lógicas y reutilizables.


¿Necesitas repasar las estructuras de datos? Consulta nuestro tutorial sobre las estructuras de datos en Python.

Comentarios

Los comentarios funcionan con GitHub Discussions

Inicia sesión con GitHub para participar en la discusión

¡Enlace copiado!