Search code examples
pythonnamespaces

In python, can an inner function be interrogated about its parent function?


Say we have a function, define in my_module.py:

def make_function(scalar: float) -> Callable[[float], float]:
    
    def increment(x: float) -> float:
        return x + scalar
    
    return increment

And on runtime, I have access to the live inner function objects (and I know that all the relevant code is defined in my_module.py).

>> make_function(2)
<function my_module.make_function.<locals>.increment(x: float) -> float>

Q: is there an elegant way, from the live object, to obtain information about what its parent function is? In particular I am interested in dynamically generating strings of the format "my_module.make_function.increment". I am looking for a solution which would also work for an arbitrary number of nested levels (i.e. "my_module.outer_func_1.outer_func_2.inner_func").

Note that the answer to my question seems to be (almost) embedded in the __repr__ of the function object as per my code sample before. But unsure about what internals are behind it and how to access them.

What have I tried?

  • inspect module: inspect.getmodule(func).__name__ but that only returns the module path
  • func.__code__.co_*, but couldn't find a way to point to the parent function
  • playing with func.__globals__, but that seemed more complex than needed

Ideally I would like to avoid decorating/modifying the defined functions and only arrive at the answer from inspecting object itself. Any help on how to proceed is much appreciated!


Solution

  • I think the safest bet is just to use __qualname__:

    In [6]: def foo():
       ...:     def bar():
       ...:         def baz():
       ...:             return 42
       ...:         return baz
       ...:     return bar()
       ...:
    
    In [7]: f = foo()
    
    In [8]: f
    Out[8]: <function __main__.foo.<locals>.bar.<locals>.baz()>
    
    In [9]:
    
    In [9]: f.__qualname__
    Out[9]: 'foo.<locals>.bar.<locals>.baz'
    

    So, something like:

    In [11]: f.__qualname__.replace("<locals>.", "")
    Out[11]: 'foo.bar.baz'