У коментарі до цієї відповіді на інше питання хтось сказав, що вони не впевнені, що 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 , видно, що повернута права оболонка повернута, з деякими атрибутами змінено за потребою.