1

Caution: This question is deceptively simple in its wording, but it might require an extra intellectual effort to answer it. Trying to take an approach as in How can I get the name of an object in Python? is not a valid answer (you may want to read it first). That approach via an object is, by definition, ambiguous, since any object could be referenced by several names or even none. The approach recommended requires to reach back to the step of parsing, where objects didn't have any existence yet.

Background: I am exploring how to create constructs in Python that allow us to do away with some redundancies, e.g. when doing unit tests:

y = 5
print("%s: %s" % ('y', y))

It would be better to write:

y = 5    
print(info(y)) => "Value of 'y' is 5"

Note: This could be solved the other way round, by passing a string and then converting it to an id, e.g. print(info('y') using something like globals()['y']. But here we want to go from the id to the string, because there are cases when it it is simpler to do it this way, e.g. when a test routine is passed a list of variables to inspect.

Question: Given an identifier called x in source code, is it possible to write a symbol function that will return the string corresponding to x?

Semantically, if I write

x = [1, 2, 3, 4] # whatever object

It is obvious from the source code that x is the string 'x'.

In other words, is it possible (in standard Python) to write a symbol function (just to coin a name), which given x, considers it as symbol at the exact place where the function is called, and returns the string 'x'?

x = [1, 2, 3, 4] # whatever object
y = x
symbol(x) = 'x'
symbol(y) = 'y'

The answer is unambiguous in this way: x and y were used as symbols in the source code when the function was called, so the result of symbol must be the string equivalent to the symbol ('x' and 'y' respectively).

I realize that this might be a good piece of 2nd degree introspection work (to find the place in source code (AST?) where the function was called and then to reason with it). Yet could it be done with Python as it exists today?

So if the proposed symbol function works, one should be able to write (typically for a unit test):

   x = 5
   y = x
   print("The value of %s is %s", symbol(x), x) => 5
   print("The value of %s is %s", symbol(y), y) => 5

Then the info function could be written as:

def info(y):
    return("%s: %s" % (symbol(y), y))

Note 1: for the sake of generality, the solution should work in standard Python, but answers for other implementations are also acceptable.

Note 2: If you feel that 'symbol' is not the right name for this function or concept, you are welcome to point this out.

Note 3: The is a precise question: given that symbol Y is used in code, write a symbol function, which gives symbol(Y) = 'Y', regardless of object on which the symbol is pointing (class, object, etc.).

fralau
  • 3,279
  • 3
  • 28
  • 41
  • It *is* a duplicate. The answer to your question lies in the other thread: "The objects itself do not know what names you are using for it". – bfontaine Nov 03 '17 at 15:29
  • 1
    I'm voting to reopen this question because it's not asking about functions like [the selected duplicate](https://stackoverflow.com/questions/1538342/how-can-i-get-the-name-of-an-object-in-python), and it has a clear definition of what the expected output is (unlike other "what's the name of this value" questions I've seen). The question is essentially "how can I read the source code that was used to call my function", and that is clearly a different question than "how can I get an object's name". – Aran-Fey Nov 03 '17 at 20:43
  • @Rawing Even if it's a different question, it strikes me as [pretty broad](https://meta.stackoverflow.com/a/284237/712526). – jpaugh Nov 03 '17 at 23:00
  • 1
    In order to recover this question please show an example of how you intend to use it. I can't come up with a definition for what this does that isn't subject to several reasoning flaws that make the answer useless. – Joshua Nov 04 '17 at 02:07
  • I suggest you take a fresh look at the question and and the answers, to realize this is a new (and rather advanced) thread. – fralau Nov 04 '17 at 06:45
  • @jpaugh : it is a *precise* question: given that symbol Y is used in code, write a symbol function, which gives symbol(Y) = 'Y', **regardless of the value of the symbol**. I hope it is clear for everyone that **we are not interested in finding an object's name**, because we are not interested in the object. – fralau Nov 04 '17 at 06:49
  • 1
    @bfontaine : I reworded my question to make it crystal clear that it is not about the name of an object. – fralau Nov 04 '17 at 07:45
  • The idea is interesting, but start the question by your purpose. Are you contributing to a debugger? Are you writing a better [Sympy : Symbolic Mathematics in Python](http://www.scipy-lectures.org/advanced/sympy.html#symbols)? The code looks sometimes `funcname(` "\n" long expression "\n" `)`, where is no name. It is much easier to call the function by string and to get the local variable of that name in the context of the parent frame or the global variable, than to get the current executed column in the line in source code. Simplify the bold paragraphs and move it to the end – hynekcer Nov 04 '17 at 16:19
  • @fralau I agree, but [precision and narrowness are not the same.](https://meta.stackoverflow.com/q/284236/712526) Asking someone else to implement an algorithm, rather than asking what's wrong with yours is what makes it broad. – jpaugh Nov 06 '17 at 15:02
  • FTR, I think this is a *fun* problem, and I'm not sure what would prevent you from writing an attempt yourself. Focusing on a specific piece of code, and asking, "Where did I go wrong?" is often vital to getting high-quality answers. – jpaugh Nov 06 '17 at 15:04

2 Answers2

2

This can be done by reading the source code that calls the symbol function:

import inspect
import re

def symbol(arg):
    frame = inspect.currentframe().f_back
    frameinfo = inspect.getouterframes(frame)[0]
    calling_code = frameinfo[4][frameinfo[5]].strip()
    del frame

    # this adds support for multiple calls in the same line
    matches = re.findall(r'\bsymbol\((\w+)\)', calling_code)
    match = matches[symbol.call_count]
    symbol.call_count += 1
    if symbol.call_count == len(matches):
        symbol.call_count = 0
    return match

symbol.call_count = 0
x = 3
print(symbol(x)) # output: x

Caveats:

  • Doesn't work in an interactive session or in any other scenario where the source code cannot be read
  • Can get tripped up by strings:

    fake = "symbol(fake)"; print(symbol(5))
    # output: fake
    
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
  • Thanks a lot, this was fast and impressive! I guess there migh be a way to improve on the parsing of the calling code to avoid your second caveat? – fralau Nov 03 '17 at 09:04
  • @fralau I think there are only hackish ways to work around the 2nd caveat. AFAIK there's no way to find the calling code any more precisely than _"it's somewhere in this line"_. The only workaround I can think of is to maintain a counter how often `symbol` has been called - when it's called the first time, return "symbol(1)". When it's called the 2nd time, return "symbol(2)" and reset the counter. – Aran-Fey Nov 03 '17 at 09:08
  • Nice! `sys._getframe` and `inspect` were my next things to investigate. – CristiFati Nov 03 '17 at 09:26
  • @Rawing: thanks for your help: it seems I have been spending unncessary time explaining my question, while you understood it at once! – fralau Nov 04 '17 at 08:22
0

Not sure if I got the question right, but here's an example that searches for symbols in [Python]: globals():

import sys

x = [1, 2, 33]


def dummy():
    pass


def symbol(obj):
    globals_dict = globals()
    for name in globals_dict:
        if obj is globals_dict[name]:
            return name


def main():
    for obj in [sys, x, dummy, symbol, main]:
        obj_name = symbol(obj)
        print(obj_name, type(obj_name))


if __name__ == "__main__":
    main()

Output:

E:\Work\Dev\StackOverflow\q47091183>"E:\WinOBT\1.0.0.0\OPSWpython\2.7.10__00\x86\python.exe" a.py
('sys', <type 'str'>)
('x', <type 'str'>)
('dummy', <type 'str'>)
('symbol', <type 'str'>)
('main', <type 'str'>)
CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • Thanks a lot and this is getting the thing started. The problem I wish to solve is that if *both* x and y refer to the same string, the function can only determine which one I *really* want by looking at the source code (since that's where I said that I wanted x => 'x'). – fralau Nov 03 '17 at 08:38
  • I imagined that it shouldn't be that simple. So what would `symbol(y)` from your code return? Also, how do you tell the function _which object you really want_? – CristiFati Nov 03 '17 at 08:44
  • I understand. The answer is to be found in the source code: ```symbol(y) => 'y'```. The content of the object is not relevant. – fralau Nov 03 '17 at 08:45