Python中如何在函数内部获取传入实参的原变量名?
问题解答
核心结论
Python没有原生语法支持直接在函数内部获取实参对应的外部变量名。Python传参传递的是对象引用,形参接收值后和外部原变量没有强绑定关系。如果是为了实现调试日志这类非核心逻辑需求,可以通过栈帧检查+源码解析的黑魔法方式实现,这类方案兼容性、性能有限,不适合在核心业务逻辑中使用。
实现方案
1. 轻量正则匹配版(适合简单单参数场景)
原理是通过inspect模块向上取栈帧,拿到函数调用位置的源码行,用正则匹配出实参的字符串内容,即可得到传入的变量名。
示例代码:
import inspect import re def _get_caller_arg(param_index: int = 0): # 向上回溯两层栈:当前工具函数一层,业务函数一层,拿到调用业务函数的栈帧 caller_frame = inspect.currentframe().f_back.f_back # 读取调用业务函数的代码行 code_lines = inspect.getframeinfo(caller_frame).code_context if not code_lines: return None call_line = code_lines[0].strip() # 提取括号内的实参列表 arg_match = re.search(r"\((.*)\)", call_line) if not arg_match: return None arg_list = [arg.strip() for arg in arg_match.group(1).split(",")] return arg_list[param_index] if param_index < len(arg_list) else None # 模拟业务依赖方法 def get_age_by_id(user_id: int) -> float: return 2024 - user_id john = 199 def foo(userId: int) -> float: # 获取第一个位置参数对应的实参名字符串 arg_name = _get_caller_arg() age = get_age_by_id(userId) # 打印目标格式日志 print(f"foo({arg_name})") print(f">>> Called with userId = {userId} # {arg_name}") return age # 测试调用 foo(john)
运行输出完全符合预期:
foo(john) >>> Called with userId = 199 # john
这个版本的局限:
- 仅适配调用写在单行、单参数的简单场景,多参数、多行调用、嵌套调用需要额外调整匹配逻辑
- 依赖源码可读取,在打包为二进制、REPL交互环境下会失效
2. AST解析版(适配多参数、关键字参数场景)
用AST(抽象语法树)解析调用行代码,比正则匹配的鲁棒性更强,可以适配多参数、关键字参数传参的场景。
示例代码:
import inspect import ast def _get_func_call_args(func): caller_frame = inspect.currentframe().f_back frame_info = inspect.getframeinfo(caller_frame) if not frame_info.code_context: return [], {} call_line = frame_info.code_context[0].strip() # 解析代码行生成AST code_ast = ast.parse(call_line) for node in ast.walk(code_ast): # 定位到目标函数的调用节点 if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == func.__name__: pos_args = [ast.unparse(arg) for arg in node.args] kw_args = {kw.arg: ast.unparse(kw.value) for kw in node.keywords} return pos_args, kw_args return [], {} john = 199 bob = 200 def foo(userId: int, otherId: int = None) -> float: pos_args, kw_args = _get_func_call_args(foo) age = get_age_by_id(userId) # 拼接调用日志 call_args_str = ", ".join(pos_args + [f"{k}={v}" for k, v in kw_args.items()]) user_arg_name = pos_args[0] if pos_args else kw_args.get("userId") print(f"foo({call_args_str})") print(f">>> Called with userId = {userId} # {user_arg_name}") return age # 测试不同传参方式 foo(john) foo(userId=bob)
运行输出:
foo(john) >>> Called with userId = 199 # john foo(userId=bob) >>> Called with userId = 200 # bob
注意事项
- 以上方案都属于调试辅助类的黑魔法实现,性能差、适配场景有限,仅建议用来打本地调试日志,绝对不要用于业务逻辑判断。
- Python的设计哲学中,变量名只是开发阶段的标识,运行时逻辑应当只和对象本身的值、类型有关,和绑定的变量名无关。如果业务逻辑强依赖获取外部变量名,说明代码设计存在缺陷,更稳妥的替代方案是显式传入带名称的结构,比如传字典
foo({"name": "john", "id": 199}),从根源上避免黑魔法带来的不稳定问题。
内容的提问来源于stack exchange,提问作者MYK
相关产品推荐
相关产品推荐

