Обьясните как работает @wraps(func) детально пжл
Знаю что @wraps(func) снимает something() из inner функции но подскажите принцип работы пжл шаг за шагом
from timeit import default_timer as timer
import math
import time
from functools import wraps
def decorator(func):
@wraps(func)
def inner(*args):
start = timer()
print(f"Function {func.__name__} starts its work")
func(*args)
end = timer()
print(f"Function {func.__name__} took {end-start} time")
return inner
@decorator
def something(a,b):
time.sleep(3)
print(a+b)
something(4,5)
#print(help(something))
Ответы (1 шт):
Любой декоратор вида
def decor(): ...
@decor
def func(): ...
можно переписать так:
def decor(): ...
def func(): ...
func = decor(func)
В вашем случае,
@wraps(func)
def inner(): ...
То же, что и
def inner(): ...
inner = wraps(func)(inner)
# вызов wraps(func) возвращает другую функцию,
# которая сама выступает в роли декоратора, сразу же вызывается с аргументом (inner)
wraps пытается сделать обертку inner максимально похожей на исходную func
Берет значения атрибутов
('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')у функцииfuncи присваивает их функцииinnerВсе значения из
func.__dict__переносит вinner.__dict__. (Справка:__dict__— словарь, хранящий все атрибуты объекта. В случае любого присваивания атрибута а-ляfunc.x = 0, он сохраняется в словареfunc.__dict__['x'])Сохраняет ссылку на исходную функцию
inner.__wrapped__ = func
Но просили пошагово - будет пошагово xD
Возможно, для начала стоит переписать @декораторы в виде обычного присваивания, для наглядности. Этот код делает то же самое, что и ваш:
#1
from timeit import default_timer as timer
import time
from functools import wraps
def decorator(func):
# @wraps(func)
def inner(*args):
start = timer()
print(f"Function {func.__name__} starts its work")
func(*args)
end = timer()
print(f"Function {func.__name__} took {end-start} time")
wrap = wraps(func) # (*1)
inner = wrap(inner) # partial.__call__(wrap, inner)
# Пока не обращайте внимания.
print(wrap.__class__) # (!) <class 'functools.partial'>
return inner
# @decorator
def something(a, b):
time.sleep(3)
print(a + b)
something = decorator(something) # ◄
something(4, 5)
Полез в исходный код functools (во всех кодах ниже, часть проверок и комментариев вырезаны), который и у вас должен лежать где-то тут:
C:\Users\USER\AppData\Local\Programs\Python\Python39\Lib\functools.py
Раз уж сперва вызывается wraps(func), посмотрим, что он из себя представляет:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
Т.е. у нас функция func, он же, something идет сюда в виде параметра wrapped
update_wrapper — какая-то вспомогательная функция, передается в виде аргумента.
Часть кода partial:
class partial:
"""New function with partial application of the given arguments and keywords"""
__slots__ = "func", "args", "keywords", "__dict__", "__weakref__"
def __new__(cls, func, /, *args, **keywords):
self = super(partial, cls).__new__(cls)
self.func = func
self.args = args
self.keywords = keywords
return self
def __call__(self, /, *args, **keywords):
keywords = {**self.keywords, **keywords}
return self.func(*self.args, *args, **keywords)
Справка: object.__new__
При вызове partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) сперва вызывается его __new__. Где cls - это сам partial, а func - функция update_wrapper. Тут не происходит ничего особенного, всего лишь сохраняет ссылку на update_wrapper в атрибуте .func, а wrapped, assigned, updated - в виде словаря .keywords. А self.args в данном случае остается пустым.
На минуточку вернемся к блоку кода #1, в строчке (*1) если wrap — это экземпляр класса partial, получается, при вызове inner = wrap(inner) будет вызван метод __call__ (он вызывается всякий раз, когда экземпляр класса пытаются вызвать как функцию)
partial.__call__(wrap, inner) // wrap → self, inner → попадает в *args
__call__ в свою очередь вызывает self.func (это update_wrapper).
Получается вызов update_wrapper(*self.args, *args, **keywords) где:
• *self.args пустой
• *args содержит одну функцию inner
• keywords - ранее сохраненные wrapped, assigned, updated
update_wrapper(inner, wrapped=wrapped, assigned=assigned, updated=updated) — в итоге имеем такой вызов, где wrapped - ссылка на исходную функцию something
А что с update_wrapper ?
def update_wrapper(wrapper, # inner
wrapped, # something
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function"""
for attr in assigned:
# assigned = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
try:
value = getattr(wrapped, attr)
except AttributeError:
pass
else:
setattr(wrapper, attr, value)
for attr in updated:
# updated = ('__dict__',)
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
wrapper.__wrapped__ = wrapped
return wrapper
Берет все необходимые атрибуты у something и присваивает их функции inner, сохраняет ссылку на исходную функцию в атрибуте __wrapped__