Python之谜:如何获取变量名?

Posted by 肖哥shelwin on June 26, 2019

初步尝试

今天我们探讨Python中一个看似很简单,实则并不容易的问题。这个问题是:如何获得变量的名字?

举例说明,给定一个变量var,给它赋值字符串”foo”。即

1
In [1]: var = "foo"

现在我们需要得到变量var的名字,即”var”。

尝试下面两种方法,我们得到的都是变量的值”foo”,而不是变量的名字”var”。

1
2
3
4
5
In [2]: print var
foo

In [3]: print "%s" % var
foo

怎么办呢?我们接着探索变量var的全部属性和方法:

1
2
In [4]: print dir(var)
['__add__', '__class__', '__contains__', ..., 'capitalize', 'center', ...]

经过尝试后发现,在var的71个(这个数字通过print len(dir(var))得到)属性和方法中,没有一个能够让我们得到var的名字。

由此可见,得到变量值(value)容易,得到变量名(name)看起来十分困难

两个示例

在进一步探索其他方案之前,我们先看下”获取变量名”在Python编程中到底有没有实际意义。这里,举两个小例子,说明”获得变量名”的好处。

示例1

第一个例子是关于异常处理的。如果能够得到变量名,那么Python异常的错误消息(Error Message)将变得更加有意义。例如:

1
2
3
4
5
      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_numsecond_num,无疑是一件好事情。

示例2

第二个例子是基于一组变量创建字典。已知若干变量,name, address, age, gender,我们希望基于它们创建一个字典person。一般可以这样实现:

1
2
3
4
5
person = {}
person["name"] = name
person["address"] = address
person["age"] = age
person["gender"] = gender

虽然这种实现完全可行,但是对于像我这样有”洁癖”的Python爱好者来说,有两点觉得不舒服。一是变量的名字出现了重复;二是赋值语句(不管是=赋值还是:赋值)出现了重复。当变量数量越多时,重复情况就越多。

如果能够使用类似下面这种循环来实现,代码就能更加简洁:

1
2
for i in [name, address, age, gender]:
    person[i] = i # 由于i是变量值,因此不可行

以上两个示例说明获得变量名对于Python编程是有好处的。

一种间接方案

然而,在前文我们也看到了,得到Python变量名是困难的。那么,为什么无法直接得到Python变量名呢?查阅相关资料后发现,在Python中,变量名字是对象(object)的单向(而不是双向)引用。也就是说,根据变量名,能够直接得到它所指向的对象;反之,根据对象,是无法得到指向它的变量名的。

之所以有这么一个规定,是基于成本考虑的。在程序中,往往存在大量的变量(整型,字符串,列表,字典,布尔…)。如果每一个变量都需要有一个包含指向它的变量名的列表,那么这些列表的创建和维护的成本(实现成本,执行成本…)将变得难以承受。

既然直接方法不可行,那么有没有间接的方法呢?Python的inspect模块提供了一种间接方案。以示例2为例,基于inspect的For循环实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

执行脚本,结果符合预期:

1
2
root@hzettv53:~# python test.py
{'gender': 'man', 'age': 21, 'name': 'bob', 'address': 'hangzhou'}

这种方案的基本思路是:通过inspect模块的currentframe()方法得到一个包含调用者的所有局部变量的名字和值的列表,然后从列表中搜索变量值与目标值相同的元素,并返回该元素对应的变量名。

虽然这种方案在一些情况下能够工作,但是其弊端是显而易见的:1) 实现的复杂度较高 2) 如果存在多个变量值相同的情况,那么retrieve_name()函数将返回多个变量名,从而无法得到精确结果。基于这两点因素,这个方法中并没有多大的实际应用意义。

总结

综上所述,我们可以看到,Python编程中并不存在一种高效和可靠的方法去获得变量名。所幸的是,尽管获得变量名在某些场景下有好处,但是这种好处往往并不显著,并且一般都存在替代方案。因此,尽管Python无法支持获得变量名这一需求,但是日常的Python开发工作并不会因此受到多大影响。

最后,欢迎扫描下方二维码,添加我的同名公众号,获得更多自动化测试/持续集成/软件工程实践/Python编程的原创精彩文章 :

公众号