Decoradores Python

Um Decorador Python fornece uma maneira concisa e reutilizável de estender uma função ou uma classe. Leia o artigo complementar Python Decorators: Simple Patterns to Level Up Your Code para exemplos práticos e padrões.

Decorador básico

Um decorador em sua forma mais simples é uma função que recebe outra função como argumento e retorna um wrapper (invólucro). O exemplo a seguir mostra a criação de um decorador e seu uso.

# Decorator: a function that takes another function and returns a wrapper
def your_decorator(func):
  def wrapper():
    # Do stuff before func...
    print("Before func!")
    func()  # Call the original function
    # Do stuff after func...
    print("After func!")
  return wrapper  # Return the wrapper function

# @your_decorator is syntactic sugar for: foo = your_decorator(foo)
@your_decorator
def foo():
  print("Hello World!")

foo()  # Calls wrapper, which calls foo with extra behavior
Before func!
Hello World!
After func!
Quiz

Faça login para responder este quiz e acompanhar seu progresso de aprendizagem

What is a decorator in Python?
A. A function that takes another function and returns a wrapper function
B. A special type of class
C. A built-in Python keyword
D. A way to delete functions

Decorador para uma função com parâmetros

# Decorator that works with functions that have parameters
def your_decorator(func):
  def wrapper(*args,**kwargs):  # Accept any arguments
    # Do stuff before func...
    print("Before func!")
    func(*args,**kwargs)  # Pass arguments to original function
    # Do stuff after func...
    print("After func!")
  return wrapper

@your_decorator
def foo(bar):
  print("My name is " + bar)

foo("Jack")  # Arguments are passed through wrapper
Before func!
My name is Jack
After func!

Template para um decorador básico

Este template é útil para a maioria dos casos de uso de decoradores. É válido para funções com ou sem parâmetros, e com ou sem valor de retorno.

import functools

# Best practice decorator template: preserves function metadata and return value
def your_decorator(func):
  @functools.wraps(func)  # Preserves function name, docstring, etc.
  def wrapper(*args,**kwargs):
    # Do stuff before func...
    result = func(*args,**kwargs)  # Call function and capture return value
    # Do stuff after func..
    return result  # Return the original function's return value
  return wrapper
Quiz

Faça login para responder este quiz e acompanhar seu progresso de aprendizagem

What does @functools.wraps(func) do in a decorator?
A. Makes the decorator execute faster
B. Preserves the original function's metadata (name, docstring, etc.)
C. Prevents the function from being called
D. Converts the function to a class

Decorador com parâmetros

Você também pode definir parâmetros para o decorador usar.

import functools

# Decorator factory: returns a decorator based on parameters
def your_decorator(arg):
  def decorator(func):
    @functools.wraps(func)  # Preserve function metadata
    def wrapper(*args,**kwargs):
      # Do stuff before func possibly using arg...
      result = func(*args,**kwargs)
      # Do stuff after func possibly using arg...
      return result
    return wrapper
  return decorator  # Return the actual decorator function

Para usar este decorador:

# Using decorator with parameters: @your_decorator(arg='x') calls your_decorator('x')
# which returns a decorator that is then applied to foo
@your_decorator(arg = 'x')
def foo(bar):
  return bar

Decoradores baseados em classes

Para decorar um método de classe, você deve definir o decorador dentro da classe. Quando apenas o argumento implícito self é passado para o método, sem quaisquer argumentos adicionais explícitos, você deve criar um decorador separado apenas para esses métodos sem quaisquer argumentos adicionais. Um exemplo disso, mostrado abaixo, é quando você quer capturar e imprimir exceções de uma certa maneira.

# Class method decorator: defined within the class
class DecorateMyMethod:

  # Static method decorator for methods with only 'self' parameter
  def decorator_for_class_method_with_no_args(method):
    def wrapper_for_class_method(self):  # Only takes self
      try:
        return method(self)  # Call original method
      except Exception as e:
        print("\nWARNING: Please make note of the following:\n")
        print(e)
    return wrapper_for_class_method

  def __init__(self,succeed:bool):
    self.succeed = succeed

  @decorator_for_class_method_with_no_args
  def class_action(self):
    if self.succeed:
      print("You succeeded by choice.")
    else:
      raise Exception("Epic fail of your own creation.")

test_succeed = DecorateMyMethod(True)
test_succeed.class_action()
You succeeded by choice.
test_fail = DecorateMyMethod(False)
test_fail.class_action()
Exception: Epic fail of your own creation.

Um decorador também pode ser definido como uma classe em vez de um método. Isso é útil para manter e atualizar um estado, como no exemplo a seguir, onde contamos o número de chamadas feitas a um método:

# Class-based decorator: maintains state between calls
class CountCallNumber:

  def __init__(self, func):
    self.func = func  # Store the function to decorate
    self.call_number = 0  # Initialize call counter

  def __call__(self, *args, **kwargs):  # Makes instance callable
    self.call_number += 1  # Increment counter
    print("This is execution number " + str(self.call_number))
    return self.func(*args, **kwargs)  # Call original function

@CountCallNumber  # Creates instance of CountCallNumber
def say_hi(name):
  print("Hi! My name is " + name)

say_hi("Jack")  # Calls CountCallNumber.__call__()
This is execution number 1
Hi! My name is Jack
say_hi("James")
This is execution number 2
Hi! My name is James

Count Example

This count example is inspired by Patrick Loeber's YouTube tutorial.