如何获取传递给函数的变量的原始变量名[重复]
- 2025-01-10 08:46:00
- admin 原创
- 15
问题描述:
是否可以获取传递给函数的变量的原始变量名?例如
foobar = "foo"
def func(var):
print var.origname
以便:
func(foobar)
返回:
foobar
我试图制作如下功能:
def log(soup):
f = open(varname+'.html', 'w')
print >>f, soup.prettify()
f.close()
并让函数根据传递给它的变量的名称生成文件名。
我想如果不可能的话我每次只需要将变量和变量的名称作为字符串传递。
解决方案 1:
编辑:说清楚一点,我完全不建议使用它,它会损坏、会造成混乱、不会以任何方式帮助你,但出于娱乐/教育目的它是可行的。
您可以随意修改该inspect
模块,我不推荐这样做,但您可以这样做......
import inspect
def foo(a, f, b):
frame = inspect.currentframe()
frame = inspect.getouterframes(frame)[1]
string = inspect.getframeinfo(frame[0]).code_context[0].strip()
args = string[string.find('(') + 1:-1].split(',')
names = []
for i in args:
if i.find('=') != -1:
names.append(i.split('=')[1].strip())
else:
names.append(i)
print names
def main():
e = 1
c = 2
foo(e, 1000, b = c)
main()
输出:
['e', '1000', 'c']
解决方案 2:
为了补充Michael Mrozek 的回答,您可以通过以下方式提取完整代码的精确参数:
import re
import traceback
def func(var):
stack = traceback.extract_stack()
filename, lineno, function_name, code = stack[-2]
vars_name = re.compile(r'((.*?)).*$').search(code).groups()[0]
print vars_name
return
foobar = "foo"
func(foobar)
# PRINTS: foobar
解决方案 3:
看起来 Ivo 已经打败我了inspect
,但是这里还有另一种实现:
import inspect
def varName(var):
lcls = inspect.stack()[2][0].f_locals
for name in lcls:
if id(var) == id(lcls[name]):
return name
return None
def foo(x=None):
lcl='not me'
return varName(x)
def bar():
lcl = 'hi'
return foo(lcl)
bar()
# 'lcl'
当然,它是可以被愚弄的:
def baz():
lcl = 'hi'
x='hi'
return foo(lcl)
baz()
# 'x'
寓意:不要这样做。
解决方案 4:
不能。它在传递给函数之前会被评估。你所能做的就是将其作为字符串传递。
解决方案 5:
如果您知道调用代码是什么样子的,您可以尝试的另一种方法是使用traceback
:
def func(var):
stack = traceback.extract_stack()
filename, lineno, function_name, code = stack[-2]
code
将包含用于调用的代码行func
(在您的示例中,它将是字符串func(foobar)
)。您可以解析它以提取参数
解决方案 6:
@Ivo Wetzel 的答案适用于在一行中进行函数调用的情况,例如
e = 1 + 7
c = 3
foo(e, 100, b=c)
如果函数调用不在一行中,例如:
e = 1 + 7
c = 3
foo(e,
1000,
b = c)
下面的代码有效:
import inspect, ast
def foo(a, f, b):
frame = inspect.currentframe()
frame = inspect.getouterframes(frame)[1]
string = inspect.findsource(frame[0])[0]
nodes = ast.parse(''.join(string))
i_expr = -1
for (i, node) in enumerate(nodes.body):
if hasattr(node, 'value') and isinstance(node.value, ast.Call)
and hasattr(node.value.func, 'id') and node.value.func.id == 'foo' # Here goes name of the function:
i_expr = i
break
i_expr_next = min(i_expr + 1, len(nodes.body)-1)
lineno_start = nodes.body[i_expr].lineno
lineno_end = nodes.body[i_expr_next].lineno if i_expr_next != i_expr else len(string)
str_func_call = ''.join([i.strip() for i in string[lineno_start - 1: lineno_end]])
params = str_func_call[str_func_call.find('(') + 1:-1].split(',')
print(params)
您将获得:
[u'e', u'1000', u'b = c']
但这仍有可能破裂。
解决方案 7:
您可以使用 python-varname 包
from varname import nameof
s = 'Hey!'
print (nameof(s))
输出:
s
包装如下:
https://github.com/pwwang/python-varname
解决方案 8:
为了方便后人理解,下面是我为这项任务编写的一些代码,总的来说,我认为 Python 中缺少一个模块,以便让每个人都能对调用方环境进行良好而强大的检查。类似于 rlang eval 框架为 R 提供的。
import re, inspect, ast
#Convoluted frame stack walk and source scrape to get what the calling statement to a function looked like.
#Specifically return the name of the variable passed as parameter found at position pos in the parameter list.
def _caller_param_name(pos):
#The parameter name to return
param = None
#Get the frame object for this function call
thisframe = inspect.currentframe()
try:
#Get the parent calling frames details
frames = inspect.getouterframes(thisframe)
#Function this function was just called from that we wish to find the calling parameter name for
function = frames[1][3]
#Get all the details of where the calling statement was
frame,filename,line_number,function_name,source,source_index = frames[2]
#Read in the source file in the parent calling frame upto where the call was made
with open(filename) as source_file:
head=[source_file.next() for x in xrange(line_number)]
source_file.close()
#Build all lines of the calling statement, this deals with when a function is called with parameters listed on each line
lines = []
#Compile a regex for matching the start of the function being called
regex = re.compile(r'.?s*%ss*(' % (function))
#Work backwards from the parent calling frame line number until we see the start of the calling statement (usually the same line!!!)
for line in reversed(head):
lines.append(line.strip())
if re.search(regex, line):
break
#Put the lines we have groked back into sourcefile order rather than reverse order
lines.reverse()
#Join all the lines that were part of the calling statement
call = "".join(lines)
#Grab the parameter list from the calling statement for the function we were called from
match = re.search('.?s*%ss*((.*))' % (function), call)
paramlist = match.group(1)
#If the function was called with no parameters raise an exception
if paramlist == "":
raise LookupError("Function called with no parameters.")
#Use the Python abstract syntax tree parser to create a parsed form of the function parameter list 'Name' nodes are variable names
parameter = ast.parse(paramlist).body[0].value
#If there were multiple parameters get the positional requested
if type(parameter).__name__ == 'Tuple':
#If we asked for a parameter outside of what was passed complain
if pos >= len(parameter.elts):
raise LookupError("The function call did not have a parameter at postion %s" % pos)
parameter = parameter.elts[pos]
#If there was only a single parameter and another was requested raise an exception
elif pos != 0:
raise LookupError("There was only a single calling parameter found. Parameter indices start at 0.")
#If the parameter was the name of a variable we can use it otherwise pass back None
if type(parameter).__name__ == 'Name':
param = parameter.id
finally:
#Remove the frame reference to prevent cyclic references screwing the garbage collector
del thisframe
#Return the parameter name we found
return param
解决方案 9:
如果您想要一个键值对关系,也许使用字典会更好?
...或者如果您试图从您的代码中创建一些自动文档,也许像 Doxygen ( http://www.doxygen.nl/ ) 这样的东西可以为您完成这项工作?
解决方案 10:
我想知道IceCream是如何解决这个问题的。所以我查看了源代码,并想出了以下(略微简化的)解决方案。它可能不是 100% 可靠的(例如,我删除了get_text_with_indentation
并且我假设只有一个函数参数),但它适用于不同的测试用例。它不需要解析源代码本身,因此它应该比以前的解决方案更强大、更简单。
#!/usr/bin/env python3
import inspect
from executing import Source
def func(var):
callFrame = inspect.currentframe().f_back
callNode = Source.executing(callFrame).node
source = Source.for_frame(callFrame)
expression = source.asttokens().get_text(callNode.args[0])
print(expression, '=', var)
i = 1
f = 2.0
dct = {'key': 'value'}
obj = type('', (), {'value': 42})
func(i)
func(f)
func(s)
func(dct['key'])
func(obj.value)
输出:
i = 1
f = 2.0
s = string
dct['key'] = value
obj.value = 42
更新:如果您想将“魔法”移到一个单独的函数中,您只需再往后退一帧并使用额外的 即可f_back
。
def get_name_of_argument():
callFrame = inspect.currentframe().f_back.f_back
callNode = Source.executing(callFrame).node
source = Source.for_frame(callFrame)
return source.asttokens().get_text(callNode.args[0])
def func(var):
print(get_name_of_argument(), '=', var)
解决方案 11:
如果您想要获取调用者参数(如@Matt Oates答案中所示)而不使用源文件(即来自 Jupyter Notebook),此代码(结合@Aeon 答案)可以解决问题(至少在某些简单情况下):
def get_caller_params():
# get the frame object for this function call
thisframe = inspect.currentframe()
# get the parent calling frames details
frames = inspect.getouterframes(thisframe)
# frame 0 is the frame of this function
# frame 1 is the frame of the caller function (the one we want to inspect)
# frame 2 is the frame of the code that calls the caller
caller_function_name = frames[1][3]
code_that_calls_caller = inspect.findsource(frames[2][0])[0]
# parse code to get nodes of abstract syntact tree of the call
nodes = ast.parse(''.join(code_that_calls_caller))
# find the node that calls the function
i_expr = -1
for (i, node) in enumerate(nodes.body):
if _node_is_our_function_call(node, caller_function_name):
i_expr = i
break
# line with the call start
idx_start = nodes.body[i_expr].lineno - 1
# line with the end of the call
if i_expr < len(nodes.body) - 1:
# next expression marks the end of the call
idx_end = nodes.body[i_expr + 1].lineno - 1
else:
# end of the source marks the end of the call
idx_end = len(code_that_calls_caller)
call_lines = code_that_calls_caller[idx_start:idx_end]
str_func_call = ''.join([line.strip() for line in call_lines])
str_call_params = str_func_call[str_func_call.find('(') + 1:-1]
params = [p.strip() for p in str_call_params.split(',')]
return params
def _node_is_our_function_call(node, our_function_name):
node_is_call = hasattr(node, 'value') and isinstance(node.value, ast.Call)
if not node_is_call:
return False
function_name_correct = hasattr(node.value.func, 'id') and node.value.func.id == our_function_name
return function_name_correct
然后您可以像这样运行它:
def test(*par_values):
par_names = get_caller_params()
for name, val in zip(par_names, par_values):
print(name, val)
a = 1
b = 2
string = 'text'
test(a, b,
string
)
获得所需的输出:
a 1
b 2
string text
解决方案 12:
由于您可以拥有多个具有相同内容的变量,因此不必传递变量(内容),将其名称传递到字符串中并从调用者堆栈框架中的本地字典中获取变量内容可能会更安全(并且更简单)。:
def displayvar(name):
import sys
return name+" = "+repr(sys._getframe(1).f_locals[name])
解决方案 13:
如果恰巧该变量是一个可调用函数,它将具有一个__name__
属性。
例如,用于记录函数执行时间的包装器:
def time_it(func, *args, **kwargs):
start = perf_counter()
result = func(*args, **kwargs)
duration = perf_counter() - start
print(f'{func.__name__} ran in {duration * 1000}ms')
return result
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理必备:盘点2024年13款好用的项目管理软件