【问题标题】:Name of a Python function in a stack trace堆栈跟踪中 Python 函数的名称
【发布时间】:2017-04-01 09:36:14
【问题描述】:

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

输出是:

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__ 属性在哪里使用呢?

【问题讨论】:

  • a.__name__ 似乎是 a.func_name 的别名。堆栈跟踪似乎都没有使用它们。
  • 函数的代码对象也存储名称,但该属性是只读的。我认为这是回溯中使用的属性。要更改该名称,您需要从其属性重新创建代码对象并将其分配回函数对象的代码属性。见stackoverflow.com/questions/16064409/…
  • 这很有趣 - 看起来它使用的是代码对象的名称 a.__code__.co_name,而不是函数对象的名称。
  • @hivert func_name 确实是一个__name__ 别名,有明确记载:docs.python.org/2/library/inspect.html
  • @DanD。我什至可以用动态名称创建新函数,但我不知道该怎么做(保存eval

标签: python metaprogramming


【解决方案1】:

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

代码块原名

它存储在f.__code__.co_name 中(其中f 是函数对象)。如果你使用def orig_name 创建函数,orig_name 就是那个名字。对于lambas,它是&lt;lambda&gt;

此属性是只读的,不能更改。所以我知道在运行时使用自定义名称创建函数的唯一方法是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

我感谢其他人的 cmets 和答案,他们帮助我整理了我的想法和知识

【讨论】:

    【解决方案2】:

    试图探索CPython 的实现,绝对不是专家。正如 cmets 中所指出的,当打印 f 的堆栈条目时,属性f.__code__.co_nameis used。还有f.__name__is initially setf.__code__.co_namebut 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__ 是为“类、函数、方法、描述符或生成器实例”定义的,所以对于函数,它映射到该属性,对于其他对象,它将映射到其他对象。

    【讨论】:

    • 那么,__name__ 呢?它是如何使用的?
    • “它从未被 CPython 修改过”——不,它完全是。你只是在寻找错误的东西。哎呀,如果您只寻找对long_add 的直接引用,那么看起来CPython 从不添加整数。在我的脑海中,functools.wraps 设置了一个函数的__name__,而functools.wraps 在 CPython 源代码中的许多地方都使用了。
    • @user2357112 很有趣,我完全没想到。从答案中删除
    猜你喜欢
    • 2011-01-03
    • 1970-01-01
    • 2018-07-13
    • 2018-09-26
    • 2015-10-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-06
    • 1970-01-01
    相关资源
    最近更新 更多