【问题标题】:Changing the __name__ of a generator更改生成器的 __name__
【发布时间】:2015-06-11 20:34:36
【问题描述】:

鉴于以下设置:

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

【问题讨论】:

  • 我认为你需要functools.wraps。看到这个:stackoverflow.com/questions/308999/what-does-functools-wraps-do/…
  • @Pynchia: 做同样的事情(加上其他属性副本)。
  • 我认为这里真正的问题是“你怎么能重新__name__一个生成器对象?”;您想在装饰器中这样做是偶然的。
  • 谢谢@jonrsharpe,我已经修改了标题。
  • 嗨@Scott,更改生成器的名称只是达到目的的一种手段:在通过转换时保留函数名称一个装饰师。 (我的示例旨在按照这些思路提出一个合理的用例。)

标签: python python-2.7 generator


【解决方案1】:

您只是将名称复制到函数中。生成的生成器对象仍然使用编译时设置的旧名称。

每次生成一个新的生成器对象时,您都必须设置该名称;不幸的是,该属性对于生成器是只读的

>>> 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 开始,生成器对象从函数对象而不是代码对象 co_name 属性中获取 __name__,请参阅 issue #21205;该属性在此过程中变为可写。

【讨论】:

  • 不幸的是,当您调用它时会引发错误:AttributeError: attribute '__name__' of 'generator' objects is not writable
  • @JoeHalliwell:在你前面,让我研究一下如何重新创建代码对象。 :-P
  • @MartijnPieters 这无济于事,因为gi_code 也是只读的。
  • Ew。也许可以用 eval() 做一些事情(可怕的)。
  • 这确实很可怕,但它正是我想要的!谢谢@MartijnPieters :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-24
  • 1970-01-01
  • 2013-07-10
相关资源
最近更新 更多