Introdução às Funções em Python
Funções são blocos de código organizados e reutilizáveis que realizam uma única ação relacionada. Elas ajudam a dividir programas grandes em partes menores e gerenciáveis, promovendo a modularidade e a reutilização de código.
Definição e Invocação de Funções
Em Python, definimos uma função usando a palavra-chave def, seguida pelo nome da função e parênteses (). O corpo da função é indentado. Para executar o código dentro de uma função, precisamos invocá-la.
# Exemplo básico de definição e chamada de função
def saudar():
"""Esta função imprime uma saudação simples."""
print("Olá, bem-vindo(a)!")
# Invocando a função
saudar()
Valores de Retorno
Uma função pode, opcionalmente, retornar um valor usando a instrução return. Se uma função não possui uma instrução return explícita, ela retorna None por padrão.
# Função que retorna um valor
def calcular_area_quadrado(lado):
"""Calcula a área de um quadrado dado o comprimento do lado."""
area = lado * lado
return area
# Invocando a função e capturando o valor de retorno
medida_lado = 5
resultado_area = calcular_area_quadrado(medida_lado)
print(f"A área do quadrado com lado {medida_lado} é: {resultado_area}")
# Função sem retorno explícito
def mostrar_mensagem():
print("Esta função não retorna nada explicitamente.")
valor_nulo = mostrar_mensagem()
print(f"O valor retornado por mostrar_mensagem é: {valor_nulo}")
Uma função pode retornar múltiplos valores, que serão empacotados em uma tupla:
def obter_coordenadas():
"""Retorna coordenadas x e y."""
x = 10
y = 25
return x, y
pos_x, pos_y = obter_coordenadas()
print(f"Coordenadas: ({pos_x}, {pos_y})")
# Se você atribuir a um único nome, ele recebe a tupla
todas_coordenadas = obter_coordenadas()
print(f"Todas as coordenadas em uma tupla: {todas_coordenadas}")
Parâmetros e Argumentos de Funções
Parâmetros são nomes definidos na declaração da função que atuam como placeholders para os valores que serão passados. Argumentos são os valores reais que são enviados para a função quando ela é invocada.
Tipos de Argumentos
-
Argumentos Posicionais: Os valores são passados para os parâmetros na ordem em que são definidos.
def exibir_info(nome, idade): print(f"Nome: {nome}, Idade: {idade}") exibir_info("Ana", 30) # "Ana" vai para 'nome', 30 para 'idade' -
Argumantos por Palavra-chave (Keyword Arguments): Os argumentos são passados identificando o nome do parâmetro, permitindo alterar a ordem.
def configurar_perfil(usuario, email): print(f"Usuário: {usuario}, Email: {email}") configurar_perfil(email="usuario@exemplo.com", usuario="dev_master") # Ordem alterada, mas funciona -
Argumentos Padrão (Default Arguments): Um parâmetro pode ter um valor padrão, tornando-o opcional. Se nenhum valor for fornecido para ele na chamada, o padrão será usado.
def registrar_log(mensagem, nivel="INFO"): print(f"[{nivel}] {mensagem}") registrar_log("Processo iniciado.") # Usa o nível padrão "INFO" registrar_log("Erro crítico!", nivel="ERRO") # Sobrescreve o nívelAtenção ao Argumento Padrão Mutável: Objetos mutáveis (como listas ou dicionários) usados como valores padrão podem levar a comportamentos inesperados, pois o objeto padrão é criado apenas uma vez e compartilhado entre todas as chamadas da função que não fornecem um valor para aquele parâmetro.
def adicionar_item(item, lista_itens=[]): # EVITAR ISSO! lista_itens.append(item) print(lista_itens) adicionar_item("maçã") # Saída: ['maçã'] adicionar_item("banana") # Saída: ['maçã', 'banana'] - Lista compartilhada! # Forma correta para evitar o problema: def adicionar_item_correto(item, lista_itens=None): if lista_itens is None: lista_itens = [] lista_itens.append(item) print(lista_itens) adicionar_item_correto("maçã") # Saída: ['maçã'] adicionar_item_correto("banana") # Saída: ['banana'] - Lista nova a cada chamada -
Argumentos de Comprimento Variável (
*argse**kwargs):*argspermite que uma função aceite um número arbitrário de argumentos posicionais. Eles são coletados em uma tupla.def somar_todos(*numeros): """Soma um número variável de argumentos.""" total = 0 for num in numeros: total += num return total print(somar_todos(1, 2, 3)) # Saída: 6 print(somar_todos(10, 20, 30, 40)) # Saída: 100**kwargspermite que uma função aceite um número arbitrário de argumentos por palavra-chave. Eles são coletados em um dicionário.def exibir_parametros(**dados): """Exibe parâmetros passados como chave=valor.""" for chave, valor in dados.items(): print(f"{chave}: {valor}") exibir_parametros(nome="Carlos", idade=28, cidade="São Paulo")
Ordem dos Parâmetros: A ordem correta ao definir parâmetros em uma função é:
- Parâmetros posicionais obrigatórios.
- Parâmetros com argumentos de comprimento variável (
*args). - Parâmetros padrão.
- Parâmetros com argumentos de palavra-chave de comprimento variável (
**kwargs).
def funcao_completa(a, b, *argumentos_pos, c=10, d=20, **argumentos_chave):
print(f"a={a}, b={b}")
print(f"Argumentos posicionais variáveis: {argumentos_pos}")
print(f"Argumentos padrão: c={c}, d={d}")
print(f"Argumentos de palavra-chave variáveis: {argumentos_chave}")
funcao_completa(1, 2, 3, 4, 5, c=100, e="extra")
Desempacotamento de Argumentos: Você pode usar * para desempacotar iteráveis (listas, tuplas) em argumentos posicionais e ** para desempacotar dicionários em argumentos de palavra-chave.
def mostrar_detalhes(primeiro, segundo, terceiro):
print(f"Primeiro: {primeiro}, Segundo: {segundo}, Terceiro: {terceiro}")
lista_valores = [10, 20, 30]
mostrar_detalhes(*lista_valores) # Desempacota a lista para os argumentos posicionais
dicionario_info = {'primeiro': 'A', 'segundo': 'B', 'terceiro': 'C'}
mostrar_detalhes(**dicionario_info) # Desempacota o dicionário para os argumentos de palavra-chave
Escopo e Namespaces
Namespace (Espaço de Nomes): Um mapeamento do nome para o objeto. Em Python, cada módulo, classe ou função tem seu próprio namespace.
Escopo: A região de um programa onde um namespace é diretamente acessível.
Existem quatro escopos principais em Python (regra LEGB - Local, Enclosing, Global, Built-in):
- Local (L): O escopo mais interno, criado dentro de uma função. Variáveis definidas aqui são locais e só existem enquanto a função está sendo executada.
- Enclosing (E): O escopo de uma função externa quando há funções aninhadas.
- Global (G): O escopo do nível superior de um módulo (arquivo
.py). Variáveis definidas aqui são acessíveis em qualquer lugar dentro do módulo. - Built-in (B): O escopo mais externo, contendo nomes como
print,len,list, etc., que estão sempre disponíveis.
Python pesquisa nomes nesta ordem: Local → Enclosing → Global → Built-in.
Funções Aninhadas e a Palavra-chave nonlocal
Funções podem ser aninhadas dentro de outras funções. A função interna tem acesso às variáveis da função externa (enclosing scope).
Para modificar uma variável no escopo enclosing (mas não global) a partir de uma função aninhada, usamos a palavra-chave nonlocal.
def funcao_externa():
contador = 0 # Variável no escopo enclosing
def funcao_interna():
nonlocal contador # Declara que 'contador' não é local, mas no escopo enclosing
contador += 1
print(f"Contador interno: {contador}")
funcao_interna()
funcao_interna()
print(f"Contador externo: {contador}")
funcao_externa()
A Palavra-chave global
Para modificar uma variável no escopo global a partir de dentro de uma função, usamos a palavra-chave global.
variavel_global = 10
def alterar_global():
global variavel_global # Declara que estamos referenciando a variável global
variavel_global += 5
print(f"Dentro da função (global): {variavel_global}")
print(f"Antes da função (global): {variavel_global}")
alterar_global()
print(f"Depois da função (global): {variavel_global}")
Funções como Objetos de Primeira Classe
Em Python, funções são objetos de primeira classe. Isso significa que elas podem ser:
- Atribuídas a variáveis.
- Armazenadas em estruturas de dados (listas, dicionários).
- Passadas como argumentos para outras funções.
- Retornadas como valores de outras funções.
def cumprimentar(nome):
return f"Olá, {nome}!"
# Atribuir a uma variável
saudacao_personalizada = cumprimentar
print(saudacao_personalizada("Mundo"))
# Armazenar em uma lista
operacoes = [cumprimentar, lambda x: f"Tchau, {x}!"]
print(operacoes[0]("Amigo"))
print(operacoes[1]("Inimigo"))
# Passar como argumento
def executar_funcao(funcao, valor):
return funcao(valor)
print(executar_funcao(cumprimentar, "Python"))
# Retornar uma função
def criar_multiplicador(fator):
def multiplicar(numero):
return numero * fator
return multiplicar
dobrar = criar_multiplicador(2)
triplicar = criar_multiplicador(3)
print(f"Dobro de 5: {dobrar(5)}")
print(f"Triplo de 5: {triplicar(5)}")
Closures (Fechamentos)
Um closure ocorre quando uma função interna (aninhada) lembra e acessa variáveis do escopo de sua função externa, mesmo após a função externa ter terminado sua execução. Essencialmente, a função interna "fecha" o acesso a essas variáveis.
def gerador_boas_vindas(idioma):
def saudacao(nome):
if idioma == "pt-br":
return f"Olá, {nome}!"
elif idioma == "en":
return f"Hello, {nome}!"
else:
return f"Oi, {nome}!"
return saudacao # Retorna a função interna, não o seu resultado
# Criamos funções específicas para cada idioma
saudacao_br = gerador_boas_vindas("pt-br")
saudacao_en = gerador_boas_vindas("en")
# As funções saudacao_br e saudacao_en ainda "lembram" o valor de 'idioma'
print(saudacao_br("Maria")) # Saída: Olá, Maria!
print(saudacao_en("John")) # Saída: Hello, John!
A característica principal de um closure é que a função interna mantém uma referência às variáveis do escopo da função externa, mesmo depois que a função externa já retornou. O atributo __closure__ de uma função pode revelar as variáveis que ela "fechou".
def criador_contador():
valor = 0 # Variável do escopo enclosing
def incrementar():
nonlocal valor
valor += 1
return valor
return incrementar
meu_contador = criador_contador()
print(meu_contador()) # Saída: 1
print(meu_contador()) # Saída: 2
print(meu_contador.__closure__) # Mostra as células de closure, incluindo 'valor'
Decoradores
Decoradores são uma forma elegante de estender ou modificar o comportamento de funções ou classes sem alterar seu código-fonte. Eles são funções que recebem outra função como argumento, adicionam alguma funcionalidade e retornam a função modificada.
O conceito de decoradores se baseia nos princípios de funções de primeira classe e closures.
import time
def medidor_tempo(func):
"""Um decorador que mede o tempo de execução de uma função."""
def wrapper(*args, **kwargs):
inicio = time.time()
resultado = func(*args, **kwargs) # Executa a função original
fim = time.time()
print(f"A função '{func.__name__}' levou {fim - inicio:.4f} segundos para executar.")
return resultado
return wrapper
@medidor_tempo # Sintaxe de decorador: equivalente a 'dizer_ola = medidor_tempo(dizer_ola)'
def dizer_ola(nome):
"""Uma função simples que simula algum trabalho."""
time.sleep(0.1) # Simula um atraso
return f"Olá, {nome}!"
@medidor_tempo
def calcular_potencia(base, expoente):
time.sleep(0.05)
return base ** expoente
print(dizer_ola("Mundo"))
print(calcular_potencia(2, 10))
O @medidor_tempo acima da definição da função dizer_ola é "açúcar sintático" para:
# Isso é o que o @medidor_tempo faz
# dizer_ola = medidor_tempo(dizer_ola)
Decoradores são poderosos para adicionar funcionalidades transversais como logging, cache, validação de argumentos, gerenciamento de transações e muito mais, sem poluir o código principal das funções.
Exemplos Práticos com Funções
Filtrar Elementos em Posições Ímpares
Crie uma função que receba uma lista ou tupla e retorne uma nova lista contendo apenas os elementos nos índices de posição ímpar.
def obter_elementos_impares(colecao):
"""
Retorna elementos em índices ímpares de uma coleção.
Ex: [0, 1, 2, 3, 4] -> [1, 3]
"""
return list(colecao[1::2]) # Usa fatiamento para pegar a partir do índice 1, pulando de 2 em 2
minha_lista = ['a', 'b', 'c', 'd', 'e', 'f']
print(f"Original: {minha_lista}, Ímpares: {obter_elementos_impares(minha_lista)}")
minha_tupla = (10, 20, 30, 40, 50)
print(f"Original: {minha_tupla}, Ímpares: {obter_elementos_impares(minha_tupla)}")
Contar Tipos de Caracteres em uma String
Desenvolva uma função que analise uma string e retorne a contagem de dígitos, letras, espaços e outros caracteres.
def analisar_string(texto):
"""
Conta dígitos, letras, espaços e outros caracteres em uma string.
Retorna um dicionário com as contagens.
"""
contagens = {'digitos': 0, 'letras': 0, 'espacos': 0, 'outros': 0}
for char in texto:
if char.isdigit():
contagens['digitos'] += 1
elif char.isalpha():
contagens['letras'] += 1
elif char.isspace():
contagens['espacos'] += 1
else:
contagens['outros'] += 1
return contagens
texto_exemplo = "Python 3.10 is awesome!"
resultado_analise = analisar_string(texto_exemplo)
print(f"Análise de '{texto_exemplo}': {resultado_analise}")
Modificar Valores de Dicionário Condicionalmente
Crie uma função que aceite um dicionário e, para cada valor que seja uma string ou lista com comprimento maior que 5, trunque-o para os primeiros 5 elementos.
def truncar_valores_dict(dicionario):
"""
Trunca valores de dicionário (strings ou listas) para os primeiros 5 elementos
se o comprimento for maior que 5.
"""
dicionario_modificado = {}
for chave, valor in dicionario.items():
if isinstance(valor, (str, list)) and len(valor) > 5:
dicionario_modificado[chave] = valor[:5]
else:
dicionario_modificado[chave] = valor
return dicionario_modificado
dados_originais = {
'nome': 'Alexandre',
'id': 1234,
'tags': ['python', 'funcoes', 'programacao', 'web'],
'descricao': 'Um texto muito longo para ser truncado',
'curto': 'abc'
}
dados_truncados = truncar_valores_dict(dados_originais)
print(f"Original: {dados_originais}")
print(f"Truncado: {dados_truncados}")
Utilitário de Substituição de Texto em Arquivo
Implemente uma função que encontre e substitua texto em um arquivo, salvando as alterações em um novo arquivo de backup.
import os
def substituir_em_arquivo(caminho_arquivo, texto_antigo, texto_novo):
"""
Substitui todas as ocorrências de texto_antigo por texto_novo em um arquivo.
Cria um arquivo de backup com a extensão '.bak'.
"""
arquivo_backup = f"{caminho_arquivo}.bak"
try:
with open(caminho_arquivo, 'r', encoding='utf-8') as f_entrada, \
open(arquivo_backup, 'w', encoding='utf-8') as f_saida:
for linha in f_entrada:
f_saida.write(linha.replace(texto_antigo, texto_novo))
print(f"Substituição concluída. Arquivo original salvo como '{arquivo_backup}'.")
except FileNotFoundError:
print(f"Erro: O arquivo '{caminho_arquivo}' não foi encontrado.")
except Exception as e:
print(f"Ocorreu um erro: {e}")
# Exemplo de uso
# Crie um arquivo 'exemplo.txt' com algum conteúdo para testar
# with open('exemplo.txt', 'w') as f:
# f.write("Esta é uma linha de teste.\n")
# f.write("Outra linha com a palavra teste.\n")
# f.write("Apenas mais um teste.")
substituir_em_arquivo('exemplo.txt', 'teste', 'exemplo')
# O arquivo 'exemplo.txt.bak' será criado com 'teste' substituído por 'exemplo'