Package: varname
Dark magics about variable names in pythonCHANGELOG|API|Playground| :fire:StackOverflow answerInstallationpipinstall-UvarnameNote if you usepython < 3.8, installvarname < 0.11FeaturesCore features:Retrieving names of variables a function/class call is assigned to from inside it, usingvarname.Retrieving variable names directly, usingnameofDetecting next immediate attribute name, usingwillFetching argument names/sources passed to a function usingargnameOther helper APIs (built based on core features):A value wrapper to store the variable name that a value is assigned to, usingWrapperA decorator to register__varname__to functions/classes, usingregisterA helper function to create dict without explicitly specifying the key-value pairs, usingjsobjAdebugfunction to print variables with their names and valuesexec_codeto replaceexecwhere source code is available at runtimeCreditsThanks goes to these awesome people/projects:executing@alexmojaki@breuleux@ElCuboNegro@thewchan@LawsOfSympathySpecial thanks to@HanyuuLuto give up the namevarnamein pypi for this project.UsageRetrieving the variable names usingvarname(...)From inside a functionfromvarnameimportvarnamedeffunction():returnvarname()func=function()# func == 'func'When there are intermediate frames:defwrapped():returnfunction()deffunction():# retrieve the variable name at the 2nd frame from this onereturnvarname(frame=2)func=wrapped()# func == 'func'Or useignoreto ignore the wrapped frame:defwrapped():returnfunction()deffunction():returnvarname(ignore=wrapped)func=wrapped()# func == 'func'Calls from standard libraries are ignored by default:importasyncioasyncdeffunction():returnvarname()func=asyncio.run(function())# func == 'func'Usestrictto control whether the call should be assigned to
the variable directly:deffunction(strict):returnvarname(strict=strict)func=function(True)# OK, direct assignment, func == 'func'func=[function(True)]# Not a direct assignment, raises ImproperUseErrorfunc=[function(False)]# OK, func == ['func']func=function(False),function(False)# OK, func = ('func', 'func')Retrieving name of a class instanceclassFoo:def__init__(self):self.id=varname()defcopy(self):# also able to fetch inside a method callcopied=Foo()# copied.id == 'copied'copied.id=varname()# assign id to whatever variable namereturncopiedfoo=Foo()# foo.id == 'foo'foo2=foo.copy()# foo2.id == 'foo2'Multiple variables on Left-hand side# since v0.5.4deffunc():returnvarname(multi_vars=True)a=func()# a == ('a',)a,b=func()# (a, b) == ('a', 'b')[a,b]=func()# (a, b) == ('a', 'b')# hierarchy is also possiblea,(b,c)=func()# (a, b, c) == ('a', 'b', 'c')Some unusual usedeffunction(**kwargs):returnvarname(strict=False)func=func1=function()# func == func1 == 'func1'# if varname < 0.8: func == func1 == 'func'# a warning will be shown# since you may not want func to be 'func1'x=function(y=function())# x == 'x'# get part of the namefunc_abc=function()[-3:]# func_abc == 'abc'# function alias supported nowfunction2=functionfunc=function2()# func == 'func'a=lambda:0a.b=function()# a.b == 'a.b'The decorator way to register__varname__to functions/classesRegistering__varname__to functionsfromvarname.helpersimportregister@registerdeffunction():return__varname__func=function()# func == 'func'# arguments also allowed (frame, ignore and raise_exc)@register(frame=2)deffunction():return__varname__defwrapped():returnfunction()func=wrapped()# func == 'func'Registering__varname__as a class property@registerclassFoo:...foo=Foo()# foo.__varname__ == 'foo'Getting variable names directly usingnameoffromvarnameimportvarname,nameofa=1nameof(a)# 'a'b=2nameof(a,b)# ('a', 'b')deffunc():returnvarname()+'_suffix'f=func()# f == 'f_suffix'nameof(f)# 'f'# get full names of (chained) attribute callsfunc.a=funcnameof(func.a,vars_only=False)# 'func.a'func.a.b=1nameof(func.a.b,vars_only=False)# 'func.a.b'Detecting next immediate attribute namefromvarnameimportwillclassAwesomeClass:def__init__(self):self.will=Nonedefpermit(self):self.will=will(raise_exc=False)ifself.will=='do':# let self handle doreturnselfraiseAttributeError('Should do something with AwesomeClass object')defdo(self):ifself.will!='do':raiseAttributeError("You don't have permission to do")return'I am doing!'awesome=AwesomeClass()awesome.do()# AttributeError: You don't have permission to doawesome.permit()# AttributeError: Should do something with AwesomeClass objectawesome.permit().do()=='I am doing!'Fetching argument names/sources usingargnamefromvarnameimportargnamedeffunc(a,b=1):print(argname('a'))x=y=z=2func(x)# prints: xdeffunc2(a,b=1):print(argname('a','b'))func2(y,b=x)# prints: ('y', 'x')# allow expressionsdeffunc3(a,b=1):print(argname('a','b',vars_only=False))func3(x+y,y+x)# prints: ('x+y', 'y+x')# positional and keyword argumentsdeffunc4(*args,**kwargs):print(argname('args[1]','kwargs[c]'))func4(y,x,c=z)# prints: ('x', 'z')# As of 0.9.0 (see: https://pwwang.github.io/python-varname/CHANGELOG/#v090)# Can also fetch the source of the argument for# __getattr__/__getitem__/__setattr/__setitem__/__add__/__lt__, etc.classFoo:def__setattr__(self,name,value):print(argname("name","value",func=self.__setattr__))Foo().a=1# prints: ("'a'", '1')Value wrapperfromvarname.helpersimportWrapperfoo=Wrapper(True)# foo.name == 'foo'# foo.value == Truebar=Wrapper(False)# bar.name == 'bar'# bar.value == Falsedefvalues_to_dict(*args):return{val.name:val.valueforvalinargs}mydict=values_to_dict(foo,bar)# {'foo': True, 'bar': False}Creating dictionary usingjsobjfromvarname.helpersimportjsobja=1b=2jsobj(a,b)# {'a': 1, 'b': 2}jsobj(a,b,c=3)# {'a': 1, 'b': 2, 'c': 3}Debugging withdebugfromvarname.helpersimportdebuga='value'b=['val']debug(a)# "DEBUG: a='value'\n"debug(b)# "DEBUG: b=['val']\n"debug(a,b)# "DEBUG: a='value'\nDEBUG: b=['val']\n"debug(a,b,merge=True)# "DEBUG: a='value', b=['val']\n"debug(a,repr=False,prefix='')# 'a=value\n'# also debug an expressiondebug(a+a)# "DEBUG: a+a='valuevalue'\n"# If you want to disable it:debug(a+a,vars_only=True)# ImproperUseErrorReplacingexecwithexec_codefromvarnameimportargnamefromvarname.helpersimportexec_codeclassObj:def__init__(self):self.argnames=[]defreceive(self,arg):self.argnames.append(argname('arg',func=self.receive))obj=Obj()# exec('obj.receive(1)')  # Errorexec_code('obj.receive(1)')exec_code('obj.receive(2)')obj.argnames# ['1', '2']Reliability and limitationsvarnameis all depending onexecutingpackage to look for the node.
The nodeexecutingdetects is ensured to be the correct one (seethis).It partially works with environments where other AST magics apply, includingpytest,ipython,macropy,birdseye,reticulatewithR, etc. Neitherexecutingnorvarnameis 100% working with those environments. Use
it at your own risk.For example:This will not work withpytest:a=1assertnameof(a)=='a'# pytest manipulated the ast here# do this insteadname_a=nameof(a)assertname_a=='a'