Funções em Python

Laboratório de Programação — Ciência da Computação • UFPI

Blocos reutilizáveis de código — definição, parâmetros, retorno, recursão, escopo e passagem de argumentos — com syntax highlighting

O que são funções?

  • Blocos de código reutilizáveis que realizam uma tarefa específica.
  • Promovem modularidade, leitura mais simples e evitam repetição (DRY).
  • Possuem parâmetros (entrada) e podem produzir valores de retorno.

Definindo uma função

def saudacao(nome):
    print("Olá,", nome + "!")

Chamando a função

saudacao("Maria")   # Saída: Olá, Maria!

Parâmetros & argumentos

def soma(a, b):
    return a + b

resultado = soma(3, 5)      # posicionais
resultado = soma(a=3, b=5)  # nomeados (keyword)

Valores padrão

def area_retangulo(largura, altura=1):
    return largura * altura

area_retangulo(5)           # 5 (altura usa padrão 1)
area_retangulo(5, altura=2) # 10

Retornando valores

def dividir(a, b):
    if b == 0:
        return None
    return a / b

q = dividir(10, 2)  # 5.0

Vários retornos

def estatisticas(valores):
    return min(valores), max(valores), sum(valores)/len(valores)

mn, mx, media = estatisticas([6.5, 7.0, 8.3])

Tipos de argumentos

  • Posicionais — ordem importa
  • Nomeados — especifica param=valor
  • Com valor padrão — parâmetro opcional

*args e **kwargs (extra)

def soma_tudo(*args):
    return sum(args)

def imprimir_config(**kwargs):
    for k, v in kwargs.items():
        print(k, "=", v)

soma_tudo(1,2,3)  # 6
imprimir_config(tema="claro", idioma="pt-BR")

Observação (Python ≥ 3.8): posicionais e nomeados explícitos

def power(base, exp, /, modulo=None, *, arredonda=False):
    # '/' marca parâmetros somente posicionais;
    # '*' marca parâmetros somente nomeados.
    r = base ** exp
    if modulo is not None:
        r %= modulo
    return round(r) if arredonda else r

power(2, 3, 5)                 # ok (posicionais + opcional)
power(2, 3, modulo=5)          # ok (nomeado)
# power(base=2, exp=3)         # ERRO: base/exp são somente posicionais
# power(2, 3, True)            # ERRO: 'modulo' é somente nomeado

Funções anônimas (lambda)

dobro = lambda x: x * 2
print(dobro(4))   # 8

Úteis para operações simples (ex.: key= em sorted, map, filter).

Docstrings

def fatorial(n):
    '''Calcula o fatorial de n (n!).'''
    if n == 0:
        return 1
    return n * fatorial(n-1)

print(fatorial.__doc__)  # exibe a docstring

Funções Recursivas

Uma função que chama a si mesma para resolver subproblemas.

  • Caso base: encerra a recursão
  • Caso recursivo: aproxima do caso base

Exemplo: fatorial

def fatorial(n):
    if n == 0:              # caso base
        return 1
    return n * fatorial(n-1)  # caso recursivo

print(fatorial(5))  # 120

Exemplo: Fibonacci

def fibonacci(n):
    if n == 0 or n == 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(5))  # 5

Observação: esta versão é exponencial; prefira memoization ou iteração.

(Extra) Otimizando com cache

from functools import lru_cache

@lru_cache(maxsize=None)
def fib_memo(n):
    if n < 2:
        return n
    return fib_memo(n-1) + fib_memo(n-2)

Escopo de Variáveis (LEGB)

  • Local — dentro da função
  • Enclosing — funções aninhadas
  • Global — módulo atual
  • Built-in — nomes do Python (len, print…)

Variáveis locais e globais

x = 20  # global

def minha_funcao():
    y = 10  # local
    print(y, x)

minha_funcao()  # 10 20
# print(y)     # NameError: y não existe aqui

Modificar global (cuidado!)

x = 10

def altera():
    global x
    x = 20

altera()
print(x)  # 20

Escopo aninhado (nonlocal)

def outer():
    msg = "outer"
    def inner():
        nonlocal msg
        msg = "inner"
    inner()
    return msg

print(outer())  # "inner"

LEGB em ação

x = "global"
def outer():
    x = "outer"
    def inner():
        x = "inner"
        print(x)  # inner
    inner()
    print(x)      # outer
outer()
print(x)          # global

Passagem de argumentos

Modelo do Python: passagem por objeto (também chamado de pass-by-assignment).

  • Imutáveis (int, str, tuple): parecem “por valor” — não há mutação.
  • Mutáveis (list, dict, set): parecem “por referência” — podem ser mutados.

Imutáveis (efeito de cópia)

def dobrar_numero(x):
    x = x * 2
    print("Dentro:", x)

n = 5
dobrar_numero(n)
print("Fora:", n)  # 5

Mutáveis (efeito de referência)

def adicionar_elemento(lst):
    lst.append(4)

val = [1, 2, 3]
adicionar_elemento(val)
print(val)  # [1, 2, 3, 4]

Atenção: argumentos padrão mutáveis

def acumular(item, colecao=[]):   # NÃO recomendado
    colecao.append(item)
    return colecao

print(acumular(1))  # [1]
print(acumular(2))  # [1, 2]  (surpresa!)

# Forma correta
def acumular_ok(item, colecao=None):
    if colecao is None:
        colecao = []
    colecao.append(item)
    return colecao

Boas práticas

  • Prefira funções puras (sem efeitos colaterais) quando possível.
  • Escreva docstrings claras e use type hints.
  • Retorne cedo (early return) para simplificar ramos.
  • Evite global; passe dados por parâmetros e retornos.
  • Teste funções isoladamente (unidades pequenas, fáceis de testar).

Cheat Sheet

# Definir: def nome(params): ...; return valor
# Chamar: nome(args); nome(param=valor)
# Padrão: def f(a, b=0): ...
# *args/**kwargs: coletores de argumentos
# Docstring: def f(): \"\"\"Descrição.\"\"\"
# Recursão: ter caso base + caso recursivo
# Escopo: LEGB; use global/nonlocal com parcimônia
# Passagem: por objeto; imutáveis vs mutáveis

Exemplo final — Funções em ação

def normalizar(nomes):
    return [n.strip().title() for n in nomes]

def filtrar(nomes, minimo=3):
    return [n for n in nomes if len(n) >= minimo]

def pipeline(nomes):
    return filtrar(normalizar(nomes), minimo=4)

dados = ["  ana", "rui ", "   mariana", "jo"]
print(pipeline(dados))  # ['Ana', 'Rui', 'Mariana']

Conclusões

  • Funções organizam, documentam e reutilizam código.
  • Parâmetros/retornos bem definidos simplificam manutenção.
  • Recursão, escopo e passagem de argumentos exigem atenção.