Как можно прикрепить декоратор к функции постфактум в python?

То, как я понимаю декораторы функций в python (и я могу ошибаться), заключается в том, что они должны добавлять побочные эффекты и изменять возвращаемое значение функции. Теперь декораторы добавляются над определением декорируемой функции или по заданию. Вот небольшой пример:

def print_args_decor(function):
    def wrapper(*args, **kwargs):
        print 'Arguments:', args, kwargs         # Added side-effect
        return function(*args, **kwargs)*5       # Modified return value
    return wrapper

@print_args_decor
def do_stuff(strg, n=10):
    """Repeats strg a few times."""
    return strg * n

new_decorated_func = print_args_decor(do_stuff)  # Decoration by assignment

print do_stuff('a', 2) # Output: aaaaaaaaaa

Теперь, как прикрепить декоратор к функции, определенной в другом месте, в идеале сохранив исходное имя функции и строку документации (как это делает functools.wraps)? Например, я импортирую функцию sqrt() из математического модуля Python и хочу ее украсить, как мне это сделать?

from functools import wraps
from math import sqrt

def print_args_decor(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        print 'Arguments:', args, kwargs         # Added side-effect
        return function(*args, **kwargs)*5       # Modified return value
    return wrapper

# Decorate the sqrt() function from math module somehow
@print_args_decor #???
sqrt #???

print sqrt(9)   
# Output: 
# Arguments: ([9],) {}
# 15                   # <--- sqrt(9)*5

Как насчет декорирования методов внутри классов постфактум? Как насчет украшения самих классов?


person con-f-use    schedule 23.11.2015    source источник


Ответы (1)


Вы импортировали sqrt в свой модуль, просто примените декоратор в своем собственном глобальном пространстве имен:

sqrt = print_args_decor(sqrt)

Это устанавливает имя sqrt в пространстве имен вашего модуля в качестве результата декоратора. Нет требования, чтобы sqrt был изначально определен в этом модуле.

Декоратор должен использовать декоратор functools.wraps() для сохранения метаданных функции, таких как имя и строка документации.

Декорирование класса ничем не отличается в этом отношении:

ClassName = decorator(ClassName)

В Python 2 для методов вам нужно быть осторожным, чтобы получить исходную несвязанную функцию; проще всего использовать атрибут method.__func__:

try:
    # Python 2
    ClassName.function_name = decorator(ClassName.function_name.__func__)
except AttributeError:
    # Python 3
    ClassName.function_name = decorator(ClassName.function_name)

Я завернул приведенное выше в try...except, чтобы шаблон работал в разных версиях Python. Альтернативой является захват объекта функции из класса __dict__, чтобы избежать срабатывания протокола дескриптора:

ClassName.function_name = decorator(ClassName.__dict__['function_name'])
person Martijn Pieters    schedule 23.11.2015
comment
Кажется достаточно простым, спасибо. Можете ли вы добавить пример оформления класса и метода внутри класса? - person con-f-use; 23.11.2015
comment
@con-f-use: декоратор просто вызывается. Синтаксис @decorator, как вы уже сказали, просто передает декорированный объект вызываемому декоратору и заменяет имя результатом. Итак, для класса это просто ClassName = decorator(ClassName). - person Martijn Pieters; 23.11.2015
comment
@con-f-use: существующие методы немного сложнее, поскольку декоратор применяется к объекту функции до того, как он станет частью класса. Вы можете получить исходную функцию с атрибутом __func__: ClassName.function_name = decorator(ClassName.function_name.__func__). - person Martijn Pieters; 23.11.2015