In a Python function, Is there a way to tell the argument's variable name on the caller side?
Question:
I am trying to create a logger function:
def logger(someVariable):
# some code that prints the name of someVariable
So when I call the function:
logger(myvarA) # should output the string 'myvarA'
logger(someobj.varB) # should output the string 'someobj.varB'
logger(myarr[3]) # should (probably) output 'myarr[3]'
Not sure if all or even any of these cases are possible? The use case for this is to have a simple function in iPython Notebook to log any variables for their values without having to type something like below:
logger(myVarA,'myVarA')
logger(myarr[i],'myarr[{}]'.format(i))
Answers:
The inspect module (https://docs.python.org/3/library/inspect.html#inspect.getframeinfo) will also give you the code context. You simply need to parse that to get the text of what was passed. With only a single variable, it’s pretty easy to do. Here’s a sample.
import inspect
import re
def handle_nested_parens(v):
parens = 0
output = ""
for c in v:
if c == ")":
parens = parens - 1
if parens < 0:
return output
output += c
if c == "(":
parens = parens + 1
return output
def logger(v):
currentframe = inspect.currentframe()
code_context = inspect.getouterframes(currentframe,1)[1][4][0]
variable = handle_nested_parens(re.sub(r".*logger((.*)",r"1", code_context))
print("{0} => {1}".format(variable, v))
def testing(a):
return(a)
def submain(a):
ab = (1, 2, a)
logger(testing(ab))
return 15
def main():
xyz = "hello"
logger(xyz)
logger(submain(xyz))
if __name__ == '__main__':
a = 5
logger(a)
main()
It outputs
a => 5
xyz => hello
testing(ab) => (1, 2, 'hello')
submain(xyz) => 15
For anyone looking for something like this today I have a get_variable_name() function in my argel1200.utilties that does this. You can do a pip install argel1200 to get it.
Documentation:
Called by dumps()
Pulls the variable names from the function that called this function
This function traces back through the call stack, so we have to subtract -1
for every intermediate function, including this function.
Subtract -1 for every intermediate step in the call stack.
So we have: -1 for this function -1 for whoever called it = -2, which is the default.
If there are more functions in the middle then subtract -1 for each of them. For example:
-1 for this function -1 for dumps(), and -1 for whoever called dumps = -3.
:param stack_back: How far back we need to go in the stack (see above description)
:return: Returns the variable name(s)
And here’s the function:
import re
import traceback
def get_variable_name(stack_back=-2):
stack = traceback.extract_stack()
caller_name = stack[-2].name
caller_len = len(caller_name)
line = stack[stack_back].line
# Example line: print('fu', 'bar/', argel1200.utilities.dumps(header), '/foobar')
my_line = re.sub(r'(s|u180B|u200B|u200C|u200D|u2060|uFEFF)+', '', line) # Remove all whitespace
caller_start = my_line.find(caller_name + '(') # Find where the caller string is (e.g. where "dumps(" starts)
caller_end = caller_start + caller_len # And where it ends (the index of the '(' in "dumps(" )
my_line_substr = my_line[caller_end:] # Get a substr of everything past the caller (e.g. "dumps").
# Now let's find all the variable names passed in
vars_passed_in = []
parens = 0
str_start = None
for idx, char in enumerate(my_line_substr):
if char == '(':
parens += 1
str_start = idx + 1
elif char == ',' or char == ')':
vars_passed_in.append(my_line_substr[str_start:idx])
str_start = idx + 1
if char == ')':
parens -= 1
if parens == 0:
break
return vars_passed_in
You can see the code on GitHub: https://github.com/argel1200/argel1200-python/blob/master/argel1200/utilities.py
The function dumps() just below it calls it, so you can see how that works.
I am trying to create a logger function:
def logger(someVariable):
# some code that prints the name of someVariable
So when I call the function:
logger(myvarA) # should output the string 'myvarA'
logger(someobj.varB) # should output the string 'someobj.varB'
logger(myarr[3]) # should (probably) output 'myarr[3]'
Not sure if all or even any of these cases are possible? The use case for this is to have a simple function in iPython Notebook to log any variables for their values without having to type something like below:
logger(myVarA,'myVarA')
logger(myarr[i],'myarr[{}]'.format(i))
The inspect module (https://docs.python.org/3/library/inspect.html#inspect.getframeinfo) will also give you the code context. You simply need to parse that to get the text of what was passed. With only a single variable, it’s pretty easy to do. Here’s a sample.
import inspect
import re
def handle_nested_parens(v):
parens = 0
output = ""
for c in v:
if c == ")":
parens = parens - 1
if parens < 0:
return output
output += c
if c == "(":
parens = parens + 1
return output
def logger(v):
currentframe = inspect.currentframe()
code_context = inspect.getouterframes(currentframe,1)[1][4][0]
variable = handle_nested_parens(re.sub(r".*logger((.*)",r"1", code_context))
print("{0} => {1}".format(variable, v))
def testing(a):
return(a)
def submain(a):
ab = (1, 2, a)
logger(testing(ab))
return 15
def main():
xyz = "hello"
logger(xyz)
logger(submain(xyz))
if __name__ == '__main__':
a = 5
logger(a)
main()
It outputs
a => 5
xyz => hello
testing(ab) => (1, 2, 'hello')
submain(xyz) => 15
For anyone looking for something like this today I have a get_variable_name() function in my argel1200.utilties that does this. You can do a pip install argel1200 to get it.
Documentation:
Called by dumps()
Pulls the variable names from the function that called this function
This function traces back through the call stack, so we have to subtract -1
for every intermediate function, including this function.
Subtract -1 for every intermediate step in the call stack.
So we have: -1 for this function -1 for whoever called it = -2, which is the default.
If there are more functions in the middle then subtract -1 for each of them. For example:
-1 for this function -1 for dumps(), and -1 for whoever called dumps = -3.
:param stack_back: How far back we need to go in the stack (see above description)
:return: Returns the variable name(s)
And here’s the function:
import re
import traceback
def get_variable_name(stack_back=-2):
stack = traceback.extract_stack()
caller_name = stack[-2].name
caller_len = len(caller_name)
line = stack[stack_back].line
# Example line: print('fu', 'bar/', argel1200.utilities.dumps(header), '/foobar')
my_line = re.sub(r'(s|u180B|u200B|u200C|u200D|u2060|uFEFF)+', '', line) # Remove all whitespace
caller_start = my_line.find(caller_name + '(') # Find where the caller string is (e.g. where "dumps(" starts)
caller_end = caller_start + caller_len # And where it ends (the index of the '(' in "dumps(" )
my_line_substr = my_line[caller_end:] # Get a substr of everything past the caller (e.g. "dumps").
# Now let's find all the variable names passed in
vars_passed_in = []
parens = 0
str_start = None
for idx, char in enumerate(my_line_substr):
if char == '(':
parens += 1
str_start = idx + 1
elif char == ',' or char == ')':
vars_passed_in.append(my_line_substr[str_start:idx])
str_start = idx + 1
if char == ')':
parens -= 1
if parens == 0:
break
return vars_passed_in
You can see the code on GitHub: https://github.com/argel1200/argel1200-python/blob/master/argel1200/utilities.py
The function dumps() just below it calls it, so you can see how that works.