🌗 ← Índice

🐍 Python P14 — Decorators e Context Managers


Data: 14/08/2026

Nível: Intermediário (Mês 2 — POO)




🎯 Objetivos





📖 Teoria


O que é um decorator


Um decorator é uma função que recebe outra função e devolve uma função modificada. O @ é açúcar sintático:



def meu_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"→ Chamando {func.__name__}...")
        resultado = func(*args, **kwargs)
        print(f"← {func.__name__} retornou {resultado!r}")
        return resultado
    return wrapper

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

soma(2, 3)   # → Chamando soma... / ← soma retornou 5

💡 `@meu_decorator` é o mesmo que `soma = meu_decorator(soma)`.

Decorator com argumentos



def repetir(vezes):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(vezes):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repetir(3)
def oi():
    print("Oi!")

oi()   # Oi! Oi! Oi!

`functools.wraps` — preservar identidade



from functools import wraps

def log(func):
    @wraps(func)                     # copia __name__, __doc__ etc.
    def wrapper(*args, **kwargs):
        print(f"Executando {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

Sem `@wraps`, `soma.__name__` viraria `"wrapper"` — confuso para debugging.

`@staticmethod` vs `@classmethod` vs método normal



class Calculadora:
    taxa = 0.1

    def __init__(self, valor):
        self.valor = valor

    def com_imposto(self):                     # usa self (instância)
        return self.valor * (1 + self.taxa)

    @staticmethod
    def dobrar(n):                             # não usa self nem cls
        return n * 2

    @classmethod
    def com_taxa_personalizada(cls, valor, taxa):
        nova = cls(valor)                      # usa cls (classe)
        nova.taxa = taxa
        return nova

TipoRecebeAcesso
método normal`self`instância
`@classmethod``cls`classe (pode alterar estado da classe)
`@staticmethod`nadautilitário sem dependência

`@property` — getter elegante



class Temperatura:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, valor):
        if valor < -273.15:
            raise ValueError("Temperatura abaixo do zero absoluto!")
        self._celsius = valor

t = Temperatura(25)
print(t.fahrenheit)   # 77.0
t.celsius = 30        # usa o setter

Context Managers — `with`



class ArquivoAberto:
    def __init__(self, nome, modo="r"):
        self.arquivo = open(nome, modo)

    def __enter__(self):
        return self.arquivo

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.arquivo.close()
        return False          # False = não engole exceções

with ArquivoAberto("dados.txt") as f:
    conteudo = f.read()
# arquivo fechado automaticamente, mesmo com erro

`contextlib.contextmanager` — versão com generator



from contextlib import contextmanager

@contextmanager
def arquivo_aberto(nome, modo="r"):
    arquivo = open(nome, modo)
    try:
        yield arquivo                # entrega o recurso
    finally:
        arquivo.close()              # garante limpeza

with arquivo_aberto("dados.txt") as f:
    print(f.read())



✍️ Exercícios


  1. 1. Crie um decorator `tempo_execucao` que mede e imprime o tempo (usando `time.perf_counter()`) de qualquer função.
  2. 2. Crie uma classe `ContaBancaria` com:
  1. 3. Use `@contextmanager` para criar um `print_vermelho()` que muda a cor do texto no terminal e restaura no final.



🧪 Desafio


Crie um decorator validar_positivo que:



@validar_positivo("preco", "quantidade")
def calcular_total(preco, quantidade, desconto=0):
    return preco * quantidade - desconto

Teste: calcular_total(10, -2) deve falhar; calcular_total(10, 2) deve retornar 20.




✅ Correção



from functools import wraps

def validar_positivo(*nomes):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # monta dicionário de argumentos
            import inspect
            ligados = inspect.signature(func).bind(*args, **kwargs)
            ligados.apply_defaults()
            for nome in nomes:
                valor = ligados.arguments.get(nome)
                if valor is not None and valor <= 0:
                    raise ValueError(
                        f"Argumento '{nome}' deve ser positivo (recebido: {valor})"
                    )
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validar_positivo("preco", "quantidade")
def calcular_total(preco, quantidade, desconto=0):
    return preco * quantidade - desconto

print(calcular_total(10, 2))     # 20
# calcular_total(10, -2)         # ValueError: Argumento 'quantidade' deve ser positivo

💡 Dica extra: inspect.signature().bind() é o jeito profissional de ler argumentos nomeados dentro de um wrapper.





HB Estudos · ← Voltar ao índice de Python