4

I like to know what was the local variable names when they are passed to a function. I'm not sure whether this is possible at all. Let's consider this example:

function definition:

def show(x):
  print(x)

usage:

a = 10
show(a)

this prints 10. But I like to print "a = 10". Is this possible in python?

6
  • Is there anything in particular that you want to use this for? Commented Dec 11, 2012 at 20:02
  • I like it to output "23.4567 = 23.4567" or some random or fixed name. Commented Dec 11, 2012 at 20:03
  • The reason is I have two kind of jobs that I'm submitting to clusters. Matlab jobs (echo "matlab -r a=1,b=2 function" | qsub) or shell jobs (echo "program 1 2" | qsub). In the matlab case I need to know the variable names. Sure, I can add another parameter, but just wanted to make sure whether there is a cleaner way to implement it. Commented Dec 11, 2012 at 20:22
  • @MohammadMoghimi there's nothing "unclean" about being explicit :) Commented Dec 11, 2012 at 20:38
  • @JonClements that's what I did at the end! :) Commented Dec 11, 2012 at 22:15

7 Answers 7

8

Not exactly like this. However, you can achieve something similar:

def show(**kwargs):
  print(', '.join('%s=%s' % kv for kv in kwargs.items()))

show(a=20)
Sign up to request clarification or add additional context in comments.

1 Comment

To specifically answer the OP, the call would be show(a=a, otherarg=otherarg, etc=etc), extended as necessary.
6

I like the answer to this question that's found in the Python programming FAQ, quoting Fredrik Lundh:

The same way as you get the name of that cat you found on your porch: the cat (object) itself cannot tell you its name, and it doesn’t really care – so the only way to find out what it’s called is to ask all your neighbours (namespaces) if it’s their cat (object)...

....and don’t be surprised if you’ll find that it’s known by many names, or no name at all!

2 Comments

Yup - gotta love the way the effbot put that :) [having said that though, I believe in one of the Dirk Gently books, one of Dirk's tasks was to try to find out the name of a cat and did achieve it:)!]
I learned more about Python from reading effbot's code than from anything else I've done. He beat the C++ out of me.
5

No, you cannot know what the name was of the local variable used to pass a value to your function.

This is an impossible task in any case. What would be the variable name in the following example?

arguments = ('a', 1, 10)
somefunction(*(arguments[:2] + [10]))

Here we pass in 3 arguments, two taken from a tuple we defined earlier, and one literal value, and all three are passed in using the variable argument list syntax.

11 Comments

My answer would be "no" - with some caveats - and I have a really horrible feeling you could do something with syshooks - but I'm staying with "no" - and "why does it matter that you'd want to?"
@JonClements: you could also try and play with sys._getframe(1) and decompilation of the code frame.. shudder. Not for the faint of heart and I am not going to explore that unless the OP has a damn good reason to do so.
I believe short of profiling/other esoteric purposes - there is none :)
@dash-tom-bang ummm, should be profiling/other/esoteric (or just not have other in there)
@JonClements Ah got it, sorry! My hackles get the best of me when I suspect someone is suggesting that profiling (or unit testing <g>) isn't something that everyone should do!
|
4

I forebode that the following solution will gain several criticisms

def show(*x):
    for el in x:
        fl = None
        for gname,gobj in globals().iteritems():
            if el==gobj:
                print '%s == %r' % (gname,el)
                fl = True
        if not fl:
            print 'There is no identifier assigned to %r in the global namespace' % el

un = 1
y = 'a'
a = 12
b = c = 45
arguments = ('a', 1, 10)
lolo = [45,'a',a,'heat']

print '============================================'
show(12)
show(a)
print '============================================'
show(45)
print
show(b)
print '============================================'
show(arguments)
print
show(('a', 1, 10))
print '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'
show(*arguments)
print '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'
show(*(arguments[1:3] + (b,)))

result

============================================
a == 12
a == 12
============================================
c == 45
b == 45

c == 45
b == 45
============================================
arguments == ('a', 1, 10)

arguments == ('a', 1, 10)
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
y == 'a'
un == 1
There is no identifier assigned to 10 in the global namespace
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
un == 1
There is no identifier assigned to 10 in the global namespace
c == 45
b == 45

2 Comments

+1 for creativity. But it seems a bit limited given that it has to be within the global namespace, and the answer itself seems a bit longwinded. I was hoping someone would do something by looking up the frame stack. Maybe I'll do that myself. Or maybe I'll decide it's not worth it.
I think I've got a superior answer, which searches the caller's local namespace, plus the global namespace, plus the built-ins, handles shadowing properly, and sort-of handles cases where multiple identifiers are assigned to the same value: stackoverflow.com/a/28634996/901641
2

It seems that it's impossible in Python but it's actually possible in C++.

#define show(x)   std::cout << #x << " = " << x << std::endl

Comments

1

New Solution Using readline

If you're in an interactive session, here's an extremely naive solution that will usually work:

def show(x):
    from readline import get_current_history_length, get_history_item
    print(get_history_item(get_current_history_length()).strip()[5:-1] + ' = ' + str(x))

All it does is read the last line input in the interactive session buffer, remove any leading or trailing whitespace, then give you everything but the first five characters (hopefully show() and the last character (hopefully )), thus leaving you with whatever was passed in.

Example:

>>> a = 10
>>> show(a)
a = 10
>>> b = 10
>>> show(b)
b = 10
>>> show(10)
10 = 10
>>> show([10]*10)
[10]*10 = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
>>> show('Hello' + 'World'.rjust(10))
'Hello' + 'World'.rjust(10) = Hello     World

If you're on OS X using the version of Python that comes with it, you don't have readline installed by default, but you can install it via pip. If you're on Windows, readline doesn't exist for you... you might be able to use pyreadline from pip but I've never tried it so I can't say if it's an acceptable substitute or not.

I leave making the above code more bullet-proof as an exercise for the reader. Things to consider would be how to make it handle things like this:

show(show(show(10)))
show(
10
)

If you want this kind of thing to show variables names from a script, you can look into using inspect and getting the source code of the calling frame. But given I can't think of why you would ever want to use show() in a script or why you would complicate the function just to handle people intentionally screwing with it as I did above, I'm not going to waste my time right now figuring it out.

Original Solution Using inspect

Here's my original solution, which is more complicated and has a more glaring set of caveats, but is more portable since it only uses inspect, not readline, so runs on all platforms and whether you're in an interactive session or in a script:

def show(x):

    from inspect import currentframe

    # Using inspect, figure out what the calling environment looked like by merging
    #   what was available from builtin, globals, and locals.
    # Do it in this order to emulate shadowing variables
    #   (locals shadow globals shadow builtins).

    callingFrame = currentframe().f_back
    callingEnv = callingFrame.f_builtins.copy()
    callingEnv.update(callingFrame.f_globals)
    callingEnv.update(callingFrame.f_locals)

    # Get the variables in the calling environment equal to what was passed in.
    possibleRoots = [item[0] for item in callingEnv.items() if item[1] == x]

    # If there are none, whatever you were given was more than just an identifier.
    if not possibleRoots:
        root = '<unnamed>'
    else:
        # If there is exactly one identifier equal to it,
        #   that's probably the one you want.
        # This assumption could be wrong - you may have been given
        #   something more than just an identifier.
        if len(possibleRoots) == 1:
            root = str(possibleRoots[0])
        else:
            # More than one possibility? List them all.
            # Again, though, it could actually be unnamed.
            root = '<'
            for possibleRoot in possibleRoots[:-1]:
                root += str(possibleRoot) + ', '
            root += 'or ' + str(possibleRoots[-1]) + '>'

    print(root + ' = ' + str(x))

Here's a case where it works perfectly (the one from the question):

>>> a = 10
>>> show(a)
a = 10

Here's another fun case:

>>> show(quit)
quit = Use quit() or Ctrl-Z plus Return to exit

Now you know how that functionality was implemented in the Python interpreter - quit is a built-in identifier for a str that says how to properly quit.

Here's a few cases where it's less than you might want, but... acceptable?

>>> b = 10
>>> show(b)
<a, or b> = 10
>>> show(11)
<unnamed> = 11
>>> show([a])
<unnamed> = [10]

And here's a case where it prints out a true statement, but definitely not what you were looking for:

>>> show(10)
<a, or b> = 10

5 Comments

Instead of turning your list of ['a','b'] into <a, or b>, why not since you have the object passed into show(x) as x... just match the id(x) with the corresponding object in globals()? You even have an abbreviated list of keys to search over. The really hard part of this question is when you are looking for the name of a variable that's defined in a local scope -- say a = 10 is defined inside a function and you want to extract the name reference and value from locals() from inside the function.
@MikeMcKerns: Did you try this yourself? When I tried comparing the results of id(x) against each other instead of values, I got the exact same results. I believe ids are tied to objects/values, not identifiers. As a quick and easy thing you can try: a = 10; b = 10; id(a); id(b); id(10) - you'll see that all three have the same id.
@MikeMcKerns: I've updated this to now have two possible solutions. My new one is extremely short and naive and just uses readline. It has a different, more acceptable (to me) list of caveats than my original solution.
the reason a = 10; b = 10; id(10) is id(a) and id(10) is id(b) is that a and b are just name references to the instance of the IntType 10 -- which is pre-built when python starts up. Roughly 1M ints are pre-built. And anyway, a just is a "pointer" (name reference) to 10, so of course they have the same id -- they are the same object. This is why there's no guaranteed way to back track from the value what the name is... it's like trying to get the key that corresponds to a certain value in a dict -- not guaranteed to be unique. That was my point.
Since your code (both variants) is now starting to look like dill.source, you might want to look at dill.pointers as well. There is a way to track down which object points to which object in every case except when multiple name references are pointing the same object -- basically that's to go through the gc module, which allows you to track what the child-parent pointer relationships are.
-2

Here's an answer that only became possible as of Python 3.6 with f-strings:

x = 10
print(f'{x=}')  # Outputs x=10

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.