2

How can I determine whether a function was called using the function's name or by the name of an alias of that function?

I can inspect a function to get its name from within the body of a function by doing:

import inspect

def foo():
   print(inspect.stack()[0][3])

foo() # prints 'foo'

source: Determine function name from within that function (without using traceback)

However, if I alias the function and try the same thing I get the original function name (not the alias)

bar = foo
bar() # prints 'foo'

I would like to be able to be able to do the following:

def foo():
    print(... some code goes here ...)

bar = foo

foo() # prints 'foo'
bar() # prints 'bar'
bunji
  • 5,063
  • 1
  • 17
  • 36
  • There's no guarantee the reference used to call your function was obtained from any name at all. It could have been `l = [foo]; l[0]()` or `sorted(some_list, key=l[0])`. – user2357112 Mar 10 '18 at 18:58
  • That's a good point, I guess in that case `l[0]()` should print `l[0]`. – bunji Mar 10 '18 at 19:05

2 Answers2

2

Based on the limited knowledge I have of the scope of your problem, this works:

import inspect

def foo():
  print(inspect.stack()[1][4][0].strip())

foo()
bar = foo
bar()

Results:

foo()
bar()
JacobIRR
  • 8,545
  • 8
  • 39
  • 68
  • This raises a 'NoneType not subscriptable' error for me. I can make it work by using inspect.stack()[0][3] instead, but then it still returns 'foo'. Any idea how you could actually make this work? – Olivier Melançon Mar 10 '18 at 19:05
  • 1
    It doesn't work in the interpreter but does as a script. – Olivier Melançon Mar 10 '18 at 19:08
  • Thanks, this works for me (both in an interpreter and in a script with python3.6) and has the added benefit of giving the arguments in the parentheses as well. – bunji Mar 10 '18 at 19:10
  • @OlivierMelançon, yuck.. Seems like any solution to this would be hacky. The only other thing I can think of is to loop through all parts of the stack and print the function name if it is NOT the name of the original, but still a hack – JacobIRR Mar 10 '18 at 19:10
  • @JacobIRR Well I am actually truly amazed that you could make it work as hacky as it is, good job! – Olivier Melançon Mar 10 '18 at 19:12
  • This solution also works for the case outlined by @user2357112 in the comment on the question: `l = [foo()]; l[0]()`, prints `'l[0]()'` – bunji Mar 10 '18 at 19:17
1

I have a (somewhat hacky) solution that relies on a regex to parse the function name out of a string. There might be a cleaner solution, but at least using inspect only this is the best I could find.

import inspect
import re


function_from_call = re.compile("\w+(?=\(\))")


def foo():
    _, call_frame, *_ = inspect.stack()
    _, _, _, _, call, *_ = call_frame
    print(re.search(function_from_call, str(call)).group())

bar = foo
bar()  # prints bar
foo()  # prints foo

Short explanation: First, I am grabbing the inspect frame of the call that resulted in a call to this function. Then, I am extracting the actual call string from this frame and I apply a regex to this call string that gives us the function name only.

Note: From an interpreter shell, inspect behaves differently and my code above produces an error because my regex cannot match an actual function name. An additional caveat is pointed out in a comment to this question by @user2357112: It is not obvious that a call is directly tied to a name, as in

l = [foo]; l[0]()

When run from a script, my solution will handle simple renaming cases properly (as the one given in this question) but I do not advocate using it since corner cases as the one above will result in confusing errors.

Nablezen
  • 362
  • 2
  • 10
  • I'm really curious to see this working! But your regexp doesn't match anything for me. – Olivier Melançon Mar 10 '18 at 18:48
  • that's odd. did you use my code exactly as provided here? I get the print output as commented. I am using Python 3.5.2. in case that plays into it somehow.. Edit and Note: I am not sure that my regexp can handle arbitrarily named functions as is (which is why I called it a "hack" above). – Nablezen Mar 10 '18 at 18:49
  • I am using Python 3.6.2, the inspect module might be slightly different – Olivier Melançon Mar 10 '18 at 18:53
  • And when I use inspect.stack(), as expected, the function name is still foo. – Olivier Melançon Mar 10 '18 at 18:58
  • I installed Python 3.6.2 in a pyenv but running my script with it still gives me my described output..mysterious. But notice that the answer of @JacobIRR (which gets around the regex) uses the same inspect access as I do. – Nablezen Mar 10 '18 at 19:02
  • I find it extremely puzzling that it works. It really shouldn't... I'll investigate – Olivier Melançon Mar 10 '18 at 19:03
  • It doesn't work in the interpreter but does as a script. – Olivier Melançon Mar 10 '18 at 19:07
  • Yes, good catch. I can reproduce your problem in the interpreter. I guess that is an intricacy of inspect. I will edit to make people aware of this. – Nablezen Mar 10 '18 at 19:09
  • I am truly amazed by the solution you both came up with. – Olivier Melançon Mar 10 '18 at 19:09
  • Thanks, this works for me as well. It is a little hacky but I don't imagine a non-hacky solution to this problem is available. – bunji Mar 10 '18 at 19:13