今天我们探讨 Python 中一个看似很简单,实则并不容易的问题。这个问题是:如何获得变量的名字?
举例说明,给定一个变量var
,给它赋值字符串"foo"。即
In [1]: var = "foo"
现在我们需要得到变量var
的名字,即"var"。
尝试下面两种方法,我们得到的都是变量的值"foo",而不是变量的名字"var"。
In [2]: print var
foo
In [3]: print "%s" % var
foo
怎么办呢?我们接着探索变量var
的全部属性和方法:
In [4]: print dir(var)
['__add__', '__class__', '__contains__', ..., 'capitalize', 'center', ...]
经过尝试后发现,在var
的 71 个 (这个数字通过print len(dir(var))
得到) 属性和方法中,没有一个能够让我们得到var
的名字。
由此可见,得到变量值 (value) 容易,得到变量名 (name) 看起来十分困难。
在进一步探索其他方案之前,我们先看下"获取变量名"在 Python 编程中到底有没有实际意义。这里,举两个小例子,说明"获得变量名"的好处。
第一个例子是关于异常处理的。如果能够得到变量名,那么 Python 异常的错误消息 (Error Message) 将变得更加有意义。例如:
1 first_num, second_num = 1, 2
2 if first_num != second_num:
----> 3 raise RuntimeError("%s is not equal to %s" % (first_num, second_num))
RuntimeError: 1 is not equal to 2
这里错误消息RuntimeError: 1 is not equal to 2
的可读性是比较差的,1 和 2 分别是什么?我们需要结合源码才能明白。假如能够在 Error Message 中输出 1 和 2 各自的变量名first_num
和second_num
,无疑是一件好事情。
第二个例子是基于一组变量创建字典。已知若干变量,name, address, age, gender
,我们希望基于它们创建一个字典person
。一般可以这样实现:
person = {}
person["name"] = name
person["address"] = address
person["age"] = age
person["gender"] = gender
虽然这种实现完全可行,但是对于像我这样有"洁癖"的 Python 爱好者来说,有两点觉得不舒服。一是变量的名字出现了重复;二是赋值语句 (不管是=
赋值还是:
赋值) 出现了重复。当变量数量越多时,重复情况就越多。
如果能够使用类似下面这种循环来实现,代码就能更加简洁:
for i in [name, address, age, gender]:
person[i] = i # 由于i是变量值,因此不可行
以上两个示例说明获得变量名对于 Python 编程是有好处的。
然而,在前文我们也看到了,得到 Python 变量名是困难的。那么,为什么无法直接得到 Python 变量名呢?查阅相关资料后发现,在 Python 中,变量名字是对象 (object) 的单向 (而不是双向) 引用。也就是说,根据变量名,能够直接得到它所指向的对象;反之,根据对象,是无法得到指向它的变量名的。
之所以有这么一个规定,是基于成本考虑的。在程序中,往往存在大量的变量 (整型,字符串,列表,字典,布尔...)。如果每一个变量都需要有一个包含指向它的变量名的列表,那么这些列表的创建和维护的成本 (实现成本,执行成本...) 将变得难以承受。
既然直接方法不可行,那么有没有间接的方法呢?Python 的inspect
模块提供了一种间接方案。以示例 2 为例,基于inspect
的 For 循环实现为:
import inspect
def retrieve_name(var):
callers_local_vars = inspect.currentframe().f_back.f_locals.items()
return [var_name for var_name, var_val in callers_local_vars if var_val is var]
name, address, age, gender = "bob", "hangzhou", 21, "man"
person = {}
for i in [name, address, age, gender]:
person[retrieve_name(i)[0]] = i
print person
执行脚本,结果符合预期:
root@hzettv53:~# python test.py
{'gender': 'man', 'age': 21, 'name': 'bob', 'address': 'hangzhou'}
这种方案的基本思路是:通过inspect
模块的currentframe()
方法得到一个包含调用者的所有局部变量的名字和值的列表,然后从列表中搜索变量值与目标值相同的元素,并返回该元素对应的变量名。
虽然这种方案在一些情况下能够工作,但是其弊端是显而易见的:1) 实现的复杂度较高 2) 如果存在多个变量值相同的情况,那么 retrieve_name() 函数将返回多个变量名,从而无法得到精确结果。基于这两点因素,这个方法中并没有多大的实际应用意义。
综上所述,我们可以看到,Python 编程中并不存在一种高效和可靠的方法去获得变量名。所幸的是,尽管获得变量名在某些场景下有好处,但是这种好处往往并不显著,并且一般都存在替代方案。因此,尽管 Python 无法支持获得变量名这一需求,但是日常的 Python 开发工作并不会因此受到多大影响。
最后,欢迎关注我的个人微信公众号《测试不将就》,我们一起探讨高质量软件养成之道。