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