Name of a Python function in a stack trace

前端 未结 2 1293
眼角桃花
眼角桃花 2020-12-30 08:46

In both Python2 and Python3, in the stack trace the __name__ of a function is not used, the original name (the one that is specified after def) is

相关标签:
2条回答
  • 2020-12-30 08:50

    Tried to explore the 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 the former, the latter is not modified accordingly.

    Therefore, I tried to modify that directly, but it is not possible:

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

    Why are there two ways to say a function's name? Well, according to the documentation, __name__ is defined for "class, function, method, descriptor, or generator instance", so in the case of functions it maps to that attribute, for other objects it will map to something else.

    0 讨论(0)
  • So, basically every function has three things that can be considered being name of the function:

    The original name of the code block

    It's stored in the f.__code__.co_name (where f is the function object). If you use def orig_name to create function, orig_name is that name. For lambas it's <lambda>.

    This attribute is readonly and can't be changed. So the only way to create function with the custom name in runtime I'm aware of is exec:

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

    (There is also more low-level way to do this that was mentioned in a comment to the question.)

    The immutability of co_name actually makes sense: with that you can be sure that the name you see in the debugger (or just stack trace) is exactly the same you see in the source code (along with the filename and line number).

    The __name__ attribute of the function object

    It's also aliased to func_name.

    You can modify it (orig_name.__name__ = 'updated name') and you surely do on a daily basis: @functools.wraps copies the __name__ of the decorated function to the new one.

    __name__ is used by tools like pydoc, that's why you need @functools.wraps: so you don't see the technical details of every decorator in your documentation. Look at the example:

    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'
    

    Here is the pydoc output:

    FUNCTIONS
        decorator1(f)
    
        decorator2(f)
    
        test1 = decorated(*args, **kwargs)
    
        test2(*args, **kwargs)
    

    With wraps there is no sign of decorated in the documentation.

    Name of the reference

    One more thing that can be called function name (though it hardly is) is the name of a variable or an attribute where reference to that function is stored.

    If you create function with def name, the name attribute will be added to the current scope. In case of lambda you should assign the result to some variable: name = lambda: None.

    Obviously you can create more than one reference to the same function and all that references can have different names.


    The only way all that three things are connected to each other is the def foo statement that creates function object with both __name__ and __code__.co_name equal to foo and assign it to the foo attribute of the current scope. But they are not bound in any way and can be different from each other:

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

    Output:

      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
    

    I thank other people for comments and answers, they helped me to organize my thoughts and knowledge.

    0 讨论(0)
提交回复
热议问题