为了增加代码的可迁移性,公司要求我们的代码应该在Python3.6及之后的所有版本上都可以直接跑。而我们知道,Python3.6及之后的各个版本差别还是有的,而且有的是致命的,直接导致代码报错。
例如Python3.8及之后fstring的{x=}在低版本就不能用,会报语法错误。如果是异常倒还好处理,try-except然后except里写个低版本的代码就可以了,但是语法错误会导致解释器拒绝执行,因此写在try里都没有用:
a='123'
try:
print(f'{a=}')
except:
print(f'a={a}')
使用Python3.6执行结果:
我开始想找个方法(例如增加参数)使得执行时忽略语法错误,但是没有找到有效的。
后来我想到可以把语法错误的代码以字符串的形式保存,然后执行时再用eval转成Python语句,就成功了:
a='123'
try:
print(eval("f'{a=}'"))
except:
print(f'a={a}')
Python3.6跑的结果是:
a=123
Python3.8跑的结果是:
a=‘123’
可见try-except生效了,我们可以用Python3.6单独跑一下try里面的代码:
虽然还是报语法错误,但是变成异常了,会被处理。
注意,try里不能写成print(f"{eval(‘{a=}’)}“),因为转换后成了print(f”{{a=}}"),嵌套了两层{},即使用Python3.8跑也会抛出异常。
如果觉得try-except不够优雅,可以执行时判断Python版本决定执行不同版本的代码,两种方法是等效的:
import sys
a='123'
if sys.version_info > (3, 8):
print(eval("f'{a=}'"))
else:
print(f'a={a}')
虽然是>(3,8),但是包括3.8的,因为sys.version_info是一个有5位的元组,3.8的各个版本的前两位是3和8,第3位是细分位,例如3.8.3版本就是(3,8,3,…)。我们知道即使是(3,8,0,…)和(3,8)比较时也是更大的,所以3.8的所有细分版本都是会通过的。
同样的,可以用eval方法解决海象(warlus)运算符(只有3.8及以上支持)的兼容问题:
def test_fun(num):
if num:
return (num,num)
else:
return None
try:
a,b=ret if eval('(ret:=test_fun(1))') else (0,0)
except:
ret = test_fun(1)
if ret:
a,b=ret
else:
a,b=(0,0)
print(a,b)
Python3.6及以上版本运行结果均为:
1 1
要注意的是,eval部分不能写成(eval(‘ret:=test_fun(1)’))。因为eval外层的括号会被忽略,而海象运算符的优先级是最低的,会导致执行整个表达式时运算顺序混乱,执行时会报错。
当然,虽然这种方法可以兼容不同的Python版本,但是会导致代码变得复杂,因此除非很有必要用只有高版本支持的写法,否则统一用低版本的写法是最简洁的。