2015-04-07 2 views
4

Учитывая следующие настройки:Изменение __name__ генератора

def mapper(f): 
    def wrapper(items): 
     for x in items: 
      yield f(x) 
    wrapper.__name__ = f.__name__ # This has no effect! 
    return wrapper 

@mapper 
def double(x): 
    return x * 2 

декоратор работает, как ожидалось:

>>> [x for x in double([1,2,3])] 
[2, 4, 6] 

Однако его __name__ не является double по желанию:

>>> double([1,2]).__name__ 
"wrapper" 

Можно ли форсировать имя генератора? В качестве альтернативы, можно ли копаться в объекте генератора и получить имя double?

+0

Думаю, вам нужно 'functools.wraps'. Смотрите это: http://stackoverflow.com/questions/308999/what-does-functools-wraps-do/309000#309000 – Pynchia

+0

@Pynchia: делает то же самое (плюс другие копии атрибутов). –

+1

Я думаю, что реальный вопрос здесь * «как вы можете re'__name__' объект-генератор?» *; тот факт, что вы хотите сделать это в декораторе, является случайным. – jonrsharpe

ответ

6

Вы только скопировали имя через функцию. Созданный объект генератора все еще использует старое имя, установленное во время компиляции.

Вам нужно будет установить это имя каждый раз, когда создается новый объект генератора; К сожалению, этот атрибут только для чтения для генераторов:

>>> def gen(): 
...  yield None 
... 
>>> gen().__name__ 
'gen' 
>>> gen().__name__ = 'foo' 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: attribute '__name__' of 'generator' objects is not writable 

Название запеченный в код объекта:

>>> gen.__code__.co_name 
'gen' 

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

def rename_code_object(func, new_name): 
    code_object = func.__code__ 
    function, code = type(func), type(code_object) 
    return function(
     code(
      code_object.co_argcount, code_object.co_nlocals, 
      code_object.co_stacksize, code_object.co_flags, 
      code_object.co_code, code_object.co_consts, 
      code_object.co_names, code_object.co_varnames, 
      code_object.co_filename, new_name, 
      code_object.co_firstlineno, code_object.co_lnotab, 
      code_object.co_freevars, code_object.co_cellvars), 
     func.__globals__, new_name, func.__defaults__, func.__closure__) 

Это создает новую функцию и код объекта , Используя new_name заменить старое название:

>>> new_name = rename_code_object(gen, 'new_name') 
>>> new_name.__name__ 
'new_name' 
>>> new_name().__name__ 
'new_name' 
>>> new_name() 
<generator object new_name at 0x10075f280> 

Используя это в вашем декоратор:

def mapper(f): 
    def wrapper(items): 
     for x in items: 
      yield f(x) 
    return rename_code_object(wrapper, f.__name__) 

Демо:

>>> def mapper(f): 
...  def wrapper(items): 
...   for x in items: 
...    yield f(x) 
...  return rename_code_object(wrapper, f.__name__) 
... 
>>> @mapper 
... def double(x): 
...  return x * 2 
... 
>>> double([1, 2]) 
<generator object double at 0x10075f370> 
>>> list(double([1, 2])) 
[2, 4] 

На Python 3.5, объекты генератора принимают их __name__ от а не из их объекта кода co_name, см. issue #21205; атрибут стал доступен для записи в процессе.

+0

К сожалению, это вызывает ошибку, когда вы ее вызываете: 'AttributeError: attribute '__name__' объектов 'generator' не записывается ' –

+0

@JoeHalliwell: way перед вами, позвольте мне выкопать, как заново создать объекты кода за секунду. :-P –

+0

@MartijnPieters Это не поможет слишком много, так как 'gi_code' также доступен только для чтения. – filmor

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