问题描述:
很多人习惯在程序中使用assert断言语句来对某些条件进行约束,如果条件不满足就抛出异常,从而强行阻止执行后面的代码。并且,很多资料建议在开发过程中使用assert进行约束,但是要在发布代码之前删除所有的assert语句。这是为啥呢?这样做是否可以呢?
Python程序运行时有个特殊的只读属性__debug__,源码解释运行(包括使用import导入模块)时值为True,这时assert语句起作用,确实可以在特定条件不满足时阻止执行后面的代码。但是,把Python程序源码使用优化模式编译为字节码后运行时,__debug__的值为False,并且在优化编译时会删除所有的assert语句,再也起不到任何的约束和拦截作用,直接执行后面代码时抛出异常。
例如,有源码文件“临时测试专用.py”,内容如下:
执行该程序时报错,最后一条语句被成功拦截,没有执行,如图
接下来,使用标准库py_compile对源码文件进行编译,得到两种优化级别的字节码,如图
切换到命令提示符cmd环境,分别执行源码文件和两个字节码文件,如图
使用pip安装扩展库uncompyle6,然后对上面得到的两个字节码文件进行反编译,得到2个源码,发现其中都没有assert断言语句,并且特殊属性__debug__被替换为固定值False,如图
综上,在Python程序中应慎用assert断言语句来阻止后面代码的执行,尤其是计划发布优化编译的字节码的场合,除非在开发和测试阶段已经考虑到了所有的可能,并且确保可以安全删除assert断言语句而不影响程序执行。
为进一步验证上面的说法,以下面的猜数游戏代码为例,源码如下:
直接运行源码,如图
代码保存为文件“猜数游戏.py”,使用不设置优化级别和设置优化级别两种方式分别编译为2个字节码文件:
切换到命令提示符cmd环境,执行字节码文件:
查看源码文件属性,大小如图所示:
查看字节码文件大小,优化编译的字节码文件比没有设置优化级别得到的字节码文件小一些。
按照本文前面介绍的方法,对得到的优化字节码文件进行反编译,结果如下,可以发现其中不包含源码中的注释,所有assert语句都被删除。
同理,对不设置优化级别得到的字节码进行反编译,查看源码,会发现没有包含源码中的注释(编译时只会删除井号开头的行注释,不会删除三引号内的块注释,请自行验证),但是保留了assert断言语句。另外,不设置优化级别时,字节码运行时__debug__属性值为True,可自行验证。
由此可以验证,优化编译时确实会删除assert语句,从而使得断言无效。同时也可以发现,把Python程序编译为源码发布并不能对源码起到任何保护,可以很容易地反编译得到源码。如果确实想保护源码,可以使用pyinstaller之类的工具打包为二进制可执行文件,虽然也可以通过特殊手段得到源码,但难度要大上很多,本文不介绍这个技术。