- Introdução à Metaprogramação
- Reflexão
- Anotações
- Decoradores
- 4.1 Como Funciona
- 4.2 Como Usar
- 4.3 Exemplos
- Geração de Código
- Orientação a Aspectos
- Sistemas de Metaprogramação
- Usando Metaprogramação de Forma Eficiente
- Conclusão
4 Decoradores
Os decoradores são uma técnica avançada da programação que permite adicionar comportamento a um método ou classe existente de forma dinâmica, sem precisar modificar o código fonte do método ou classe original.
Neste tópico, vamos explorar como os decoradores funcionam e como usá-los em suas aplicações.
4.1 Como Funcionam os Decoradores
Os decoradores são implementados como funções que recebem uma função ou classe como argumento e retornam uma nova versão modificada da função ou classe original.
Eles são especialmente úteis quando você quer adicionar funcionalidade a uma classe ou método sem ter que herdar de uma nova classe ou sobrescrever o método existente.
Por exemplo, aqui está como podemos implementar um decorador que adiciona um log de depuração a um método:
def debug(func):
def wrapper(*args, **kwargs):
print(f"Chamando {func.__name__} com os argumentos {args} {kwargs}")
result = func(*args, **kwargs)
print(f"Resultado: {result}")
return result
return wrapper
Neste exemplo, estamos definindo uma função debug
que é um decorador.
Ela recebe uma função func
como argumento e retorna uma nova função wrapper
que é uma versão modificada da função original.
Quando a função add
é chamada, o decorador debug
é aplicado a ela e a função wrapper
é chamada em vez da função original.
A função wrapper
adiciona o log de depuração antes e depois de chamar a função original e retorna o resultado da chamada original.
Dessa forma, os decoradores nos permitem adicionar comportamento a um método ou classe existente de forma dinâmica e flexível, sem precisar modificar o código fonte original.
4.2 Como Usar Decoradores
Para usar um decorador, basta anexá-lo ao método ou classe que você deseja modificar.
O decorador será aplicado automaticamente quando o método ou classe for chamado.
Por exemplo, aqui está como podemos usar o decorador debug
que definimos anteriormente:
@debug
def add(x, y):
return x + y
print(add(2, 3))
Neste exemplo, estamos anexando o decorador debug
ao método add
.
Quando o método add
é chamado, o decorador debug
é aplicado automaticamente e a função
wrapper é chamada em vez da função original.
Isso adiciona o log de depuração ao método add
e retorna o resultado da chamada original.
Você também pode passar argumentos para o decorador quando o anexa ao método ou classe.
Por exemplo, aqui está como podemos modificar o decorador debug
para aceitar um argumento prefix
:
def debug(prefix):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"{prefix}: Chamando {func.__name__} com os argumentos {args} {kwargs}")
result = func(*args, **kwargs)
print(f"{prefix}: Resultado: {result}")
return result
return wrapper
return decorator
@debug("DEBUG")
def add(x, y):
return x + y
print(add(2, 3))
Neste exemplo, estamos definindo o decorador debug
para receber um argumento prefix
.
Quando o decorador é anexado ao método add
, passamos o valor "DEBUG"
para o argumento prefix
. Isso adiciona o prefixo "DEBUG: "
ao log de depuração adicionado pelo decorador.
Dessa forma, os decoradores nos permitem adicionar comportamento a um método ou classe de forma flexível e personalizada, dependendo de nossas necessidades específicas.
4.3 Exemplos de Decoradores
Existem muitas bibliotecas e frameworks que usam decoradores de forma eficiente para adicionar funcionalidades aos métodos e classes.
Aqui estão alguns exemplos de uso comum dos decoradores:
- Pytest: A biblioteca de teste Pytest permite que você anexe decoradores aos métodos de teste para modificar o comportamento deles. Por exemplo, você pode usar o decorador @pytest.mark.xfail para marcar um teste como esperado para falhar ou o decorador @pytest.mark.parametrize para rodar o mesmo teste com diferentes entrada e argumentos.
- Unittest: A biblioteca de teste Unittest do Python também permite que você anexe decoradores aos métodos de teste. Por exemplo, você pode usar o decorador @unittest.skip para pular um teste ou o decorador @unittest.expectedFailure para marcar um teste como esperado para falhar.
4.3.1 Exemplo de Decoradores com Pytest
Usando o Pytest como exemplo, podemos criar um decorador para marcar uma função de teste como "skip" se ela estiver em um sistema operacional específico:
import pytest
import platform
def skip_on_windows(func):
if platform.system() == "Windows":
return pytest.mark.skip(reason="Test is not supported on Windows")(func)
return func
@skip_on_windows
def test_example():
# Test code goes here
assert True
Neste exemplo, estamos criando um decorador skip_on_windows
que verifica o sistema operacional atual e, se for o Windows, marca a função de teste test_example
como "skip" usando o marcador do Pytest pytest.mark.skip
.
Se o sistema operacional não for o Windows, a função de teste é executada normalmente.
4.3.2 Exemplo de Decoradores com Unittest
Usando o Unittest como exemplo, podemos criar um decorador para marcar uma função de teste como "expected failure" se ela estiver em um sistema operacional específico:
import unittest
import platform
def expected_failure_on_windows(func):
if platform.system() == "Windows":
return unittest.expectedFailure(func)
return func
@expected_failure_on_windows
def test_example():
# Test code goes here
assert False
Neste exemplo, estamos criando um decorador expected_failure_on_windows
que verifica o sistema operacional atual e, se for o Windows, marca a função de teste test_example
como "expected failure" usando o método unittest.expectedFailure
.
Se o sistema operacional não for o Windows, a função de teste é executada normalmente.