- 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.