Introdução: Entendendo Funções em Python
Funções são blocos de código reutilizáveis que executam uma tarefa específica. Em Python, elas são objetos de primeira classe, o que significa que podem ser atribuídas a variáveis, passadas como argumentos e retornadas de outras funções. Dominar funções é fundamental porque praticamente todo programa profissional as utiliza para organizar lógica, evitar repetição e criar código maintível.
Nesta aula, você aprenderá não apenas como criar funções básicas, mas também técnicas avançadas como *args e **kwargs, que permitem trabalhar com números variáveis de argumentos. Também exploraremos como Python gerencia escopos de variáveis, um conceito crítico que causa bugs silenciosos em código iniciante.
Definição e Sintaxe Básica de Funções
Criando Sua Primeira Função
Uma função em Python começa com a palavra-chave def, seguida pelo nome da função, parênteses e dois pontos. O corpo da função é indentado e pode conter múltiplas instruções. Opcionalmente, a função retorna um valor com return.
def calcular_media(nota1, nota2, nota3):
"""Calcula a média aritmética de três notas."""
media = (nota1 + nota2 + nota3) / 3
return media
resultado = calcular_media(7.5, 8.0, 9.5)
print(resultado) # Saída: 8.333...
Observe que incluímos uma docstring (a string entre aspas triples). Essa é uma boa prática que documenta o propósito da função. A função recebe parâmetros (nota1, nota2, nota3) e retorna um valor. Sem return explícito, Python retorna None.
Argumentos Padrão e Argumentos Posicionais
Você pode definir valores padrão para parâmetros. Argumentos com valores padrão devem vir após argumentos sem valores padrão.
def saudar(nome, sobrenome="Silva", prefixo="Sr./Sra."):
"""Cria uma saudação personalizada."""
return f"{prefixo} {nome} {sobrenome}"
print(saudar("João")) # Sr./Sra. João Silva
print(saudar("Maria", "Santos")) # Sr./Sra. Maria Santos
print(saudar("Pedro", "Costa", "Dr.")) # Dr. Pedro Costa
Quando você chama a função, pode usar argumentos posicionais (na ordem) ou argumentos nomeados (em qualquer ordem).
print(saudar(nome="Ana", prefixo="Dra.")) # Dra. Ana Silva
print(saudar(prefixo="Prof.", nome="Carlos", sobrenome="Oliveira"))
# Prof. Carlos Oliveira
*args: Número Variável de Argumentos Posicionais
Por que Usar *args?
Às vezes, você não sabe quantos argumentos uma função receberá. Imagine uma função que calcula a soma de qualquer quantidade de números. Sem *args, você teria que criar versões diferentes da função. Com *args, você resolve isso elegantemente.
*args é uma convenção de nomenclatura (o importante é o asterisco, não o nome "args"). Ele coleta todos os argumentos posicionais extras em uma tupla. A função consegue iterar sobre essa tupla.
def somar(*numeros):
"""Soma qualquer quantidade de números."""
total = 0
for numero in numeros:
total += numero
return total
print(somar(1, 2, 3)) # 6
print(somar(10, 20, 30, 40, 50)) # 150
print(somar()) # 0 (tupla vazia)
Você pode verificar que numeros é uma tupla:
def inspecionar(*args):
print(f"Tipo: {type(args)}")
print(f"Conteúdo: {args}")
inspecionar(1, "olá", 3.14)
# Tipo: <class 'tuple'>
# Conteúdo: (1, 'olá', 3.14)
Combinando Argumentos Normais com *args
Frequentemente, você quer alguns argumentos obrigatórios seguidos de argumentos variáveis. A ordem importa: argumentos normais primeiro, depois *args.
def criar_relatorio(titulo, *dados):
"""Cria um relatório com um título e múltiplos dados."""
print(f"=== {titulo} ===")
for i, dado in enumerate(dados, start=1):
print(f"{i}. {dado}")
criar_relatorio("Vendas Mensais", 1500, 2300, 1800, 2100)
# === Vendas Mensais ===
# 1. 1500
# 2. 2300
# 3. 1800
# 4. 2100
**kwargs: Número Variável de Argumentos Nomeados
Entendendo **kwargs
**kwargs (keyword arguments) funciona como *args, mas para argumentos nomeados. Ele coleta todos os argumentos nomeados em um dicionário. Isso é especialmente útil quando você precisa passar configurações ou opções variáveis.
def configurar_servidor(**opcoes):
"""Configura um servidor com opções variáveis."""
for chave, valor in opcoes.items():
print(f"{chave}: {valor}")
configurar_servidor(host="localhost", porta=8080, debug=True, timeout=30)
# host: localhost
# porta: 8080
# debug: True
# timeout: 30
A diferença fundamental: *args é uma tupla (ordenada), **kwargs é um dicionário (chave-valor).
Combinando args, *kwargs e Argumentos Normais
Você pode usar os três tipos juntos, mas a ordem é crucial: argumentos normais → *args → **kwargs.
def processar_pedido(cliente, *itens, **opcoes):
"""Processa um pedido com cliente, itens e opções."""
print(f"Cliente: {cliente}")
print(f"Itens: {', '.join(itens)}")
print("Opções:")
for chave, valor in opcoes.items():
print(f" {chave}: {valor}")
processar_pedido(
"João Silva",
"Notebook", "Mouse", "Teclado",
frete="expressa",
garantia="24 meses",
desconto=0.10
)
# Cliente: João Silva
# Itens: Notebook, Mouse, Teclado
# Opções:
# frete: expressa
# garantia: 24 meses
# desconto: 0.10
Desempacotamento com * e **
Você também pode usar * e ** ao chamar uma função, para desempacotar iteráveis e dicionários.
def adicionar(a, b, c):
return a + b + c
numeros = [1, 2, 3]
print(adicionar(*numeros)) # 6, desempacota a lista
opcoes = {"a": 10, "b": 20, "c": 30}
print(adicionar(**opcoes)) # 60, desempacota o dicionário
Escopo de Variáveis
Os Quatro Níveis de Escopo: LEGB
Python utiliza a regra LEGB para resolver nomes de variáveis. Quando você referencia uma variável, Python procura nesta ordem: Local → Enclosing → Global → Built-in.
- Local (L): Variáveis dentro da função atual
- Enclosing (E): Variáveis em funções externas (para funções aninhadas)
- Global (G): Variáveis no nível do módulo/script
- Built-in (B): Nomes pré-definidos do Python (print, len, etc.)
x = "global"
def funcao_externa():
x = "enclosing"
def funcao_interna():
x = "local"
print(f"Dentro de funcao_interna: {x}")
funcao_interna()
print(f"Dentro de funcao_externa: {x}")
funcao_externa()
print(f"No nível global: {x}")
# Dentro de funcao_interna: local
# Dentro de funcao_externa: enclosing
# No nível global: global
Modificando Variáveis Globais: global e nonlocal
Por padrão, se você tenta atribuir um valor a uma variável dentro de uma função, Python cria uma nova variável local, não modifica a global. Use a palavra-chave global para referir-se à variável global.
contador = 0
def incrementar():
global contador
contador += 1
incrementar()
incrementar()
print(contador) # 2
Para funções aninhadas, use nonlocal para acessar variáveis do escopo envolvente (não global).
def externa():
valor = 10
def interna():
nonlocal valor
valor += 5
print(valor)
interna()
print(valor)
externa()
# 15
# 15
Escopo e Funções Aninhadas (Closures)
Funções internas podem "capturar" variáveis do escopo externo, criando closures. Isso é poderoso para criar funções parametrizadas.
def criar_multiplicador(fator):
"""Retorna uma função que multiplica por um fator."""
def multiplicar(numero):
return numero * fator
return multiplicar
vezes_3 = criar_multiplicador(3)
vezes_5 = criar_multiplicador(5)
print(vezes_3(10)) # 30
print(vezes_5(10)) # 50
A função multiplicar captura a variável fator do escopo externo. Cada vez que você chama criar_multiplicador, uma nova closure é criada com seu próprio fator.
Exemplo Prático Integrado
Para consolidar tudo que aprendemos, aqui está uma função que combina os conceitos:
historico_global = []
def registrar_operacao(operacao, *valores, usuario="sistema", **metadados):
"""Registra uma operação com valores variáveis e metadados."""
global historico_global
resultado = None
if operacao == "soma":
resultado = sum(valores)
elif operacao == "multiplicacao":
resultado = 1
for v in valores:
resultado *= v
registro = {
"operacao": operacao,
"valores": valores,
"resultado": resultado,
"usuario": usuario,
"metadados": metadados
}
historico_global.append(registro)
return resultado
# Exemplos de uso
registrar_operacao("soma", 1, 2, 3, usuario="admin", timestamp="2024-01-15")
registrar_operacao("multiplicacao", 2, 3, 4, prioridade="alta")
registrar_operacao("soma", 10, 20)
for registro in historico_global:
print(f"{registro['usuario']}: {registro['operacao']} = {registro['resultado']}")
Este exemplo demonstra:
- Argumentos normais (operacao)
- *valores para múltiplos argumentos posicionais
- Argumentos com padrão (usuario="sistema")
- **metadados para configurações variáveis
- Uso de variável global (historico_global)
- Lógica condicional e processamento
Conclusão
Ao dominar funções em Python, você adquire ferramentas essenciais para escrever código profissional e mantível. Três pontos-chave a lembrar: Primeiro, *args e **kwargs não são obrigatórios para toda função, mas são inestimáveis quando você precisa flexibilidade com argumentos variáveis — use-os conscientemente para APIs e funções utilitárias. Segundo, compreender a regra LEGB e as palavras-chave global e nonlocal evita bugs silenciosos que surgem quando você inadvertidamente cria variáveis locais pensando estar modificando globais. Terceiro, closures e funções aninhadas abrem possibilidades de programação funcional e design patterns avançados — explore-as para expandir sua capacidade de abstração.