检查包__init__.py中包含较新语法的Python版本



在我的程序中检查Python版本通常是没有问题的,但如果使用语法,通常该如何做,而旧的解释器不知道这一点?

考虑子目录/包foo:中的以下__init__.py

import sys
__version__ = '0.1'
try:
    if sys.version_info[:2] < (3,8):
        print("you're doomed")
    else:
        print("you're fine")
    print(f"{__version__=}")
    
except SyntaxError:
    print("why is this line not reached on importing?")
print("here we go")

现在,当试图导入该包时,会出现SyntaxError:

$ python3
Python 3.7.7 (default, Apr  1 2020, 13:48:52) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<fstring>", line 1
    (__version__=)
                ^
SyntaxError: invalid syntax
>>> 

或直接使用:

$ python3 foo/__init__.py                                                                                       /dev/shm |myself@mydesk|0|11:53:43 
  File "<fstring>", line 1
    (__version__=)
                ^
SyntaxError: invalid syntax

尽管我试图捕捉异常(如果没有相应的eval,SyntaxError当然不可能捕捉到异常(,但如果解释器版本低于3.8,Python甚至不会运行该文件,因为使用了新的f-string"="说明符。(在这个最小的例子中,有人可能会建议把它放在一边,但在我的真实包中,它在很多地方都被使用,包括许多子包的__init__.py;特别是为了方便日志记录。(

那么,在这样的场景中,我们如何确保使用新语法(包括给用户的警告消息(的Python最低版本是正确的呢?

我能想到的最简单的解决方案是不在__init__.py中放入版本特定的语法,以确保它始终可以被解析。

import sys
__version__ = '0.1'
if sys.version_info[:2] < (3,8):
    raise RuntimeError("You're doomed.")
from .baz import quux
from .hernekeitto import teline

这个文件总是可以用…来解析和执行。。。基本上是2.7以上版本的任何Python,并且由于导入是运行时的,所以只有当baz.pyhernekeitto.py使用深奥的语法时,程序才会开始崩溃。

在通过评论进行简短讨论后,我们明确了目标是尽可能多地使用最新的语法,并满足以下要求:

  • 在给定模块中包含指令(代码(,以便在导入模块时,执行该模块以向用户提供将遇到SyntaxError的警告
  • 在没有间接模块的情况下实现所有这些

不幸的是,将所有这些结合起来实际上是不可能的。Python必须首先成功解析整个源文件,然后才能执行任何指令。换句话说,导致SyntaxError解析失败的语法只会导致源文件中的任何代码都没有被转换为可以执行的字节码指令。

这种类型的更改并非没有优先级-Python 3最初重新定义了str令牌的语法,使其不接受u前缀(例如u'Hello'(。然后,Python 3.3出现了,并(重新(引入了该语法(来自Python 2.7(,作为使库开发人员更容易将代码从Python 2迁移到Python 3的努力的一部分,这意味着包含该u前缀字符串的Python模块将不适用于Python 3.2,但适用于Python 3.3。

考虑到在给定Python文件中的单个字符串中仅存在u前缀就会破坏Python 3.2,除了在任何代码(包括版本检测(之前删除源文件中的所有u前缀之外,没有任何可能的缓解措施。这种情况与您希望使用新的f-string语法进行日志记录的愿望并无不同。

正如@MisterMiyagi所指出的,模块的版本管理是";meta";,或者环境的管理,所以这应该不是在运行时从模块内管理的,而是应该在环境级别处理的。像pip这样的Python封装实用程序将正确地防止安装标记有超出当前使用的Python版本的模块,这应该是这样做的方式。

对于与语法无关的其他情况,例如更改标准库的导入位置,这可能会以类似于问题本身的方式被捕获——当代码被正确解析时,try...except块可以变成字节码指令,然后可以执行。

或者,如果语法问题不够普遍,无法阻止这种方法的可能性,则可以使用间接模块导入,如提供的答案@AKX所建议的导入。然而,这将提出如何处理回退的问题,但这又回到了将问题放回应该解决的正确域(即包管理(。

最新更新