In Python, is there a way for an instance of an object to see the variable name it\'s assigned to? Take the following for example:
class MyObject(object):
I was independently working on this and have the following. It's not as comprehensive as driax's answer, but efficiently covers the case described and doesn't rely on searching for the object's id in global variables or parsing source code...
import sys
import dis
class MyObject:
def __init__(self):
# uses bytecode magic to find the name of the assigned variable
f = sys._getframe(1) # get stack frame of caller (depth=1)
# next op should be STORE_NAME (current op calls the constructor)
opname = dis.opname[f.f_code.co_code[f.f_lasti+2]]
if opname == 'STORE_NAME': # not all objects will be assigned a name
# STORE_NAME argument is the name index
namei = f.f_code.co_code[f.f_lasti+3]
self.name = f.f_code.co_names[namei]
else:
self.name = None
x = MyObject()
x.name == 'x'
Yes, it is possible*. However, the problem is more difficult than it seems upon first glance:
Regardless, knowing how to find the names of an object can sometimes be useful for debugging purposes - and here is how to do it:
import gc, inspect
def find_names(obj):
frame = inspect.currentframe()
for frame in iter(lambda: frame.f_back, None):
frame.f_locals
obj_names = []
for referrer in gc.get_referrers(obj):
if isinstance(referrer, dict):
for k, v in referrer.items():
if v is obj:
obj_names.append(k)
return obj_names
If you're ever tempted to base logic around the names of your variables, pause for a moment and consider if redesign/refactor of code could solve the problem. The need to recover an object's name from the object itself usually means that underlying data structures in your program need a rethink.
* at least in Cpython
No. Objects and names live in separate dimensions. One object can have many names during its lifetime, and it's impossible to determine which one might be the one you want. Even in here:
class Foo(object):
def __init__(self): pass
x = Foo()
two names denote the same object (self
when __init__
runs, x
in global scope).
As many others have said, it can't be done properly. However inspired by jsbueno's, I have an alternative to his solution.
Like his solution, I inspect the callers stack frame, which means it only works properly for Python-implemented callers (see note below). Unlike him, I inspect the bytecode of the caller directly (instead of loading and parsing the source code). Using Python 3.4+'s dis.get_instructions()
this can be done with some hope of minimal compatibility. Though this is still some hacky code.
import inspect
import dis
def take1(iterator):
try:
return next(iterator)
except StopIteration:
raise Exception("missing bytecode instruction") from None
def take(iterator, count):
for x in range(count):
yield take1(iterator)
def get_assigned_name(frame):
"""Takes a frame and returns a description of the name(s) to which the
currently executing CALL_FUNCTION instruction's value will be assigned.
fn() => None
a = fn() => "a"
a, b = fn() => ("a", "b")
a.a2.a3, b, c* = fn() => ("a.a2.a3", "b", Ellipsis)
"""
iterator = iter(dis.get_instructions(frame.f_code))
for instr in iterator:
if instr.offset == frame.f_lasti:
break
else:
assert False, "bytecode instruction missing"
assert instr.opname.startswith('CALL_')
instr = take1(iterator)
if instr.opname == 'POP_TOP':
raise ValueError("not assigned to variable")
return instr_dispatch(instr, iterator)
def instr_dispatch(instr, iterator):
opname = instr.opname
if (opname == 'STORE_FAST' # (co_varnames)
or opname == 'STORE_GLOBAL' # (co_names)
or opname == 'STORE_NAME' # (co_names)
or opname == 'STORE_DEREF'): # (co_cellvars++co_freevars)
return instr.argval
if opname == 'UNPACK_SEQUENCE':
return tuple(instr_dispatch(instr, iterator)
for instr in take(iterator, instr.arg))
if opname == 'UNPACK_EX':
return (*tuple(instr_dispatch(instr, iterator)
for instr in take(iterator, instr.arg)),
Ellipsis)
# Note: 'STORE_SUBSCR' and 'STORE_ATTR' should not be possible here.
# `lhs = rhs` in Python will evaluate `lhs` after `rhs`.
# Thus `x.attr = rhs` will first evalute `rhs` then load `a` and finally
# `STORE_ATTR` with `attr` as instruction argument. `a` can be any
# complex expression, so full support for understanding what a
# `STORE_ATTR` will target requires decoding the full range of expression-
# related bytecode instructions. Even figuring out which `STORE_ATTR`
# will use our return value requires non-trivial understanding of all
# expression-related bytecode instructions.
# Thus we limit ourselfs to loading a simply variable (of any kind)
# and a arbitary number of LOAD_ATTR calls before the final STORE_ATTR.
# We will represents simply a string like `my_var.loaded.loaded.assigned`
if opname in {'LOAD_CONST', 'LOAD_DEREF', 'LOAD_FAST',
'LOAD_GLOBAL', 'LOAD_NAME'}:
return instr.argval + "." + ".".join(
instr_dispatch_for_load(instr, iterator))
raise NotImplementedError("assignment could not be parsed: "
"instruction {} not understood"
.format(instr))
def instr_dispatch_for_load(instr, iterator):
instr = take1(iterator)
opname = instr.opname
if opname == 'LOAD_ATTR':
yield instr.argval
yield from instr_dispatch_for_load(instr, iterator)
elif opname == 'STORE_ATTR':
yield instr.argval
else:
raise NotImplementedError("assignment could not be parsed: "
"instruction {} not understood"
.format(instr))
Note: C-implemented functions don't show up as Python stack frames and are thus hidden to this script. This will result in false positives. Consider Python function f()
which calls a = g()
. g()
is C-implemented and calls b = f2()
. When f2()
tries to lookup up the assigned name, it will get a
instead of b
because the script is oblivious to C functions. (At least this is how I guess it will work :P )
Usage example:
class MyItem():
def __init__(self):
self.name = get_assigned_name(inspect.currentframe().f_back)
abc = MyItem()
assert abc.name == "abc"
Here is a simple function to achieve what you want, assuming you wish to retrieve the name of the variable where the instance is assigned from a method call :
import inspect
def get_instance_var_name(method_frame, instance):
parent_frame = method_frame.f_back
matches = {k: v for k,v in parent_frame.f_globals.items() if v is instance}
assert len(matches) < 2
return list(matches.keys())[0] if matches else None
Here is an usage example :
class Bar:
def foo(self):
print(get_instance_var_name(inspect.currentframe(), self))
bar = Bar()
bar.foo() # prints 'bar'
def nested():
bar.foo()
nested() # prints 'bar'
Bar().foo() # prints None
assuming this:
class MyObject(object):
pass
x = MyObject()
then you can search through the environment by the object's id, returning the key when there is a match.
keys = list(globals().keys()) # list all variable names
target = id(x) # find the id of your object
for k in keys:
value_memory_address = id(globals()[k]) # fetch id of every object
if value_memory_address == target:
print(globals()[k], k) # if there is a variable assigned to that id, then it is a variable that points to your object