2015-04-07 59 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

我認爲這裏真正的問題是*「你怎麼能'''給一個發電機對象命名?」*;你想在裝飾器中這樣做的事實是偶然的。 – 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:'generator'對象的屬性'__name__'不可寫' –

+0

@JoeHalliwell:way在此之前,讓我來挖掘一下如何重新創建代碼對象。 :-P –

+0

@MartijnPieters這不會有太大幫助,因爲'gi_code'也是隻讀的。 – filmor