<h2>Introdução: Entendendo Funções em Python</h2>
<p>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.</p>
<p>Nesta aula, você aprenderá não apenas como criar funções básicas, mas também técnicas avançadas como <code><em>args</code> e <code></em>*kwargs</code>, 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.</p>
<h2>Definição e Sintaxe Básica de Funções</h2>
<h3>Criando Sua Primeira Função</h3>
<p>Uma função em Python começa com a palavra-chave <code>def</code>, 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 <code>return</code>.</p>
<pre><code class="language-python">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...</code></pre>
<p>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 <code>return</code> explícito, Python retorna <code>None</code>.</p>
<h3>Argumentos Padrão e Argumentos Posicionais</h3>
<p>Você pode definir valores padrão para parâmetros. Argumentos com valores padrão devem vir <strong>após</strong> argumentos sem valores padrão.</p>
<pre><code class="language-python">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</code></pre>
<p>Quando você chama a função, pode usar argumentos posicionais (na ordem) ou argumentos nomeados (em qualquer ordem).</p>
<pre><code class="language-python">print(saudar(nome="Ana", prefixo="Dra.")) # Dra. Ana Silva
print(saudar(prefixo="Prof.", nome="Carlos", sobrenome="Oliveira"))
Prof. Carlos Oliveira</code></pre>
<h2>*args: Número Variável de Argumentos Posicionais</h2>
<h3>Por que Usar *args?</h3>
<p>À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 <code><em>args</code>, você teria que criar versões diferentes da função. Com <code></em>args</code>, você resolve isso elegantemente.</p>
<p><code>*args</code> é 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.</p>
<pre><code class="language-python">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)</code></pre>
<p>Você pode verificar que <code>numeros</code> é uma tupla:</p>
<pre><code class="language-python">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)</code></pre>
<h3>Combinando Argumentos Normais com *args</h3>
<p>Frequentemente, você quer alguns argumentos obrigatórios seguidos de argumentos variáveis. A ordem importa: argumentos normais primeiro, depois <code>*args</code>.</p>
<pre><code class="language-python">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</code></pre>
<h2>**kwargs: Número Variável de Argumentos Nomeados</h2>
<h3>Entendendo **kwargs</h3>
<p><code>*<em>kwargs</code> (keyword arguments) funciona como <code></em>args</code>, 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.</p>
<pre><code class="language-python">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</code></pre>
<p>A diferença fundamental: <code><em>args</code> é uma tupla (ordenada), <code></em>*kwargs</code> é um dicionário (chave-valor).</p>
<h3>Combinando <em>args, </em>*kwargs e Argumentos Normais</h3>
<p>Você pode usar os três tipos juntos, mas a ordem é <strong>crucial</strong>: argumentos normais → <code><em>args</code> → <code></em>*kwargs</code>.</p>
<pre><code class="language-python">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</code></pre>
<h3>Desempacotamento com <em> e </em>*</h3>
<p>Você também pode usar <code><em></code> e <code><strong></code> ao </strong>chamar</em>* uma função, para desempacotar iteráveis e dicionários.</p>
<pre><code class="language-python">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</code></pre>
<h2>Escopo de Variáveis</h2>
<h3>Os Quatro Níveis de Escopo: LEGB</h3>
<p>Python utiliza a regra LEGB para resolver nomes de variáveis. Quando você referencia uma variável, Python procura nesta ordem: <strong>L</strong>ocal → <strong>E</strong>nclosing → <strong>G</strong>lobal → <strong>B</strong>uilt-in.</p>
<ul>
<li><strong>Local (L)</strong>: Variáveis dentro da função atual</li>
<li><strong>Enclosing (E)</strong>: Variáveis em funções externas (para funções aninhadas)</li>
<li><strong>Global (G)</strong>: Variáveis no nível do módulo/script</li>
<li><strong>Built-in (B)</strong>: Nomes pré-definidos do Python (print, len, etc.)</li>
</ul>
<pre><code class="language-python">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</code></pre>
<h3>Modificando Variáveis Globais: global e nonlocal</h3>
<p>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 <code>global</code> para referir-se à variável global.</p>
<pre><code class="language-python">contador = 0
def incrementar():
global contador
contador += 1
incrementar()
incrementar()
print(contador) # 2</code></pre>
<p>Para funções aninhadas, use <code>nonlocal</code> para acessar variáveis do escopo envolvente (não global).</p>
<pre><code class="language-python">def externa():
valor = 10
def interna():
nonlocal valor
valor += 5
print(valor)
interna()
print(valor)
externa()
15
15</code></pre>
<h3>Escopo e Funções Aninhadas (Closures)</h3>
<p>Funções internas podem "capturar" variáveis do escopo externo, criando closures. Isso é poderoso para criar funções parametrizadas.</p>
<pre><code class="language-python">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</code></pre>
<p>A função <code>multiplicar</code> captura a variável <code>fator</code> do escopo externo. Cada vez que você chama <code>criar_multiplicador</code>, uma nova closure é criada com seu próprio <code>fator</code>.</p>
<h2>Exemplo Prático Integrado</h2>
<p>Para consolidar tudo que aprendemos, aqui está uma função que combina os conceitos:</p>
<pre><code class="language-python">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']}")</code></pre>
<p>Este exemplo demonstra:</p>
<ul>
<li>Argumentos normais (<code>operacao</code>)</li>
<li><code>*valores</code> para múltiplos argumentos posicionais</li>
<li>Argumentos com padrão (<code>usuario="sistema"</code>)</li>
<li><code>**metadados</code> para configurações variáveis</li>
<li>Uso de variável global (<code>historico_global</code>)</li>
<li>Lógica condicional e processamento</li>
</ul>
<h2>Conclusão</h2>
<p>Ao dominar funções em Python, você adquire ferramentas essenciais para escrever código profissional e mantível. Três pontos-chave a lembrar: <strong>Primeiro</strong>, <code><em>args</code> e <code><strong>kwargs</code> 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. </strong>Segundo<strong>, compreender a regra LEGB e as palavras-chave <code>global</code> e <code>nonlocal</code> evita bugs silenciosos que surgem quando você inadvertidamente cria variáveis locais pensando estar modificando globais. </strong>Terceiro</em>*, 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.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://docs.python.org/3/tutorial/controlflow.html#defining-functions" target="_blank" rel="noopener noreferrer">Python Official Documentation - Functions</a></li>
<li><a href="https://realpython.com/python-args-kwargs/" target="_blank" rel="noopener noreferrer">Real Python - <em>args and </em>*kwargs in Python</a></li>
<li><a href="https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces" target="_blank" rel="noopener noreferrer">Python Official Documentation - Scope and Namespaces</a></li>
<li><a href="https://www.geeksforgeeks.org/scope-resolution-in-python-legb-rule/" target="_blank" rel="noopener noreferrer">GeeksforGeeks - Python Scope Resolution</a></li>
<li><a href="https://www.oreilly.com/library/view/fluent-python-2nd/9781492046592/" target="_blank" rel="noopener noreferrer">Fluent Python (2nd Ed.) - Luciano Ramalho - Chapter on Functions</a></li>
</ul>
<p><!-- FIM --></p>