418

I already read How to get a function name as a string?.

How can I do the same for a variable? As opposed to functions, Python variables do not have the __name__ attribute.

In other words, if I have a variable such as:

foo = dict()
foo['bar'] = 2

I am looking for a function/attribute, e.g. retrieve_name() in order to create a DataFrame in Pandas from this list, where the column names are given by the names of the actual dictionaries:

# List of dictionaries for my DataFrame
list_of_dicts = [n_jobs, users, queues, priorities]
columns = [retrieve_name(d) for d in list_of_dicts] 
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Amelio Vazquez-Reina
  • 91,494
  • 132
  • 359
  • 564
  • 1
    For constants, you can use an enum, which supports retrieving its name. – Dana Aug 23 '17 at 23:00
  • 2
    I came across a similar problem. I realize instead of storing the original data as a list of things, you can store it as a dict of things, (and you can easily generate a list from the dict on the fly if you want). For example, suppose you have: my_dict = {'n_jobs': ..., 'users': ...}. Then you can use my_dict['n_jobs'] in lieu of n_jobs. Anyway, what matters to me is that I only need to type "n_jobs" once, either as a var name or as a string in the dict key. – Ying Zhang Jun 03 '21 at 13:59
  • Variables don't "have names"; "variable" means the same thing as "name" in Python. Presumably you meant *value* (as in, the actual object that is being named); but values don't actually "have" names - rather, they *are named*. In other words, the *name has (holds onto) the value*. Any value could **be named any number of times, including zero**. – Karl Knechtel Oct 15 '22 at 06:08
  • Thanks for the question. I was trying to figure out how to use a variable name in the name of the file to which I'm writing the results (as in writing a dataframe named 'data' to 'data.csv') which is useful in pipelines having intermediate results. Using the variable names allows for clear and automatically generated reference. – Rony Armon Jun 10 '23 at 09:46

35 Answers35

259

With Python 3.8 one can simply use f-string debugging feature:

>>> foo = dict()
>>> f'{foo=}'.split('=')[0]
'foo' 

One drawback of this method is that in order to get 'foo' printed you have to add f'{foo=}' yourself. In other words, you already have to know the name of the variable. In other words, the above code snippet is exactly the same as just

>>> 'foo'
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Aivar Paalberg
  • 4,645
  • 4
  • 16
  • 17
  • 5
    Nice ! And just if you want to get the name of a property instead an object you can `f'{foo=}'.split('=')[0].split('.')[-1]` – Mickael V. Mar 18 '20 at 18:51
  • 156
    How is this useful in anyway ? One can only get the result "foo" if one manually write "foo". It doen't solve OP problem. – Hugo Dec 11 '20 at 08:34
  • 11
    It did address OP problem at the time of answer. This question has 'evolved' over time, there was question which header was in par with content. Then there was update with pandas part, clearly labeled 'Update'. Then it was edited and made part of question and this was left out: `I am looking for a function/attribute, e.g. retrieve_name: retrieve_name(foo) that returns the string 'foo' `. Now anybody who is 'late to the party' can wonder what the heck these answers about... – Aivar Paalberg Dec 11 '20 at 16:10
  • 16
    The point that many seem to be missing (except @ Hugo) is that in order be able to write `f'{foo=}'.split('=')[0]` you obviously **already know the name of the variable**. – martineau Aug 14 '21 at 10:23
  • 8
    @Hugo I was searching for this to save typing. I had a number of pandas dataframes to export to .xlsx, where the name of the dataframe is the name of the sheet in the output excel file like so: vel.to_excel(writer,sheet_name='vel'), vol.to_excel(writer,sheet_name='vol'), area.to_excel(writer,sheet_name='area'). It would have saved typing, reduced the chance for errors, and made the code more readable to drop the dataframes into a list and then do the export in a for loop - for df in df_list: df.to_excel(writer,sheet_name=df.name) or something similar. – Jeremy Matt Aug 28 '21 at 01:01
  • 4
    @AivarPaalberg still f'{foo=}'.split('=')[0] is never useful. In what possible case is it useful ? at this rate the expression "i|have|to|manually|type|foo".split('|')[-1] is equally useful to obtain the string "foo". – Hugo Sep 22 '21 at 14:15
  • Thanks for this answer. This is pretty useful enforcing the integrity of a string value, converting it from a magic string imo. – Nae Dec 09 '21 at 15:35
  • 4
    It does help detect variable name changes. and the IDE will show old values as an error. For example, if one day 'foo' is changed to 'boo', strings like `"foo is a var"` will stay with the old name, which is not good, while `f"{foo=}".split('=')[0] + "is a var"` will have compile error and force the developer to also change the `{foo=}` to `{boo=}`. – motcke Dec 14 '21 at 14:42
  • 1
    I'm surprised. Is this a good practice? I'm about to add it to print the name of the kw argument when rising an error related to it, but I'm wondering if entering debugging mode may crash the code somehow. Thanks a lot! – Miguel Gonzalez Dec 19 '21 at 20:10
  • 2
    @martineau yeah but in that expression, `f'{foo=}'` foo is now connected to any refactoring tools...whereas hard coding the name foo into the string cannot be automatically refactored without manual manipulation. This, in general, is the **reason why** you need reflection. We always know the variable names when we write them initially unless you are up to something awkward. – Chris Mar 01 '22 at 17:26
  • As of May/2022, I tried your answer and it gives me a Syntax Error, because the `=` sign is inside the `f-string`. Maybe is a python version problem? – Chris May 28 '22 at 00:58
  • @Chris *As it says at the beginning of the answer* (and in the documentation linked there), 3.8 is required. (f-strings were introduced with 3.6; they did not initially support the `=` syntax.) Otherwise, follow the usual debugging steps, create a [mre] and ask a new question. – Karl Knechtel Oct 15 '22 at 06:18
  • Is it bad practice what I'm planning to do? I have a NoSQL query like `category="foo` and `query={"category"=category}`. No I could use `query={f"{category=}".split("=")[0]=category}`. Whenever I'm refactoring my variable my query does, too. – TheRuedi Feb 09 '23 at 21:23
162

Even if variable values don't point back to the name, you have access to the list of every assigned variable and its value, so I'm astounded that only one person suggested looping through there to look for your var name.

Someone mentioned on that answer that you might have to walk the stack and check everyone's locals and globals to find foo, but if foo is assigned in the scope where you're calling this retrieve_name function, you can use inspect's current frame to get you all of those local variables.

My explanation might be a little bit too wordy (maybe I should've used a "foo" less words), but here's how it would look in code (Note that if there is more than one variable assigned to the same value, you will get both of those variable names):

import inspect

x, y, z = 1, 2, 3

def retrieve_name(var):
    callers_local_vars = inspect.currentframe().f_back.f_locals.items()
    return [var_name for var_name, var_val in callers_local_vars if var_val is var]

print(retrieve_name(y))

If you're calling this function from another function, something like:

def foo(bar):
    return retrieve_name(bar)

foo(baz)

And you want the baz instead of bar, you'll just need to go back a scope further. This can be done by adding an extra .f_back in the caller_local_vars initialization.

See an example here: ideone

scohe001
  • 15,110
  • 2
  • 31
  • 51
  • 1
    @theodox I absolutely agree, as this will probably act up with `import hooks`, `ironpython`, and `jython` – scohe001 Aug 25 '13 at 04:04
  • 13
    This won't work. atomic variables don't have their name as an attribute. So if you have `a, b = 2, 2`, `retrieve_name(a)` and `retrieve_name(b)` will both return `['a', 'b']` or `['b', 'a']` – tomas Apr 15 '16 at 10:41
  • 3
    @tomas Actually, that is an implementation detail of an optimization of cPython in which integers below 255 are basically singletons, so any variables assigned those values will effectively return `true` for an `is` comparison – Toote Jan 25 '17 at 13:51
  • 2
    is there a mod of this to get the name of a passed var? e.g. `def foo(bar): retrieve_name(bar)` will always return bar, but what if you want `foo(baz)` to return `baz`) instead of `bar`? – SumNeuron Apr 01 '19 at 14:30
  • 2
    @SumNeuron you'd just have to modify the line that assigns `callers_local_vars` to go one scope further back so it'll be looking at the scope of whatever is calling `foo`. Change the line to be `inspect.currentframe().f_back.f_back.f_locals.items()` (notice the extra `f_back`). – scohe001 Apr 01 '19 at 14:55
  • for all the saying it doesn't work, it works perfectly for my use case on Python 3.8. Thank you! – Brad Nov 12 '21 at 18:29
  • This mostly works. I noticed that if the variable is a string with spaces it will not return the variable name; there could be other cases too. Great answer for everything else though! – bryvo01 Nov 17 '21 at 15:58
  • @bryan can you post a minimal example? I can't reproduce that behavior (https://ideone.com/9cVOZk). As long as you have a value that can be compared with `if var_val is var`, this should work fine. – scohe001 Nov 17 '21 at 16:38
  • @scohe001 of course! I used the code you provided (I modified the print statements in foo(bar) to work with python3.x) and created variable a='a string', then ran foo('a string') and it returns an empty list. The value is in f_back.f_locals.items(); I got it in some irrelevant way - irrelevant because your code works for my needs! – bryvo01 Nov 18 '21 at 18:40
  • 1
    @bryvo the issue there is that you're passing the string literal. This is a quirk of how `is` works--`is` is doing identity comparison, which is the reason I used it here. If you want to do equality comparison instead, you can swap `is` for `==` in the return from the `retrieve_name()` function. Here's a simplified example of what's happening to you: https://ideone.com/s6ZCb6 (read more [here](https://stackoverflow.com/a/1504848/2602718)) – scohe001 Nov 19 '21 at 14:38
  • I think the parameter would better be called val instead of var. – Kelly Bundy Dec 21 '21 at 06:40
137

The only objects in Python that have canonical names are modules, functions, and classes, and of course there is no guarantee that this canonical name has any meaning in any namespace after the function or class has been defined or the module imported. These names can also be modified after the objects are created so they may not always be particularly trustworthy.

What you want to do is not possible without recursively walking the tree of named objects; a name is a one-way reference to an object. A common or garden-variety Python object contains no references to its names. Imagine if every integer, every dict, every list, every Boolean needed to maintain a list of strings that represented names that referred to it! It would be an implementation nightmare, with little benefit to the programmer.

Community
  • 1
  • 1
kindall
  • 178,883
  • 35
  • 278
  • 309
  • 9
    Thanks. But why does Python do this for functions then ? (i.e. one type of Python objects) – Amelio Vazquez-Reina Aug 25 '13 at 03:09
  • 18
    Your statement "it simply isn't possible" is False, as @scohe001 [showed](http://stackoverflow.com/a/18425523/623735). Python's variable name database is just like any other relational DB, you can always search for related objects in "reverse" and return the first found or the whole set of valid variable names for any given variable. – hobs Jan 13 '16 at 20:52
  • 4
    @hobs You are technically correct... the best kind of correct. In practice, however, there are so many potential names for an object that it's more trouble than it's worth to try to get them. – kindall Apr 18 '16 at 20:35
  • 2
    @kindall I guess you're right if your "worth it" threshold is O(1). scohe001 's loop would be O(N). – hobs Apr 20 '16 at 18:20
  • 3
    @hobs Well, an object could be a member of a list or a dictionary or other container, or an object attribute... so in that case you'd need to find the name of the containing object too... and that too might be contained by another object... so you may need to recursively find names until you reach an object that can be reached by the current code. That seems like a lot of error-prone code to write and debug for not much benefit. – kindall Apr 20 '16 at 18:28
  • 3
    I'm sure @scohe001 would appreciate it if you linked to his "possibility" near your "not possible" assertion, or even corrected it, since the possibility he offers is correct, simple, and provides what the OP needed (a var in globals() root), which I interpreted as "benefit" that is indeed worth it. – hobs Apr 20 '16 at 23:44
  • 1
    @kindall We do have `UnboundLocalError: "variable_name" does not exist`, so that explanation doesn't hold. – Guimoute Oct 30 '19 at 11:14
  • I would think this is implemented by the interpreter. C# has reflection as a built it. C# is also interpreted (I am interpreting the "JIT" -> intermediate binary step as an interpretation step, since if you are not compiling, you must be interpreting). – Chris Jun 13 '21 at 21:26
99

TL;DR

Use the Wrapper helper from python-varname:

from varname.helpers import Wrapper

foo = Wrapper(dict())

# foo.name == 'foo'
# foo.value == {}
foo.value['bar'] = 2

For list comprehension part, you can do:

n_jobs = Wrapper(<original_value>) 
users = Wrapper(<original_value>) 
queues = Wrapper(<original_value>) 
priorities = Wrapper(<original_value>) 

list_of_dicts = [n_jobs, users, queues, priorities]
columns = [d.name for d in list_of_dicts]
# ['n_jobs', 'users', 'queues', 'priorities']
# REMEMBER that you have to access the <original_value> by d.value

I am the author of the python-varname package. Please let me know if you have any questions or you can submit issues on Github.

The long answer

Is it even possible?

Yes and No.

We are retrieving the variable names at runtime, so we need a function to be called to enable us to access the previous frames to retrieve the variable names. That's why we need a Wrapper there. In that function, at runtime, we are parsing the source code/AST nodes in the previous frames to get the exact variable name.

However, the source code/AST nodes in the previous frames are not always available, or they could be modified by other environments (e.g: pytest's assert statement). One simple example is that the codes run via exec(). Even though we are still able to retrieve some information from the bytecode, it needs too much effort and it is also error-prone.

How to do it?

First of all, we need to identify which frame the variable is given. It's not always simply the direct previous frame. For example, we may have another wrapper for the function:

from varname import varname

def func():
  return varname()

def wrapped():
  return func()

x = wrapped()

In the above example, we have to skip the frame inside wrapped to get to the right frame x = wrapped() so that we are able to locate x. The arguments frame and ignore of varname allow us to skip some of these intermediate frames. See more details in the README file and the API docs of the package.

Then we need to parse the AST node to locate where the variable is assigned value (function call) to. It's not always just a simple assignment. Sometimes there could be complex AST nodes, for example, x = [wrapped()]. We need to identify the correct assignment by traversing the AST tree.

How reliable is it?

Once we identify the assignment node, it is reliable.

varname is all depending on executing package to look for the node. The node executing detects is ensured to be the correct one (see also this).

It partially works with environments where other AST magics apply, including pytest, ipython, macropy, birdseye, reticulate with R, etc. Neither executing nor varname is 100% working with those environments.

Do we need a package to do it?

Well, yes and no, again.

If your scenario is simple, the code provided by @juan Isaza or @scohe001 probably is enough for you to work with the case where a variable is defined at the direct previous frame and the AST node is a simple assignment. You just need to go one frame back and retrieve the information there.

However, if the scenario becomes complicated, or we need to adopt different application scenarios, you probably need a package like python-varname, to handle them. These scenarios may include to:

  1. present more friendly messages when the source code is not available or AST nodes are not accessible
  2. skip intermediate frames (allows the function to be wrapped or called in other intermediate frames)
  3. automatically ignores calls from built-in functions or libraries. For example: x = str(func())
  4. retrieve multiple variable names on the left-hand side of the assignment
  5. etc.

How about the f-string?

Like the answer provided by @Aivar Paalberg. It's definitely fast and reliable. However, it's not at runtime, meaning that you have to know it's foo before you print the name out. But with varname, you don't have to know that variable is coming:

from varname import varname

def func():
  return varname()

# In external uses
x = func() # 'x'
y = func() # 'y'

Finally

python-varname is not only able to detect the variable name from an assignment, but also:

  • Retrieve variable names directly, using nameof
  • Detect next immediate attribute name, using will
  • Fetch argument names/sources passed to a function using argname

Read more from its documentation.

However, the final word I want to say is that, try to avoid using it whenever you can.

Because you can't make sure that the client code will run in an environment where the source node is available or AST node is accessible. And of course, it costs resources to parse the source code, identify the environment, retrieve the AST nodes and evaluate them when needed.

Panwen Wang
  • 3,573
  • 1
  • 18
  • 39
  • 3
    OK, finally got it! Installed using the following `pip3 install python-varname==0.1.5`; imported using `from varname import nameof` – enter_display_name_here Jun 06 '20 at 02:42
  • Somehow the function does not work in a loop: `test = {}` `print(varname.nameof(test))` `for i in [0]:` `print(varname.nameof(test))` The first print gives `test`, the print in the loop raises `VarnameRetrievingError: Callee's node cannot be detected.` – tbrodbeck Jul 17 '20 at 10:14
  • 1
    @Tillus Fixed at `v0.2.0` – Panwen Wang Jul 17 '20 at 18:34
  • This glosses over all kinds of problems and caveats and gives the misleading impression that variable name inspection is something people can expect to "just work". For example, if you try to use `varname.nameof` as the `retrieve_name` function in the question, you get `['d', 'd', 'd', 'd']` for `columns`. – user2357112 Aug 13 '20 at 10:40
  • I am having some issues with import: ```Traceback (most recent call last): File "C:/Users/gtrm/AppData/Roaming/JetBrains/PyCharmCE2020.1/scratches/scratch_56.py", line 2, in from varname import varname, nameof ImportError: cannot import name 'nameof' from 'varname' (C:\Users\gtrm\AppData\Local\Continuum\anaconda3\envs\py38\lib\site-packages\varname\__init__.py)``` – Gustav Rasmussen Aug 13 '20 at 10:41
  • 1
    While the `python-varname` master branch has a `caller` argument for `nameof` that technically allows disambiguating the `columns` example in the question (due to the extra scope created by the list comprehension), this is no help if you instead try to populate `columns` through an ordinary `for` loop. In that case, disambiguation is impossible. Objects carry no information about any "original" or "true" variable. (Also, the `caller` argument is unreleased.) – user2357112 Aug 13 '20 at 10:51
  • @GustavRasmussen Could you submit an issue on Github with the information including which version you are using? – Panwen Wang Aug 13 '20 at 15:58
  • @user2357112supportsMonica The question was updated after my last revision. You are right, with `nameof` in the comprehension, we will get `['d'] * 4`. See my updates. – Panwen Wang Aug 13 '20 at 16:07
  • @user2357112supportsMonica `caller` argument of `nameof` is just a helper to retrieve inner frames. It will not be exposed. And `nameof` is only to work with the desired variables directly. – Panwen Wang Aug 13 '20 at 16:09
  • Just to comment, this doesn't seem to work when the argument is the name of a PyQT5 widget, it causes the application to crash. – Jkind9 Feb 27 '21 at 20:03
  • @Jkind9 Would you submit an issue? – Panwen Wang Feb 28 '21 at 05:53
  • 1
    @PanwenWang, would you mind adding `varname` to any annaconda channel ? – Jiadong Apr 06 '21 at 02:07
  • 2
    @ted930511 I don't mind if anyone else adding it to any conda channel. And it is welcome to be a contributor to the project by adding it to a channel and keeping it up-to-date. – Panwen Wang Apr 07 '21 at 04:50
  • `varname` doesn't support python2.7 - so why would one use it over e.g. an f-string? – jtlz2 Jun 17 '21 at 06:37
  • @jtlz2 f-string can't get the variable name from inside a function like this `var = func()`. – Panwen Wang Jun 18 '21 at 06:08
  • this doesn't seem to work in juypter notebook, i had this error ```VarnameRetrievingError: Couldn't retrieve the call node. This may happen if you're using some other AST magic at the same time, such as pytest, ipython, macropy, or birdseye.``` – FrankZhu Jun 27 '22 at 20:25
  • @FrankZhu `varname` is tested with jupyter. Could you submit an issue to the repo with some detailed information (including OS, python version, jupyter version, etc)? – Panwen Wang Jun 28 '22 at 19:30
  • Thanks for the in-depth review. python-varname's nameof seems as a straight forward solution as in from varname import nameof var = 1 print(nameof(var), type(nameof(var))) – Rony Armon Jun 10 '23 at 09:43
50

On python3, this function will get the outer most name in the stack:

import inspect


def retrieve_name(var):
        """
        Gets the name of var. Does it from the out most frame inner-wards.
        :param var: variable to get name from.
        :return: string
        """
        for fi in reversed(inspect.stack()):
            names = [var_name for var_name, var_val in fi.frame.f_locals.items() if var_val is var]
            if len(names) > 0:
                return names[0]

It is useful anywhere on the code. Traverses the reversed stack looking for the first match.

juan Isaza
  • 3,646
  • 3
  • 31
  • 37
33

I don't believe this is possible. Consider the following example:

>>> a = []
>>> b = a
>>> id(a)
140031712435664
>>> id(b)
140031712435664

The a and b point to the same object, but the object can't know what variables point to it.

cgnorthcutt
  • 3,890
  • 34
  • 41
korylprince
  • 2,969
  • 1
  • 18
  • 27
  • 2
    Surely the relationship can be made two-way by extending [reference counting](https://en.m.wikipedia.org/wiki/Reference_counting). This [answer](https://stackoverflow.com/a/51347986/7195043) (and some others) even provides an implementation. – shayaan Nov 15 '18 at 15:22
18
>> my_var = 5
>> my_var_name = [ k for k,v in locals().items() if v == my_var][0]
>> my_var_name 
'my_var'

In case you get an error if myvar points to another variable, try this (suggested by @mherzog)-

 >> my_var = 5
>> my_var_name = [ k for k,v in locals().items() if v is my_var][0]
>> my_var_name 
'my_var'

locals() - Return a dictionary containing the current scope's local variables. by iterating through this dictionary we can check the key which has a value equal to the defined variable, just extracting the key will give us the text of variable in string format.

from (after a bit changes) https://www.tutorialspoint.com/How-to-get-a-variable-name-as-a-string-in-Python

Anshu Pandey
  • 209
  • 2
  • 6
  • 1
    The ```if``` statement in your code gives an error in if ```myvar``` points to another variable. However, it works beautifully if you use ```is`` rather than ```==``. I.e. change to ```my_var_name = [ k for k,v in locals().items() if v is e][0]``` ``` – mherzog Aug 22 '20 at 13:42
  • 1
    @mherzog this "is" work, but if we want to know the pointed variable name instead of the index variable name, we may need to pick [1] or other – Frank Nov 30 '20 at 04:16
  • is there a way of extending this to global vars instead of just locals? – markgalassi Mar 24 '21 at 21:32
16
def name(**variables):
    return [x for x in variables]

It's used like this:

name(variable=variable)
fdvfcges
  • 402
  • 3
  • 7
  • 16
    This will not return the name of the desired variable, but "variables". For example - using `name(variable=variable)` will output `['variable']`, and using `name(variable=another_variable)` will *not* output `['another_variable']` but rather `['variable']`. – DalyaG May 10 '18 at 12:33
  • 1
    Actually it works as expected. You just have to replace both «variable»s with your variable. It will return a one-element list with a string of the **first** variable's name. For example: `>>> a = []` `>>> b = a` `>>> name(a=b)` `['a']` `>>> name(b=a)` `['b']` ` – Benur21 Nov 28 '19 at 22:13
  • 2
    This is just a backward way of writing varname = 'variable'. The first "variable" in the name function isn't a variable but a keyword name. It's the equivalent of writing ```lambda name, var: name``` – Or Sharir Dec 04 '20 at 13:44
  • 1
    I agree with Sharir. OP's problem is to have a function name_of, that can be use to iterate through a list and get the variable name associated with each value of the list. – Hugo Dec 11 '20 at 13:49
14

I wrote the package sorcery to do this kind of magic robustly. You can write:

from sorcery import dict_of

columns = dict_of(n_jobs, users, queues, priorities)

and pass that to the dataframe constructor. It's equivalent to:

columns = dict(n_jobs=n_jobs, users=users, queues=queues, priorities=priorities)
Alex Hall
  • 34,833
  • 5
  • 57
  • 89
  • I was so hopeful but for a syntax error under python2.7 - is there a version that supports this? :/ – jtlz2 Jun 18 '21 at 08:41
  • @jtlz2 sorcery uses https://github.com/alexmojaki/executing which works in Python 2. Use that to get the call node, then dict_of itself is pretty simple https://github.com/alexmojaki/sorcery/blob/master/sorcery/spells.py#L245 – Alex Hall Jun 18 '21 at 09:47
  • this is really what i want. good job. – WilsonF Jun 13 '22 at 04:48
9

Here's one approach. I wouldn't recommend this for anything important, because it'll be quite brittle. But it can be done.

Create a function that uses the inspect module to find the source code that called it. Then you can parse the source code to identify the variable names that you want to retrieve. For example, here's a function called autodict that takes a list of variables and returns a dictionary mapping variable names to their values. E.g.:

x = 'foo'
y = 'bar'
d = autodict(x, y)
print d

Would give:

{'x': 'foo', 'y': 'bar'}

Inspecting the source code itself is better than searching through the locals() or globals() because the latter approach doesn't tell you which of the variables are the ones you want.

At any rate, here's the code:

def autodict(*args):
    get_rid_of = ['autodict(', ',', ')', '\n']
    calling_code = inspect.getouterframes(inspect.currentframe())[1][4][0]
    calling_code = calling_code[calling_code.index('autodict'):]
    for garbage in get_rid_of:
        calling_code = calling_code.replace(garbage, '')
    var_names, var_values = calling_code.split(), args
    dyn_dict = {var_name: var_value for var_name, var_value in
                zip(var_names, var_values)}
    return dyn_dict

The action happens in the line with inspect.getouterframes, which returns the string within the code that called autodict.

The obvious downside to this sort of magic is that it makes assumptions about how the source code is structured. And of course, it won't work at all if it's run inside the interpreter.

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Zachary Ernst
  • 91
  • 1
  • 2
9

This function will print variable name with its value:

import inspect

def print_this(var):
    callers_local_vars = inspect.currentframe().f_back.f_locals.items()
    print(str([k for k, v in callers_local_vars if v is var][0])+': '+str(var))
***Input & Function call:***
my_var = 10

print_this(my_var)

***Output**:*
my_var: 10
Community
  • 1
  • 1
Dilip Lilaramani
  • 1,126
  • 13
  • 28
8
>>> locals()['foo']
{}
>>> globals()['foo']
{}

If you wanted to write your own function, it could be done such that you could check for a variable defined in locals then check globals. If nothing is found you could compare on id() to see if the variable points to the same location in memory.

If your variable is in a class, you could use className.dict.keys() or vars(self) to see if your variable has been defined.

GetBackerZ
  • 448
  • 5
  • 12
blakev
  • 4,154
  • 2
  • 32
  • 52
  • What if the name is in a caller frame? Then you'd have to silly things like walking the stack and checking everyone's `locals` and `globals`...and you risk getting the name wrong if none actually exist. It's a ton of work for no actual useful gain. – nneonneo Aug 25 '13 at 03:22
  • the whole question is silly...but if it's something he wanted to do, it is possible. As far as checking existence you could use Globals().setdefault(var, – blakev Aug 25 '13 at 03:26
6

I have a method, and while not the most efficient...it works! (and it doesn't involve any fancy modules).

Basically it compares your Variable's ID to globals() Variables' IDs, then returns the match's name.

def getVariableName(variable, globalVariables=globals().copy()):
    """ Get Variable Name as String by comparing its ID to globals() Variables' IDs

        args:
            variable(var): Variable to find name for (Obviously this variable has to exist)

        kwargs:
            globalVariables(dict): Copy of the globals() dict (Adding to Kwargs allows this function to work properly when imported from another .py)
    """
    for globalVariable in globalVariables:
        if id(variable) == id(globalVariables[globalVariable]): # If our Variable's ID matches this Global Variable's ID...
            return globalVariable # Return its name from the Globals() dict
Amr Sharaki
  • 63
  • 1
  • 4
4

In Python, the def and class keywords will bind a specific name to the object they define (function or class). Similarly, modules are given a name by virtue of being called something specific in the filesystem. In all three cases, there's an obvious way to assign a "canonical" name to the object in question.

However, for other kinds of objects, such a canonical name may simply not exist. For example, consider the elements of a list. The elements in the list are not individually named, and it is entirely possible that the only way to refer to them in a program is by using list indices on the containing list. If such a list of objects was passed into your function, you could not possibly assign meaningful identifiers to the values.

Python doesn't save the name on the left hand side of an assignment into the assigned object because:

  1. It would require figuring out which name was "canonical" among multiple conflicting objects,
  2. It would make no sense for objects which are never assigned to an explicit variable name,
  3. It would be extremely inefficient,
  4. Literally no other language in existence does that.

So, for example, functions defined using lambda will always have the "name" <lambda>, rather than a specific function name.

The best approach would be simply to ask the caller to pass in an (optional) list of names. If typing the '...','...' is too cumbersome, you could accept e.g. a single string containing a comma-separated list of names (like namedtuple does).

korylprince
  • 2,969
  • 1
  • 18
  • 27
nneonneo
  • 171,345
  • 36
  • 312
  • 383
4

I think it's so difficult to do this in Python because of the simple fact that you never will not know the name of the variable you're using. So, in his example, you could do:

Instead of:

list_of_dicts = [n_jobs, users, queues, priorities]

dict_of_dicts = {"n_jobs" : n_jobs, "users" : users, "queues" : queues, "priorities" : priorities}
Martin Evans
  • 45,791
  • 17
  • 81
  • 97
Joe Camel
  • 49
  • 3
4

Many of the answers return just one variable name. But that won't work well if more than one variable have the same value. Here's a variation of Amr Sharaki's answer which returns multiple results if more variables have the same value.

def getVariableNames(variable):
    results = []
    globalVariables=globals().copy()
    for globalVariable in globalVariables:
        if id(variable) == id(globalVariables[globalVariable]):
            results.append(globalVariable)
    return results

a = 1
b = 1
getVariableNames(a)
# ['a', 'b']
spidermila
  • 41
  • 3
3

just another way to do this based on the content of input variable:

(it returns the name of the first variable that matches to the input variable, otherwise None. One can modify it to get all variable names which are having the same content as input variable)

def retrieve_name(x, Vars=vars()):
    for k in Vars:
        if isinstance(x, type(Vars[k])):
            if x is Vars[k]:
                return k
    return None
ses
  • 128
  • 5
2

If the goal is to help you keep track of your variables, you can write a simple function that labels the variable and returns its value and type. For example, suppose i_f=3.01 and you round it to an integer called i_n to use in a code, and then need a string i_s that will go into a report.

def whatis(string, x):
    print(string+' value=',repr(x),type(x))
    return string+' value='+repr(x)+repr(type(x))
i_f=3.01
i_n=int(i_f)
i_s=str(i_n)
i_l=[i_f, i_n, i_s]
i_u=(i_f, i_n, i_s)

## make report that identifies all types
report='\n'+20*'#'+'\nThis is the report:\n'
report+= whatis('i_f ',i_f)+'\n'
report+=whatis('i_n ',i_n)+'\n'
report+=whatis('i_s ',i_s)+'\n'
report+=whatis('i_l ',i_l)+'\n'
report+=whatis('i_u ',i_u)+'\n'
print(report)

This prints to the window at each call for debugging purposes and also yields a string for the written report. The only downside is that you have to type the variable twice each time you call the function.

I am a Python newbie and found this very useful way to log my efforts as I program and try to cope with all the objects in Python. One flaw is that whatis() fails if it calls a function described outside the procedure where it is used. For example, int(i_f) was a valid function call only because the int function is known to Python. You could call whatis() using int(i_f**2), but if for some strange reason you choose to define a function called int_squared it must be declared inside the procedure where whatis() is used.

2

Maybe this could be useful:

def Retriever(bar):
    return (list(globals().keys()))[list(map(lambda x: id(x), list(globals().values()))).index(id(bar))]

The function goes through the list of IDs of values from the global scope (the namespace could be edited), finds the index of the wanted/required var or function based on its ID, and then returns the name from the list of global names based on the acquired index.

Reitsch-Z2
  • 21
  • 2
  • Incredibly scary looking but it works and it works pretty fast. `4.41 us +/- 1.2 us per loop (mean +/- std. dev. of 7 runs, 100000 loops each` for just one variable and function. Runtime does increase slowly as defined variable count increases. 21us with 103 defined in iPython. Probably not ideal for very large environments but maybe there’s a way to optimize…? – Kamikaze Rusher Feb 08 '22 at 15:00
  • Ok, there appears to be some drawbacks with this. Take for example two variables assigned the value of 0 and they’re assigned to a tuple, ie `t = (var1, var2)`. While it’s possible to use this to get the variable name assigned to a tuple slot, the slot for `var2` will return a variable name of `var1`. I believe this is because constants are pre-generated and assigned a slot in memory as an optimization technique, so the reverse lookup will point to the oldest existing variable assigned the value. As a result, I don’t recommend using this for large projects due to the issue of inaccuracy. – Kamikaze Rusher Feb 08 '22 at 17:03
2

Whenever I have to do it, mostly while communicating json schema and constants with the frontend I define a class as follows

class Param:
    def __init__(self, name, value):
        self.name = name
        self.value = value

Then define the variable with name and value.

frame_folder_count = Param({'name':'frame_folder_count', 'value':10})

Now you can access the name and value using the object.

>>> frame_folder_count.name
'frame_folder_count'
rusty
  • 652
  • 7
  • 21
  • I don't think that this matches the question. But anyway I want to suggest you using `pydantic`, it's really good for that type of usage – ccvhd Apr 02 '21 at 08:17
  • Your code doesn't run. should be on the lines of `Param('frame_folder_count', 10)` or `Param(**{'name':'frame_folder_count', 'value':10})` – gonzalo mr Oct 04 '21 at 11:15
2
>>> def varname(v, scope=None):
        d = globals() if not scope else vars(scope); return [k for k in d if d[k] == v]
...
>>> d1 = {'a': 'ape'}; d2 = {'b': 'bear'}; d3 = {'c': 'cat'}
>>> ld = [d1, d2, d3]
>>> [varname(d) for d in ld]
[['d1'], ['d2'], ['d3']]
>>> d5 = d3
>>> [varname(d) for d in ld]
[['d1'], ['d2'], ['d3', 'd5']]
>>> def varname(v, scope=None):
        d = globals() if not scope else vars(scope); return [k for k in d if d[k] is v]
...
>>> [varname(d) for d in ld]
[['d1'], ['d2'], ['d3', 'd5']]

As you see and is noted here, there can be multiple variables with the same value or even address, so using a wrapper to keep the names with the data is best.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Cees Timmerman
  • 17,623
  • 11
  • 91
  • 124
1

Following method will not return the name of variable but using this method you can create data frame easily if variable is available in global scope.

class CustomDict(dict):
    def __add__(self, other):
        return CustomDict({**self, **other})

class GlobalBase(type):
    def __getattr__(cls, key):
        return CustomDict({key: globals()[key]})

    def __getitem__(cls, keys):
        return CustomDict({key: globals()[key] for key in keys})

class G(metaclass=GlobalBase):
    pass

x, y, z = 0, 1, 2

print('method 1:', G['x', 'y', 'z']) # Outcome: method 1: {'x': 0, 'y': 1, 'z': 2}
print('method 2:', G.x + G.y + G.z) # Outcome: method 2: {'x': 0, 'y': 1, 'z': 2}

A = [0, 1]
B = [1, 2]
pd.DataFrame(G.A + G.B) # It will return a data frame with A and B columns
user5828964
  • 302
  • 2
  • 10
1

Some of the previous cases would fail if there are two variables with the same value. So it is convenient to alert it:

Defining function:

# Variable to string of variable name

def var_name(variable,i=0):

  results = []
  for name in globals():   
     if eval(name) == variable:
       results.append(name)

  if len(results) > 1:
    print('Warning:' )
    print('   var_name() has found',len(results), 'possible outcomes.')
    print('   Please choose the suitable parameter "i". Where "i" is the index')
    print('   that matches your choice from the list below.')
    print('  ',results) ; print('')

  return results[i]

Use:

var_1 = 10
var_name(var_1) # Output will be "var_1"

If you have 2 variables with the same value like var_1 = 8 and var_2 = 8, then a warning will appear.

var_1 = 8
var_2 = 8
var_name(var_2)  # Output will be "var_1" too but Warning will appear
1

You can get your variable as kwargs and return it as string:

var=2
def getVarName(**kwargs):
    return list(kwargs.keys())[0]

print (getVarName(var = var))

Note: variable name must be equal to itself.

  • This answer is so underrated! Works beautifully with `var=None`, `var=''`, `var=_` ... . Tried with globals() and .__repr__ and id() and ... which didn't work with the odd test variables. Well answered! – user319436 Dec 04 '22 at 03:28
  • Not working, s. `print([getVarName(v=v) for v in [v1, v2, v3]])` – Jaja Mar 03 '23 at 10:55
1

How can I do the same for a variable? As opposed to functions, Python variables do not have the __name__ attribute.

The problem comes up because you are confused about terminology, semantics or both.

"variables" don't belong in the same category as "functions". A "variable" is not a thing that takes up space in memory while the code is running. It is just a name that exists in your source code - so that when you're writing the code, you can explain which thing you're talking about. Python uses names in the source code to refer to (i.e., give a name to) values. (In many languages, a variable is more like a name for a specific location in memory where the value will be stored. But Python's names actually name the thing in question.)

In Python, a function is a value. (In some languages, this is not the case; although there are bytes of memory used to represent the actual executable code, it isn't a discrete chunk of memory that your program logic gets to interact with directly.) In Python, every value is an object, meaning that you can assign names to it freely, pass it as an argument, return it from a function, etc. (In many languages, this is not the case.) Objects in Python have attributes, which are the things you access using the . syntax. Functions in Python have a __name__ attribute, which is assigned when the function is created. Specifically, when a def statement is executed (in most languages, creation of a function works quite differently), the name that appears after def is used as a value for the __name__ attribute, and also, independently, as a variable name that will get the function object assigned to it.

But most objects don't have an attribute like that.

In other words, if I have a variable such as:

That's the thing: you don't "have" the variable in the sense that you're thinking of. You have the object that is named by that variable. Anything else depends on the information incidentally being stored in some other object - such as the locals() of the enclosing function. But it would be better to store the information yourself. Instead of relying on a variable name to carry information for you, explicitly build the mapping between the string name you want to use for the object, and the object itself.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
0

I try to get name from inspect locals, but it cann't process var likes a[1], b.val. After it, I got a new idea --- get var name from the code, and I try it succ! code like below:

#direct get from called function code
def retrieve_name_ex(var):
    stacks = inspect.stack()
    try:
        func = stacks[0].function
        code = stacks[1].code_context[0]
        s = code.index(func)
        s = code.index("(", s + len(func)) + 1
        e = code.index(")", s)
        return code[s:e].strip()
    except:
        return ""
Ryan
  • 1
  • 1
0

You can try the following to retrieve the name of a function you defined (does not work for built-in functions though):

import re
def retrieve_name(func):
    return re.match("<function\s+(\w+)\s+at.*", str(func)).group(1)

def foo(x):
    return x**2

print(retrieve_name(foo))
# foo
Sandipan Dey
  • 21,482
  • 2
  • 51
  • 63
0

When finding the name of a variable from its value,
you may have several variables equal to the same value,
for example var1 = 'hello' and var2 = 'hello'.

My solution:

def find_var_name(val):

    dict_list = []
    global_dict = dict(globals())

    for k, v in global_dict.items():
        dict_list.append([k, v])
   
    return [item[0] for item in dict_list if item[1] == val]

var1 = 'hello'
var2 = 'hello'
find_var_name('hello')

Outputs

['var1', 'var2']
Alex Ricciardi
  • 181
  • 1
  • 9
0

Compressed version of iDilip's answer:

import inspect
def varname(x):
  return [k for k,v in inspect.currentframe().f_back.f_locals.items() if v is x][0]

hi = 123
print(varname(hi))
étale-cohomology
  • 2,098
  • 2
  • 28
  • 33
0

It's totally possible to get the name of an instance variable, so long as it is the property of a class.

I got this from Effective Python by Brett Slatkin. Hope it helps someone:

The class must implement the get, set, and set_name dunder methods, which are part of the "Descriptor Protocol"

This worked when I ran it:

class FieldThatKnowsItsName():
    def __init__(self):
        self.name = None
        self._value= None
        self.owner = None
 
    def __set_name__(self, owner, name):
        self.name = name
        self.owner = owner
        self.owner.fields[self.name] = self

    def __get__(self, instance, instance_type):
        return self

    def __set__(self, instance, value):
        self = value

class SuperTable:
    fields = {}
    field_1=FieldThatKnowsItsName()
    field_2=FieldThatKnowsItsName()

table = SuperTable()
print(table.field_1.name)
print(table.field_2.name)

You can then add methods and or extend your datatype as you like.

As a bonus, the set_name(self, owner, name) dunder also passes the parent instance, so the Field class instance can register itself with the parent.

I got this from Effective Python by Brett Slatkin. It took a while to figure out how to implement.

Dharman
  • 30,962
  • 25
  • 85
  • 135
0

If you already have a list of dataframes, as is stated in the comments, then it is easy enough to make that list into a list of strings. Instead of going from list of variables to strings, go the other way, a list of strings to variables with the builtin exec function combined with f-strings (assuming that the variables are already assigned, i.e. vel, vol, and area are variable names of existing pandas dataframes):

If:

dfstr_list = [
              'vel',
              'vol',
              'area'
             ]

This iterates through the list and uses each dataframe to write to excel, and defines the sheetname:

for dfstr in dfstr_list:
    if dfstr in globals():
        exec(f"{dfstr}.to_excel(writer,sheet_name=dfstr)")
wisedesign
  • 21
  • 2
0
from __future__ import annotations
import inspect
import pandas as pd


# 변수 이름 가져오기
def getVariableName(variable: str) -> (str | Exception):
    callers_local_vars = inspect.currentframe().f_back.f_locals.items()

    df = pd.DataFrame(callers_local_vars)
    df.columns = ['함수명', '값']

    try:
        return df[(df['값'] == variable)].iloc[0]['함수명']
    except Exception as e:
        return e


test = 'Hello World!'

if __name__ == '__main__':
    print(getVariableName(test))
Hong승원
  • 122
  • 6
0

You can dynamically get the name of the variable by updating the 'globals' variable within a function, and tracking the variables defined within a block . Here is an example :

keys1 = set(globals().keys())
a = 1
b = 1 
c = 2
d = (a,b)
keys2 = set(globals().keys())
vals = [a,b,c,d]
new_keys = [ key for key in new_keys if globals()[key] in vals]
new_keys = keys2.difference(keys1)
new_keys = [ key for key in new_keys if globals()[key] in vals]
print(new_keys)
myg = globals()
for key in new_keys:
    myg[key] = (val,key) # now the variable points also to the key
for key in new_keys:
    myg[key] = (globals()[key],key)
# now the variable points also to the key
print(a,b,c,d)

And once you got the names of your variable, you can modify again the globals variable to get restore the values of the variables. This can be done using a context manager.

Remark, you cannot modify all the variables of globals(), as it might cause some issues , this is why I restricted it using variables defined on globals.

Also , the full solution is not necessarily thread safe,and the variables defined within the context must be freed before to be assigned again, in order for the "new_keys" to contains al least the names of the new variables.

am ar
  • 13
  • 3
0

I think if we use the global.items() or the local.items() functions, we will be able to get the variable name. In the current scenario, the dictionaries are inside a list so we need to see whether the dictionary that we get as part of the iteration matches with the dictionary in the list. If they match, then get the name of the dictionary.

# Iterate over all global/local variables

for name, value in locals().items():

    for i, mydict in enumerate(list_of_dicts):

        # Check if the value matches the dictionary we're interested in
        if value is mydict:

            # Print the variable name as a string
            print("Variable name:", name)
ChrisGPT was on strike
  • 127,765
  • 105
  • 273
  • 257
Sajeev A
  • 15
  • 1
  • 4
0

I use this little code snippet for debugging macros

from inspect import currentframe, getframeinfo_
def p(var): # print variable and its name
    in_fo = getframeinfo(currentframe().f_back)
    co_de = str(in_fo.code_context)
    na_me = co_de[
    co_de.index('(')+1:co_de.index(')')]
    print("Line "+str(in_fo.lineno)+": "
    +na_me+" is "+str(var))

The odd names with _ in the middle are to reduce the risk of aliasing 'var'. p(r1) produces

Line 154: r1 is 13.999013120731464

Richard Parkins
  • 347
  • 2
  • 13
  • If you replace the second 'index' by 'rindex', the function will behave correctly if the argument is an expression, at least unless the expression contains any )'s inside strings. To print multiple arguments with their names, use index(', '). – Richard Parkins May 09 '23 at 17:34