堆栈跟踪中 Python 函数的名称

Name of a Python function in a stack trace

在Python2和Python3中,在stack trace中没有使用函数的__name__,原名(在[=14=之后指定的那个) ]) 被改用。

考虑示例:

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();

输出为:

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()))

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

前者试探CPython implementation, definitely not an expert. As pointed out in the comments, when the stack entry of f is printed, the attribute f.__code__.co_name is used. Also, f.__name__ is initially set to f.__code__.co_name, but when you modify,后者未做相应修改

因此,我试图直接修改它,但这是不可能的:

>>> f.__code__.co_name = 'g'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute
>>>

为什么函数名有两种说法?好吧,according to the documentation__name__ 是为 "class, function, method, descriptor, or generator instance" 定义的,所以在函数的情况下它映射到该属性,对于其他对象它将映射到其他东西。

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

代码块原名

它存储在 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'

(问题的评论中还提到了更底层的 way to do this。)

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'

这里是 pydoc 输出:

FUNCTIONS
    decorator1(f)

    decorator2(f)

    test1 = decorated(*args, **kwargs)

    test2(*args, **kwargs)

对于 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()                             

输出:

  File "my.py", line 13, in <module>
    name_in_module()
  File "my.py", line 7, in orig_name
    traceback.print_stack()

Pydoc:

FUNCTIONS
    make_function()

    name_in_module = updated name()
        Docstring here

我感谢其他人的评论和回答,他们帮助我整理了我的思想和知识