0%
Funções em Python: organização e reutilização de código

Funções

Estruture e reutilize seu código com eficiência

10-15 min

Funções em Python: organização e reutilização de código

As funções são um dos conceitos fundamentais da programação. Elas permitem agrupar sequências de instruções em blocos reutilizáveis, organizar o código em componentes lógicos e evitar repetições. Neste tutorial, vamos explorar em profundidade as funções em Python, da sintaxe básica aos conceitos avançados.

O que é uma função?

Uma função é um bloco de código nomeado que executa uma tarefa específica. As funções podem aceitar dados de entrada (parâmetros), realizar operações com esses dados e retornar um resultado. Elas permitem:

  • Reutilizar código sem precisar reescrevê-lo
  • Simplificar a manutenção ao compartimentar a lógica
  • Melhorar a legibilidade ao dividir um problema complexo em subproblemas
  • Facilitar os testes ao isolar comportamentos específicos

Definindo uma função simples

Em Python, as funções são definidas com a palavra-chave def, seguida do nome da função e de parênteses que podem conter parâmetros.

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

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

Anatomia de uma função

  • Definição: começa com def, seguido do nome da função e de parênteses
  • Docstring: string de documentação (opcional, mas recomendada) entre aspas triplas
  • Corpo: código indentado que constitui a função
  • Instrução return: permite retornar um valor (opcional)

Parâmetros e argumentos

Os parâmetros permitem que as funções aceitem dados de entrada, tornando-as mais flexíveis e reutilizáveis.

Parâmetros posicionais

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 com valor padrão

Os parâmetros com valor padrão definem um valor que será usado caso nenhum argumento seja fornecido.

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 nomeados

Os argumentos nomeados permitem especificar explicitamente qual parâmetro recebe qual 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 variável de argumentos

Python permite definir funções que aceitam um número variável de argumentos.

Argumentos posicionais variáveis (*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 nomeados variáveis (**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

Combinação 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

Uma função pode retornar um valor por meio da instrução return. Se nenhuma instrução return estiver presente, a função retorna implicitamente None.

Retornando um valor simples

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

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

Retornando vários 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

Retornando valores diferentes conforme as condições

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')

Saída antecipada com 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)

Escopo de variáveis e namespaces

O escopo de uma variável determina onde essa variável é acessível no seu código.

Variáveis locais e globais

# 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

Modificando uma variável global

Para modificar uma variável global dentro de uma função, é necessário usar a palavra-chave 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

Variáveis não locais (closures)

Em funções aninhadas, a palavra-chave nonlocal permite acessar variáveis da função externa.

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

Funções lambda (funções anônimas)

As funções lambda são funções anônimas, definidas em uma única linha com uma sintaxe concisa.

Sintaxe das funções 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 com funções de ordem superior

As lambdas são frequentemente usadas com funções como map(), filter() e 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

Limitações das funções lambda

As funções lambda são limitadas a uma única expressão e não podem conter múltiplas instruções nem estruturas de controle complexas. Para funções mais complexas, é preferível usar a sintaxe def.

Funções recursivas

Uma função recursiva é uma função que chama a si mesma.

Exemplo básico: fatorial

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)

Exemplo avançado: percorrendo uma estrutura aninhada

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)

Limites da recursão

O Python limita a profundidade de recursão para evitar estouros de pilha. Por padrão, esse limite geralmente é de 1000 chamadas.

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 problemas de recursão profunda, alguns algoritmos recursivos podem ser reescritos de forma iterativa ou usando técnicas como a memoização.

Decoradores de funções

Os decoradores são um conceito avançado que permite modificar ou estender o comportamento das funções.

Conceito 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 preserva os 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 com 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 !

Documentação e anotações de tipo

Docstrings

As docstrings (strings de documentação) permitem documentar suas funções.

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)

As docstrings podem ser consultadas com a função help():

help(calculer_moyenne)

Anotações de tipo (Python 3.5+)

As anotações de tipo melhoram a legibilidade e permitem a verificação estática do 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 tipos mais complexos, use o 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

Boas práticas para funções

Princípios gerais

  1. Uma função, uma responsabilidade: cada função deve ter um objetivo claro e único.

  2. Nomes descritivos: escolha nomes que expliquem o que a função faz, e não como ela faz.

    # Bon
    def calculer_moyenne(nombres):
        return sum(nombres) / len(nombres)
    
    # À éviter
    def fonction1(liste):
        return sum(liste) / len(liste)
  3. Tamanho razoável: as funções devem ser relativamente curtas (geralmente menos de 20 a 30 linhas).

  4. Parâmetros padrão imutáveis: evite usar objetos mutáveis como valores padrão.

    # Problemático
    def ajouter_element(element, liste=[]):
        liste.append(element)
        return liste
    
    # Melhor abordagem
    def ajouter_element(element, liste=None):
        if liste is None:
            liste = []
        liste.append(element)
        return liste
  5. Documentação clara: use docstrings para explicar a finalidade, os parâmetros e os valores de retorno.

Convenção de nomenclatura

Siga a PEP 8 para as convenções de nomenclatura:

  • Use nomes em minúsculas separados por underscores (snake_case)
  • Prefira verbos para as funções que executam ações
  • Seja consistente em suas convenções
# Bom
def calculer_moyenne(nombres):
    pass

def obtenir_utilisateur_par_id(user_id):
    pass

def est_valide(donnee):
    pass

Exemplo completo: aplicação das funções

Aqui está um exemplo que combina vários conceitos de funções para criar um sistema simples de gerenciamento de tarefas:

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:
    """
    Cria uma nova tarefa com carimbo de data/hora automático.
    
    Args:
        titre: O título da tarefa
        description: Uma descrição detalhada (opcional)
        priorite: Nível de prioridade (1-5, sendo 5 o mais importante)
        
    Returns:
        Um dicionário representando a tarefa
    """
    return {
        "id": int(time.time()),
        "titre": titre,
        "description": description,
        "priorite": max(1, min(5, priorite)),  # Garante que a prioridade fique entre 1 e 5
        "termine": False,
        "date_creation": time.strftime("%Y-%m-%d %H:%M:%S")
    }

def ajouter_tache(
    liste_taches: ListeTaches, 
    tache: Tache
) -> ListeTaches:
    """Adiciona uma tarefa à lista e retorna a lista atualizada."""
    liste_taches.append(tache)
    return liste_taches

def marquer_comme_termine(
    liste_taches: ListeTaches, 
    tache_id: int
) -> Tuple[bool, str]:
    """
    Marca uma tarefa como concluída.
    
    Returns:
        Uma tupla (sucesso, mensagem)
    """
    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:
    """
    Filtra as tarefas segundo vários critérios.
    
    Args:
        liste_taches: Lista das tarefas a filtrar
        termine: Se especificado, filtra as tarefas de acordo com o estado
        priorite_min: Filtra as tarefas com prioridade maior ou igual a este valor
        
    Returns:
        Uma nova lista contendo as tarefas filtradas
    """
    resultat = []
    for tache in liste_taches:
        # Verificar o estado da tarefa se o parâmetro for especificado
        if termine is not None and tache["termine"] != termine:
            continue
        
        # Verificar a prioridade mínima
        if tache["priorite"] < priorite_min:
            continue
            
        resultat.append(tache)
    return resultat

def afficher_taches(
    liste_taches: ListeTaches,
    formatter: Callable[[Tache], str] = None
) -> None:
    """
    Exibe uma lista de tarefas.
    
    Args:
        liste_taches: Lista das tarefas a exibir
        formatter: Função opcional para formatar cada tarefa
    """
    if not liste_taches:
        print("Aucune tâche à afficher.")
        return
    
    # Formatador padrão
    if formatter is None:
        def formatter(tache):
            statut = "✓" if tache["termine"] else "☐"
            return f"{statut} [{tache['priorite']}] {tache['titre']}"
    
    # Exibir cada tarefa
    for i, tache in enumerate(liste_taches, 1):
        print(f"{i}. {formatter(tache)}")

# Utilização de nossas funções
if __name__ == "__main__":
    mes_taches = []
    
    # Adicionar algumas tarefas
    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)
    )
    
    # Exibir as tarefas
    print("Toutes les tâches:")
    afficher_taches(mes_taches)
    
    # Marcar uma tarefa como concluída
    succes, message = marquer_comme_termine(mes_taches, mes_taches[1]["id"])
    print(f"\nRésultat: {message}")
    
    # Filtrar e exibir as tarefas não concluídas com prioridade >= 4
    taches_importantes = filtrer_taches(mes_taches, termine=False, priorite_min=4)
    print("\nTâches importantes non terminées:")
    afficher_taches(taches_importantes)
    
    # Usar um formatador personalizado
    print("\nAffichage détaillé:")
    afficher_taches(mes_taches, lambda t: f"{t['titre']} - Priorité: {t['priorite']} - Créée le: {t['date_creation']}")

Conclusão

As funções são um dos pilares da programação em Python. Elas permitem estruturar seu código, torná-lo mais modular, mais legível e mais fácil de manter. Ao dominar as diversas técnicas apresentadas neste tutorial, você será capaz de:

  • Dividir problemas complexos em subproblemas gerenciáveis
  • Evitar a duplicação de código
  • Criar bibliotecas de funções reutilizáveis
  • Escrever código mais expressivo e fácil de manter

No próximo tutorial, vamos explorar os módulos e pacotes em Python, que permitirão organizar suas funções e classes em unidades lógicas e reutilizáveis.


Precisa revisar as estruturas de dados? Confira nosso tutorial sobre as estruturas de dados em Python.

Comentários

Os comentários são alimentados pelo GitHub Discussions

Conecte-se com o GitHub para participar da discussão

Link copiado!