堆栈跟踪中的Python函数的名称

Vad*_*aev 10 python metaprogramming

在Python2和Python3中,在堆栈跟踪__name__中没有使用函数,而是使用原始名称(后面指定的名称def).

考虑这个例子:

import traceback

def a():
    return b()

def b():
    return c()

def c():
    print("\n".join(line.strip() for line in traceback.format_stack()))

a.__name__ = 'A'
b.__name__ = 'B'
c.__name__ = 'C'

a();
Run Code Online (Sandbox Code Playgroud)

输出是:

File "test.py", line 16, in <module>
    a();
File "test.py", line 4, in a
    return b()
File "test.py", line 7, in b
    return c()
File "test.py", line 10, in c
    print("\n".join(line.strip() for line in traceback.format_stack()))
Run Code Online (Sandbox Code Playgroud)

为什么这样?如何更改堆栈跟踪中使用的名称?__name__那么使用的属性在哪里?

Vad*_*aev 12

所以,基本上每个函数都有三个可以被认为是函数名称的东西:

代码块的原始名称

它存储在f.__code__.co_name(f函数对象所在的位置).如果您使用def orig_name创建函数,那orig_name就是该名称.对于lambas来说<lambda>.

此属性是只读的,无法更改.因此,我知道在运行时使用自定义名称创建函数的唯一方法是exec:

exec("""def {name}():
  print '{name}'
""".format(name='any')) in globals()

any()  # prints 'any'
Run Code Online (Sandbox Code Playgroud)

(在对问题的评论中提到的还有更多低级别的方法.)

不可变性co_name实际上是有道理的:你可以确定你在调试器中看到的名称(或者只是堆栈跟踪)与源代码中看到的名称(以及文件名和行号)完全相同.

__name__函数对象的属性

它也有别名func_name.

你可以修改它(orig_name.__name__ = 'updated name'),你肯定每天都做:将装饰功能@functools.wraps复制__name__到新功能.

__name__由工具使用pydoc,这就是你需要的原因@functools.wraps:所以你没有看到文档中每个装饰器的技术细节.看看这个例子:

from functools import wraps

def decorator1(f):
    def decorated(*args, **kwargs):
        print 'start1'
        f(*args, **kwargs)
    return decorated

def decorator2(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        print 'start2'
        f(*args, **kwargs)
    return decorated

@decorator1
def test1():
    print 'test1'

@decorator2
def test2():
    print 'test2'
Run Code Online (Sandbox Code Playgroud)

这是pydoc输出:

FUNCTIONS
    decorator1(f)

    decorator2(f)

    test1 = decorated(*args, **kwargs)

    test2(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

由于文档中wraps没有任何迹象decorated.

参考名称

可以称为函数名称的另一件事(尽管很难)是存储对该函数的引用的变量或属性的名称.

如果使用创建函数def name,则该name属性将添加到当前范围.如果lambda你应该将结果分配给某个变量:name = lambda: None.

显然,您可以为同一个函数创建多个引用,并且所有引用都可以具有不同的名称.


所有这三个事物彼此连接的唯一方法是def foo创建具有两个__name____code__.co_name等于的函数对象的语句,foo并将其分配给foo当前范围的属性.但它们不受任何限制,可以彼此不同:

import traceback                             

def make_function():                         
    def orig_name():                         
        """Docstring here                    
        """                                  
        traceback.print_stack()              
    return orig_name                         

globals()['name_in_module'] = make_function()
name_in_module.__name__ = 'updated name'     

name_in_module()                             
Run Code Online (Sandbox Code Playgroud)

输出:

  File "my.py", line 13, in <module>
    name_in_module()
  File "my.py", line 7, in orig_name
    traceback.print_stack()
Run Code Online (Sandbox Code Playgroud)

是pydoc:

FUNCTIONS
    make_function()

    name_in_module = updated name()
        Docstring here
Run Code Online (Sandbox Code Playgroud)

我感谢其他人的意见和答案,他们帮助我组织了我的想法和知识.