Dark magics about variable names in python
CHANGELOG | API | Playground | :fire: StackOverflow answer
Installation
pip install -U varname
Note if you use python < 3.8
, install varname < 0.11
Features
Credits
Thanks goes to these awesome people/projects:
Special thanks to @HanyuuLu to give up the name varname
in pypi for this project.
Usage
Retrieving the variable names using varname(...)
-
From inside a function
from varname import varname
def function():
return varname()
func = function()
When there are intermediate frames:
def wrapped():
return function()
def function():
return varname(frame=2)
func = wrapped()
Or use ignore
to ignore the wrapped frame:
def wrapped():
return function()
def function():
return varname(ignore=wrapped)
func = wrapped()
Calls from standard libraries are ignored by default:
import asyncio
async def function():
return varname()
func = asyncio.run(function())
Use strict
to control whether the call should be assigned to
the variable directly:
def function(strict):
return varname(strict=strict)
func = function(True)
func = [function(True)]
func = [function(False)]
func = function(False), function(False)
-
Retrieving name of a class instance
class Foo:
def __init__(self):
self.id = varname()
def copy(self):
copied = Foo()
copied.id = varname()
return copied
foo = Foo()
foo2 = foo.copy()
-
Multiple variables on Left-hand side
def func():
return varname(multi_vars=True)
a = func()
a, b = func()
[a, b] = func()
a, (b, c) = func()
-
Some unusual use
def function(**kwargs):
return varname(strict=False)
func = func1 = function()
x = function(y = function())
func_abc = function()[-3:]
function2 = function
func = function2()
a = lambda: 0
a.b = function()
The decorator way to register __varname__
to functions/classes
-
Registering __varname__
to functions
from varname.helpers import register
@register
def function():
return __varname__
func = function()
@register(frame=2)
def function():
return __varname__
def wrapped():
return function()
func = wrapped()
-
Registering __varname__
as a class property
@register
class Foo:
...
foo = Foo()
Getting variable names directly using nameof
from varname import varname, nameof
a = 1
nameof(a)
b = 2
nameof(a, b)
def func():
return varname() + '_suffix'
f = func()
nameof(f)
func.a = func
nameof(func.a, vars_only=False)
func.a.b = 1
nameof(func.a.b, vars_only=False)
Detecting next immediate attribute name
from varname import will
class AwesomeClass:
def __init__(self):
self.will = None
def permit(self):
self.will = will(raise_exc=False)
if self.will == 'do':
return self
raise AttributeError('Should do something with AwesomeClass object')
def do(self):
if self.will != 'do':
raise AttributeError("You don't have permission to do")
return 'I am doing!'
awesome = AwesomeClass()
awesome.do()
awesome.permit()
awesome.permit().do() == 'I am doing!'
Fetching argument names/sources using argname
from varname import argname
def func(a, b=1):
print(argname('a'))
x = y = z = 2
func(x)
def func2(a, b=1):
print(argname('a', 'b'))
func2(y, b=x)
def func3(a, b=1):
print(argname('a', 'b', vars_only=False))
func3(x+y, y+x)
def func4(*args, **kwargs):
print(argname('args[1]', 'kwargs[c]'))
func4(y, x, c=z)
class Foo:
def __setattr__(self, name, value):
print(argname("name", "value", func=self.__setattr__))
Foo().a = 1
Value wrapper
from varname.helpers import Wrapper
foo = Wrapper(True)
bar = Wrapper(False)
def values_to_dict(*args):
return {val.name: val.value for val in args}
mydict = values_to_dict(foo, bar)
Creating dictionary using jsobj
from varname.helpers import jsobj
a = 1
b = 2
jsobj(a, b)
jsobj(a, b, c=3)
Debugging with debug
from varname.helpers import debug
a = 'value'
b = ['val']
debug(a)
debug(b)
debug(a, b)
debug(a, b, merge=True)
debug(a, repr=False, prefix='')
debug(a+a)
debug(a+a, vars_only=True)
Replacing exec
with exec_code
from varname import argname
from varname.helpers import exec_code
class Obj:
def __init__(self):
self.argnames = []
def receive(self, arg):
self.argnames.append(argname('arg', func=self.receive))
obj = Obj()
exec_code('obj.receive(1)')
exec_code('obj.receive(2)')
obj.argnames
Reliability and limitations
varname
is all depending on executing
package to look for the node.
The node executing
detects is ensured to be the correct one (see 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. Use
it at your own risk.
For example: