更改生成器的 __name__
Changing the __name__ of a generator
给定以下设置:
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
?
您只是将名称复制到了函数中。产生的生成器对象仍然使用编译时设置的旧名称。
每次生成一个新的生成器对象时,您都必须设置该名称;不幸的是,该属性对于生成器是 只读:
>>> 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;该属性在此过程中变得可写。
给定以下设置:
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
?
您只是将名称复制到了函数中。产生的生成器对象仍然使用编译时设置的旧名称。
每次生成一个新的生成器对象时,您都必须设置该名称;不幸的是,该属性对于生成器是 只读:
>>> 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;该属性在此过程中变得可写。