Finding the name of a function while defining it

S

Steven D'Aprano

[...]
If i call one() and two() respectively, i would like to see "one" and
"two".

I'm afraid you're going to be disappointed. There is no possible way for
one() and two() as shown above to report different names, because they
are the same function object.

py> two = lambda : "one"
py> one = two
py> one is two
True
py> one, two
(<function <lambda> at 0xb7abd92c>, <function <lambda> at 0xb7abd92c>)
 
S

Steven D'Aprano

I've only ever wanted the name. If you need the actual function object,
I suppose you might eval() the name, or something like that.

Oh look, I found a peanut! Let me get a 50lb sledgehammer to crack it
open!

*wink*

Please do not use eval to retrieve the function object. It's slow,
overkill, a bad habit to get into, and a security risk if the name you
are eval'ing comes from an untrusted source.

Instead, look the name up in globals() or locals():


py> def spam():
.... return "NOBODY expects the Spanish Inquisition!"
....
py> name = "spam"
py> func = globals()[name]
py> func()
'NOBODY expects the Spanish Inquisition!'
 
S

Steven D'Aprano

While I am defining a function, how can I access the name (separately as
string as well as object) of the function without explicitly naming
it(hard-coding the name)? For eg. I am writing like:
def abc():
#how do i access the function abc here without hard-coding the name?

You can't easily get the function name from inside the function. But you
*can* easily get the function name from outside the function, so the
simple solution is to use a decorator to wrap the function with something
that knows its name. For example:


import functools

def print_function_name(func):
# Decorate func so that it prints a message when called.
@functools.wraps(func)
def inner(*args, **kwargs):
print "Calling function %s" % func.__name__
return func(*args, **kwargs)
return inner


@print_function_name
def spam(n):
return ' '.join(['spam']*n)

@print_function_name
def breakfast(n):
return ' and '.join(['ham', 'eggs', spam(n)])


And then in use:

py> spam(3)
Calling function spam
'spam spam spam'
py>
py> breakfast(5)
Calling function breakfast
Calling function spam
'ham and eggs and spam spam spam spam spam'


I recommend you use this solution if possible.

Unfortunately this doesn't help if you actually need the function name
*inside* the function, for example:

def invert(n):
if n != 0:
return 1/n
else:
raise ZeroDivisionError(
'divide by zero error in function %s' % ***MAGIC GOES HERE***)

(But note, why would I bother doing this? When the traceback prints, it
already displays the function name. Why am I doing by hand what Python
automatically does for me?)

If you really must do this, you can do it like this:


import traceback

def get_current_function_name():
"""If this looks like magic, that's because it is."""
x = traceback.extract_stack(limit=2)
return x[0][2]


def invert(n):
if n != 0:
return 1/n
else:
me = get_current_function_name()
raise ZeroDivisionError('divide by zero error in func %s' % me)
 
T

Tim Chase

two = lamba : "one"
one = two

Which one of these is the "name" of the function?
[...]
If i call one() and two() respectively, i would like to see "one" and
"two".

I'm afraid you're going to be disappointed. There is no possible way for
one() and two() as shown above to report different names, because they
are the same function object.

py> two = lambda : "one"
py> one = two
py> one is two
True
py> one, two
(<function <lambda> at 0xb7abd92c>, <function <lambda> at 0xb7abd92c>)

And for similar fun:

def call(fn, *args, **kwargs):
return fn(*args, **kwargs)

two = lambda : "one"
one = two
print(call(two))
print(call(one))

Depending on where in the code you are, the same function object
also has a local name of "fn". It's madness until you understand
it, and then it's beauty :)

-tkc
 
S

Steven D'Aprano

Depending on where in the code you are, the same function object also
has a local name of "fn". It's madness until you understand it, and
then it's beauty :)

"This is madness!"

"No, this is PYTHON!!!"
 
R

Roy Smith

Steven D'Aprano said:
two = lamba : "one"
one = two

Which one of these is the "name" of the function?
[...]
If i call one() and two() respectively, i would like to see "one" and
"two".

I'm afraid you're going to be disappointed. There is no possible way for
one() and two() as shown above to report different names, because they
are the same function object.

Well, there is the (yes, I know it's absurd) sledgehammer-and-peanut way
of getting a stack trace, finding the frame that called your function,
and parsing the text of that line.

Never tell a hacker, "no possible way" :)
 
S

Steven D'Aprano

Steven D'Aprano said:
two = lamba : "one"
one = two

Which one of these is the "name" of the function? [...]
If i call one() and two() respectively, i would like to see "one" and
"two".

I'm afraid you're going to be disappointed. There is no possible way
for one() and two() as shown above to report different names, because
they are the same function object.

Well, there is the (yes, I know it's absurd) sledgehammer-and-peanut way
of getting a stack trace, finding the frame that called your function,
and parsing the text of that line.

Never tell a hacker, "no possible way" :)


1) That is not supported by Python the language, only by certain Python
implementations. You might be running IronPython with frame support
turned off, or some other implementation with no frames at all.


2) Even if you have frames, you might not have access to the source code.
The code may be running from the (CPython) interactive interpreter, or
from a .pyc or .pyo file.


3) Even if you have the text of the source code, it might be ambiguous
(e.g. "f(); g(); one(); two()") and you cannot tell which is "the
current" function call.


4) Or the code may be obfuscated and too hard for you to parse:

eval("eval(''.join('ronnie'[1::2])+chr(40)+chr(41))")

(I expect that you, a human intelligence, can decipher the above, but can
your parser?)


5) Which also rules out decompiling the byte code and parsing the pseudo-
assembly.

No matter how clever your parser, a sufficiently obfuscated algorithm
will defeat it.


When I say something is impossible, I mean it is not a promised by the
language. Naturally there may be ways of accomplishing a task that the
language does not promise to allow which "only sometimes" works. At
worst, you can always guess an answer, and hope that you're right!

def get_current_function_name():
# Only sometimes right.
return "one" if random.random() < 0.5 else "two"
 
A

alex23

Unfortunately this doesn't help if you actually need the function name
*inside* the function

Here's an extension of Steven's original decorator that adds a
reference to the function itself into the function's globals, and then
composes a new function from the component parts:

import new

def IdentityAwareFunctionDecorator( fn ):
_globals = fn.__globals__
_globals['selffunc'] = fn
name_aware_func = new.function(
fn.__code__, _globals, fn.__name__, fn.__defaults__,
fn.__closure__ )
return name_aware_func

id = IdentityAwareFunctionDecorator

@id
def foo():
return selffunc.__name__

Unfortunately, it also only knows about the original function name, so
'bar = foo; bar()' will still give you 'foo'.
 
J

Jussi Piitulainen

Abhas Bhattacharya writes:

[...]
If i call one() and two() respectively, i would like to see "one"
and "two". I dont have much knowledge of lambda functions, neither
am i going to use them, so that's something I cant answer.

It's not about lambda. The following does not contain lambda. What
should be name(one)? name(two)? name(foo)? name(fun(1))? name(fun(3))?

def foo():
return 3

def fun(x):
def foo(): return x
return foo

one = fun(1)
two = one

Note that fun(1)() is a valid call where fun(1) is not given any name
outside fun.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,777
Messages
2,569,604
Members
45,220
Latest member
MathewSant

Latest Threads

Top