У коментарі до цієї відповіді на інше питання хтось сказав, що вони не впевнені, що functools.wrapsце робить. Отже, я задаю це запитання, щоб він був записом його в StackOverflow для подальшої довідки: що functools.wrapsробити саме?
У коментарі до цієї відповіді на інше питання хтось сказав, що вони не впевнені, що functools.wrapsце робить. Отже, я задаю це запитання, щоб він був записом його в StackOverflow для подальшої довідки: що functools.wrapsробити саме?
Відповіді:
Використовуючи декоратор, ви замінюєте одну функцію іншою. Іншими словами, якщо у вас є декоратор
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
то коли ти кажеш
@logged
def f(x):
"""does some math"""
return x + x * x
це точно так само, як сказати
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
і ваша функція fзамінюється на функцію with_logging. На жаль, це означає, що якщо ви потім скажете
print(f.__name__)
він буде надрукований, with_loggingтому що це назва вашої нової функції. Насправді, якщо ви подивитеся на docstring для f, він буде порожнім, тому що with_loggingне має docstring, і тому docstring, який ви написали, більше не буде. Крім того, якщо ви подивитеся на результат pydoc для цієї функції, він не буде вказаний як один аргумент x; замість цього він буде вказаний як взяття, *argsі **kwargsтому, що для цього потрібно.
Якщо використання декоратора завжди означало втрату цієї інформації про функцію, це було б серйозною проблемою. Тому ми маємоfunctools.wraps . Це займає функцію, яка використовується в декораторі, і додає функціональність копіювання через ім'я функції, docstring, список аргументів і т. Д. І оскільки wrapsсам по собі є декоратором, наступний код робить правильно:
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print(f.__name__) # prints 'f'
print(f.__doc__) # prints 'does some math'
functools.wrapsця робота, чи не повинна вона в першу чергу бути лише частиною декоративного малюнка? коли ви не хочете використовувати @wraps?
@wraps, щоб виконати різні типи модифікацій або анотацій на значеннях, скопійованих. По суті, це розширення філософії Python, що явне краще, ніж неявне, а спеціальні випадки недостатньо спеціальні для порушення правил. (Код набагато простіший, а мову легше зрозуміти, якщо його @wrapsпотрібно надавати вручну, а не використовувати якийсь спеціальний механізм відмови.)
Я дуже часто використовую заняття, а не функції, для своїх декораторів. У мене були проблеми з цим, оскільки об’єкт не буде мати однакових атрибутів, які очікуються від функції. Наприклад, об’єкт не буде мати атрибут __name__. У мене була проблема з цим, що було досить важко простежити, де Джанго повідомляв про помилку "об’єкт не має атрибута __name__" ". На жаль, для декораторів у стилі класу я не вірю, що @wrap зробить цю роботу. Натомість я створив базовий клас декораторів так:
class DecBase(object):
func = None
def __init__(self, func):
self.__func = func
def __getattribute__(self, name):
if name == "func":
return super(DecBase, self).__getattribute__(name)
return self.func.__getattribute__(name)
def __setattr__(self, name, value):
if name == "func":
return super(DecBase, self).__setattr__(name, value)
return self.func.__setattr__(name, value)
Цей клас проксі-сервера передає всі атрибути до функції, яка декорується. Отже, тепер ви можете створити простий декоратор, який перевіряє, що два аргументи вказані так:
class process_login(DecBase):
def __call__(self, *args):
if len(args) != 2:
raise Exception("You can only specify two arguments")
return self.func(*args)
@wrapsговориться в документах , @wrapsце просто зручність функції functools.update_wrapper(). У разі оформлення класу, ви можете зателефонувати update_wrapper()безпосередньо зі свого __init__()методу. Таким чином, вам не потрібно створювати DecBaseна всіх, ви можете просто включити на __init__()з process_loginлінії: update_wrapper(self, func). Це все.
Станом на python 3.5+:
@functools.wraps(f)
def g():
pass
Псевдонім для g = functools.update_wrapper(g, f). Це робить саме три речі:
__module__, __name__, __qualname__, __doc__, і __annotations__атрибути fна g. Цей список за замовчуванням є WRAPPER_ASSIGNMENTS, ви можете побачити його у джерелі функцій .__dict__з gусіх елементів f.__dict__. (див. WRAPPER_UPDATESу джерелі)__wrapped__=fатрибутgНаслідком цього є те, що gсхоже на те, що має те саме ім'я, docstring, ім'я модуля та підпис, ніж f. Єдина проблема полягає в тому, що стосовно підпису це насправді не так: саме inspect.signatureза замовчуванням йде ланцюжок обгортки. Ви можете перевірити це, використовуючи inspect.signature(g, follow_wrapped=False)пояснення в документі . Це має дратівливі наслідки:
Signature.bind().Зараз існує певна плутанина між functools.wrapsдекораторами, адже дуже частою справою для розробки декораторів є використання функцій. Але обидва є абсолютно самостійними поняттями. Якщо вам цікаво зрозуміти різницю, я застосував бібліотеки-помічники для обох: decopatch, щоб легко писати декоратори, і makefun, щоб забезпечити заміну, що зберігає підписи @wraps. Зауважте, що makefunпокладається на той самий перевірений трюк, що й відома decoratorбібліотека.
це вихідний код про обгортання:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
Необхідна умова: Ви повинні знати, як користуватися декораторами та спеціально з обгортаннями. Цей коментар пояснює це трохи чітко, або це посилання також досить добре пояснює його.
Щоразу, коли ми використовуємо для Наприклад, наприклад: @wraps, за яким слідує наша функція обгортки. Відповідно до деталей, наведених у цьому посиланні , воно говорить про це
functools.wraps - це функція зручності для виклику update_wrapper () як декоратора функції при визначенні функції обгортки.
Вона еквівалентна частковій (update_wrapper, wrapped = загорнута, призначена = призначена, оновлена = оновлена).
Тож декоратор @wraps насправді дзвонить на functools.partial (func [, * args] [, ** ключові слова]).
Визначення functools.partial () говорить про це
Частинка () використовується для часткового застосування функції, яка "заморожує" деяку частину аргументів функції та / або ключові слова, в результаті чого новий об'єкт має спрощену підпис. Наприклад, parcial () може бути використаний для створення дзвінка, який веде себе як функція int (), де базовий аргумент за замовчуванням до двох:
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
Що підводить мене до висновку, що @wraps викликає частковий (), і він передає вашу функцію обгортки як параметр йому. Часткове () в кінцевому підсумку повертає спрощену версію, тобто об'єкт того, що знаходиться всередині функції обгортки, а не саму функцію обгортки.
Коротше кажучи, functools.wraps - це лише звичайна функція. Розглянемо цей офіційний приклад . За допомогою вихідного коду ми можемо побачити більше подробиць про реалізацію та запущені кроки наступним чином:
обгортка = O1 .__ виклик __ (обгортка)
Перевіряючи реалізацію __call__ , ми бачимо, що після цього кроку (ліва сторона) обгортка стає об'єктом в результаті self.func (* self.args, * args, ** newkeywords) Перевіряючи створення O1 у __new__ , ми Знайте self.func - це функція update_wrapper . В якості першого параметра він використовує параметр * args , праворучну обгортку . Перевіряючи останній крок update_wrapper , видно, що повернута права оболонка повернута, з деякими атрибутами змінено за потребою.