当我们调试Python代码时,有时需要将变量或表达式的值打印出来观察表达式的内容。但当打印内容过多时搞不清楚哪个表达式对应到哪个值。
比较傻的办法是将表达式在格式字符串中输入两遍:
import localeencoding = locale.getpreferredencoding()print("encoding = %s" % encoding)print("type(encoding) = %s" % type(encoding))------输出-------------encoding = cp936type(encoding) = <class 'str'>
当需要打印的内容变多时,打印函数会变得很长,比如:
print("fname.encode(encoding).decode(encoding) = %s" %fname.encode(encoding).decode(encoding))
遇到这个问题,我们的第一反应是在 Python 中能否获取变量名本身,比如:
encoding = locale.getpreferredencoding()
我们如果知道 encoding 这个变量名所代表的字符串就可以自定义打印函数,按我们的要求把变量名和内容打印出来。最好的结果就是传个变量进去,就把变量名和变量内容打印出来。比如下面这样:
myprint(encoding)myprint(fname)------输出-------------encoding = 'cp936'fname = 'd:\\python3'
于是问题就变成了如何获取Python变量名。这个问题可以单独写一篇帖子,而且方法比较多。我们使用已有的方法,代码来源我贴在最后。
import inspectimport localedef retrieve_name(var):"""Gets the name of var. Does it from the out most frame inner-wards.:param var: variable to get name from.:return: string"""for fi in reversed(inspect.stack()):names = [var_name for var_name, var_val in fi.frame.f_locals.items() if var_val is var]if len(names) > 0:return names[0]encoding = locale.getpreferredencoding()print(retrieve_name(encoding))----输出------------encoding
以上是使用别人写好的函数,可以获取变量名,将变量名转换成字符串。方法比较多,也可以使用 locals() 函数获取局部变量名,对应 locals() 函数,还有一个获取全局变量名的函数 globals()。
>>> import locale>>> encoding = locale.getpreferredencoding()>>> locals(){'__name__': '__main__','__doc__': None,'__package__': None,'encoding': 'cp936'}>>> globals(){'__name__': '__main__','__doc__': None,'__package__': None,'encoding': 'cp936'}>>>
我们如果只是处理变量名,这些函数都还好。可以构造自己的调试函数把变量名和变量传给调试函数,然后格式化输出。比如:
import inspectimport localedef retrieve_name(var):"""Gets the name of var. Does it from the out most frame inner-wards.:param var: variable to get name from.:return: string"""for fi in reversed(inspect.stack()):names = [var_name for var_name, var_val in fi.frame.f_locals.items() if var_val is var]if len(names) > 0:return names[0]def myprint(var):fmt = retrieve_name(var) + " = {0}"print(fmt.format(var))encoding = locale.getpreferredencoding()s = "test"test_dir = r"D:\python3"myprint(encoding)myprint(s)myprint(test_dir)------输出----------encoding = cp936s = testtest_dir = D:\python3
一切看起来很好,变量名被成功捕获到了,只用传一次参就可以把变量名和变量内容都打印出来。但是我们有时并不只打印变量,有时还要打印表达式。在处理表达式时,以上方法就失效了,比如下面这种表达式:
fname = r'D:\python3'fname.encode()fname.encode().decode()fname.encode(encoding)fname.encode(encoding).decode(encoding)
像上面这种表达式,它们并不是变量,并且在传参前已经计算好了,所以上述处理方法都失效了。因为问题不再是获取变量名,问题变成如何获取表达式名了。
实际上我们已经把问题的导向搞偏了,我们刚开始只是想让打印调试信息简化一点。如果能写个函数把 print() 包一层,能输出变量名和变量内容就好。
对于调试信息的输出,在 Python 3.6 新引入了一种新的语法,即以 f 为前缀的格式字符串。这种新的语法适用我们这种打印调试信息的场景。如果想把变量或表达式打印出来,可以直接使用如下语法:
print(f"{encoding = }")print(f"{fname = }")print(f"{type(fname) = }")print(f"{fname.encode() = }")print(f"{fname.encode().decode() = }")print(f"{fname.encode(encoding) = }")print(f"{fname.encode(encoding).decode(encoding) = }")
格式字符串的格式为:f"{ var = }"
就这么简单,支持变量、表达式、各种加减运算。最终打印效果如下:
------输出-------------encoding = 'cp936'fname = 'd:\\python3'type(fname) = <class 'str'>fname.encode() = b'd:\\python3'fname.encode().decode() = 'd:\\python3'fname.encode(encoding) = b'd:\\python3'fname.encode(encoding).decode(encoding) = 'd:\\python3'
为了控制调试开关,我们把 print 函数包装一下,在函数中加上调试状态判断,可以根据需要关闭或启用调试信息。
#! pyhton3import locale# 调试开关debug = True# 自定义打印函数def pprint(var):global debugif debug:print(var)# 测试打印效果encoding = locale.getpreferredencoding()fname = r'D:\python3'pprint(f"{encoding = }")pprint(f"{fname = }")pprint(f"{type(fname) = }")pprint(f"{fname.encode() = }")pprint(f"{fname.encode().decode() = }")pprint(f"{fname.encode(encoding) = }")pprint(f"{fname.encode(encoding).decode(encoding) = }")
关于Python3 的字符串前缀,我有一篇帖子做了总结:
Python3 字符串前缀 u、r、b、f
hyang0,公众号:生有可恋Python 字符串前缀
获取变量名的函数retrieve_name(),代码来源:
https://stackoverflow.com/questions/18425225/getting-the-name-of-a-variable-as-a-string/40536047#40536047
参考:
https://blog.csdn.net/jiangwei741/article/details/103801578
https://www.zhihu.com/question/42768955
https://zhuaxia.xyz/detail/10076
全文完。
如果转发本文,文末务必注明:“转自微信公众号:生有可恋”。




