2016-11-04 4 views
2

У меня есть два декоратора. Каждый декоратор получает функцию в качестве аргумента. Каждый декоратор устанавливает атрибут функции. После связывания декораторов с одной функцией я ожидаю увидеть два новых атрибута. Однако верхний декоратор t2 «перезаписывает» атрибуты t1. В противном случае t1 больше не существует после того, как все будет разрешено. Может ли кто-нибудь объяснить, почему и как это исправить?Декораторы связывания питона переписывают атрибуты

def t1(function): 
def wrapper(*args, **kwargs): 
    setattr(wrapper, "t1", True) 
    return function(*args, **kwargs) 
setattr(wrapper, "t1", False) 
return wrapper 

def t2(function): 
def wrapper(*args, **kwargs): 
    setattr(wrapper, "t2", True) 
    return function(*args, **kwargs) 
setattr(wrapper, "t2", False) 
return wrapper 

@t2 
@t1 
def test(): 
pass 
+1

't1'«оборачивает»функцию' test', а затем 't2'«оборачивает» функция, возвращаемая 't1'. Поэтому 't2' должен ожидать, что функция * украшена * как ее параметр, а не' test'. –

+1

Как стилистическая сторона, [стандарт PEP8] (https://www.python.org/dev/peps/pep-0008/#indentation) требует 4 пробелов для отступов. В то время как только 1 работает синтаксически, его действительно трудно читать и затрудняет обмен вашим кодом с другими. –

+0

Да, я вижу, что это так. Если I dir (function) - вход t2, я вижу атрибуты t1. Однако, если I dir (test), я вижу только t2 (t1 стирается). – Sonny

ответ

3

Это происходит, потому что ваши декораторы устанавливают атрибуты на обертках. Когда первый декодированный устанавливает атрибут на своей обертке, он передает оболочку второму декоратору, который добавляет другую оболочку поверх первой и устанавливает атрибут во вторую обертку. Таким образом, вы получаете вторую оболочку.

In [3]: def decorator_a(fn): 
    ...:  def wrapper(*args, **kwargs): 
    ...:   return fn(*args, **kwargs) 
    ...:  print("I'm setting the attribute on function {}".format(id(wrapper))) 
    ...:  setattr(wrapper, "attr1", True) 
    ...:  return wrapper 
    ...: 

In [4]: def decorator_b(fn): 
    ...:  def wrapper(*args, **kwargs): 
    ...:   return fn(*args, **kwargs) 
    ...:  print("I'm setting the attribute on function {}".format(id(wrapper))) 
    ...:  setattr(wrapper, "attr2", True) 
    ...:  return wrapper 
    ...: 

In [5]: first_time_decorated = decorator_a(lambda x: x) 
I'm setting the attribute on function 4361847536 

In [6]: second_time_decorated = decorator_b(first_time_decorated) 
I'm setting the attribute on function 4361441064 

Вы можете решить эту проблему, установив все атрибуты функции украшаются на обертке

In [14]: def decorator_a(fn): 
    ...:  def wrapper(*args, **kwargs): 
    ...:   return fn(*args, **kwargs) 
    ...:  setattr(wrapper, "attr1", True) 
    ...:  for attribute in set(dir(fn)) - set(dir(wrapper)): 
    ...:   setattr(wrapper, attribute, getattr(fn, attribute)) 
    ...:  return wrapper 
    ...: 

In [15]: def decorator_b(fn): 
    ...:  def wrapper(*args, **kwargs): 
    ...:   return fn(*args, **kwargs) 
    ...:  setattr(wrapper, "attr2", True) 
    ...:  for attribute in set(dir(fn)) - set(dir(wrapper)): 
    ...:   setattr(wrapper, attribute, getattr(fn, attribute)) 
    ...:  return wrapper 
    ...: 

In [16]: first_time_decorated = decorator_a(lambda x: x) 

In [17]: second_time_decorated = decorator_b(first_time_decorated) 

In [18]: second_time_decorated.attr1 
Out[18]: True 

In [19]: second_time_decorated.attr2 
Out[19]: True 
+0

Или еще лучше, используйте 'functools.wraps' для сохранения метаданных украшенной функции. – thorhunter

+0

@thorhunter да, хотя, насколько я помню, 'wraps' не копирует все атрибуты. –

+0

Ahhh, хорошо, поэтому цепочки декораторов (обертки) производят обернутые обертки, поэтому я не мог видеть t1. Теперь это имеет гораздо больший смысл. Спасибо. – Sonny

Смежные вопросы