如何在使用新语言特性的程序中检查Python版本?

251
如果我有一个要求至少特定版本Python的Python脚本,当使用早期版本的Python启动脚本时,正确的失败处理方式是什么?如何在足够早的时间获取控制权以发出错误消息并退出?例如,我有一个程序使用三元运算符(2.5中新引入)和“with”块(2.6中新引入)。我编写了一个简单的解释器版本检查例程,这是脚本将调用的第一件事...但它并没有达到那个地步。相反,在我的例程甚至被调用之前,脚本在Python编译期间失败。因此,脚本的用户会看到一些非常晦涩的语法错误回溯 - 这几乎需要专家才能推断出它只是运行了错误版本的Python。我知道如何检查Python的版本。问题是某些语法在旧版本的Python中是不合法的。考虑这个程序:
import sys
if sys.version_info < (2, 4):
    raise "must use python 2.5 or greater"
else:
    # syntax error in 2.4, ok in 2.5
    x = 1 if True else 2
    print x

在2.4版本下运行,我希望得到这个结果。
$ ~/bin/python2.4 tern.py 
must use python 2.5 or greater

而不是这个结果:

$ ~/bin/python2.4 tern.py 
  File "tern.py", line 5
    x = 1 if True else 2
           ^
SyntaxError: invalid syntax

(Channeling for a coworker.)


4
@S.Lott 您并没有错,只是困难在于将代码包含在某个地方,使其既不被阅读(解析),也不被执行 - 这一点并不立即显而易见,正如答案所示。 - Brendan
7
S.Lott,你无法在旧版本的Python中执行你的测试,因为它不能编译。相反,你会收到一个通用的语法错误。尝试使用2.4解释器运行示例代码,你会发现你无法进入版本测试。 - Mark Harrison
7
@S.Lott 这要看您认为什么是琐碎的了-就我个人而言,我不认为为不同版本的Python创建单独的文件或生成额外的进程是琐碎的事情。我认为这个问题很有价值,特别是考虑到Python充满了巧妙而常常令人惊讶的技巧-我从Google来到这里,想知道是否有一个巧妙的答案。 - Brendan
3
@S.Lott,在我的程序中,第一个条件语句正在检查版本号,因此问题不在于如何检查版本号。请键入示例程序,使用Python 2.4运行它,然后修复程序以避免SyntaxError错误。发布你的答案,如果还有疑问,我会在那里跟进。 - Mark Harrison
7
我认为我们已经讨论到了尽头。我问了一个我不知道如何做的问题,得到了一个告诉我如何做的答案。我没有提出任何建议,我只是接受了orip的答案,它对我而言非常有效(实际上是我代表的同事)。堆栈溢出万岁! - Mark Harrison
显示剩余10条评论
19个回答

118
您可以使用 eval 进行测试:
try:
  eval("1 if True else 2")
except SyntaxError:
  # doesn't have ternary

此外,在Python 2.5中还提供了with语句,只需添加from __future__ import with_statement即可。

编辑:为了尽早获得控制权,您可以将其拆分为不同的.py文件,并在导入之前在主文件中检查兼容性(例如,在包中的__init__.py中):

# __init__.py

# Check compatibility
try:
  eval("1 if True else 2")
except SyntaxError:
  raise ImportError("requires ternary support")

# import from another module
from impl import *

11
这是一个很棒的回答。需要解决的主要问题是,程序必须在该版本的Python中语法正确才能开始执行,因此使用新语法会导致程序无法在旧版解释器上启动。eval可以解决这个问题。 - Autoplectic
7
如果这个包是由setuptools安装的,那么字节编译源文件会失败。另外,为了产生运行时错误消息而进行的所有扭曲似乎有点毫无意义--为什么不只是记录要求并让它保持这样呢? - John Machin
2
请注意,如果尝试检查一个表达式而不是简单语句,则需要使用exec而不是eval。我在尝试编写一个可以在py2k和py3k中都打印到stderr的函数时遇到了这个问题。 - Xiong Chiamiov
2
我认为这个解决方案的更简洁版本应该是将你的“检查”放在一个单独的模块中并导入它(在import语句处包装try/except)。请注意,您可能还需要检查其他事项,而不仅仅是SyntaxError(例如内置函数或标准库的新增内容)。 - Steven

109

在你的程序周围包装一个函数,执行以下操作。

import sys

req_version = (2,5)
cur_version = sys.version_info

if cur_version >= req_version:
   import myApp
   myApp.run()
else:
   print "Your Python interpreter is too old. Please consider upgrading."

如果你计划遇到使用Python 2.0之前版本解释器的人,你还可以考虑使用 sys.version(),但是这样你需要使用一些正则表达式。

当然,也可能有更加优雅的方法来解决这个问题。


7
“cur_version >= req_version” 可以作为条件语句。 - orip
5
sys.version_info 不是一个函数。 - nh2
4
像这样将代码放在成功条件内是非常不好的做法,因为这会增加不必要的缩进和逻辑判断。只需执行以下操作:如果sys.version_info[:2] < req_version,则打印“old”并退出系统 - 否则继续按照通常的方式进行。 - timss
2
就像 Tim Peters 在《Python之禅》中所说的那样:“扁平比嵌套好。”(你可以在 Python 中输入“import this”来查看) - Christopher Shroba
1
@ChristopherShroba 谢谢你的 import this。这是一个美妙的消遣。 - Samuel Harmer

34

尝试

import platform
platform.python_version()

应该会给您一个像“2.3.1”这样的字符串。如果这不是您想要的,那么可以通过内置的“platform”模块获得丰富的数据集合。您想要的内容应该在其中某个地方。


5
这样行不通,如更新的问题所述。如果你使用任何来自新版本 Python 的语法,你的文件将无法编译,如果无法编译,就无法运行和检查版本! - Scott Griffiths
1
@ScottGriffiths 要运行 print(platform.python_version()) 而不是 platform.python_version() - Suriyaa
@ScottGriffiths 也请查看我的回答:https://dev59.com/jXRB5IYBdhLWcg3w-8Ho#40633458。 - Suriyaa

22

做版本比较最好的方式可能是使用sys.hexversion。这很重要,因为在所有Python版本中,比较版本元组并不能给你期望的结果。

import sys
if sys.hexversion < 0x02060000:
    print "yep!"
else:
    print "oops!"

我认为这是最优雅的写法,但可能不太容易被其他开发人员理解。 - Nick Bolton
6
在什么情况下,比较版本元组将无法给出期望的结果? - SpoonMeiser
版本元组也可以包含字母数字值。 - sorin
8
这不起作用,正如更新的问题所解释的那样。如果你使用任何来自较新版本Python的语法,则你的文件将无法编译,如果它不能编译,则无法运行和检查版本! - Scott Griffiths
这个问题出现在哪个版本/平台上? - sorin

15
import sys    
# prints whether python is version 3 or not
python_version = sys.version_info.major
if python_version == 3:
    print("is python 3")
else:
    print("not python 3")

7
请注意,在Python 2.6及以下版本,sys.version_info不是一个命名元组。您需要使用sys.version_info[0]获取主版本号,并使用sys.version_info[1]获取次版本号。 - coredumperror

9
AskUbuntu中Nykakin的答案:
您还可以使用标准库中的platform模块从代码本身检查Python版本。
有两个函数:
  • platform.python_version()(返回字符串)。
  • platform.python_version_tuple()(返回元组)。

Python代码

创建一个文件,例如:version.py

检查版本的简单方法:

import platform

print(platform.python_version())
print(platform.python_version_tuple())

你也可以使用 eval 方法:

try:
  eval("1 if True else 2")
except SyntaxError:
  raise ImportError("requires ternary support")

在命令行中运行Python文件:

$ python version.py 
2.7.11
('2', '7', '11')

通过WAMP Server在Windows 10上使用CGI运行Python的输出结果:

Suriyaa Kudo的2016年11月16日14:39:01截图


有用的资源


8

Sets 在 Python 2.4 中成为核心语言的一部分,以保持向后兼容性。我当时做了这个,这也适用于您:

if sys.version_info < (2, 4):
    from sets import Set as set

3
是不是最好检查特征而不是版本呢? 尝试:set except NameError: from sets import Set as set - orip
@orip:为什么?如果你知道一个功能是在哪个版本中引入的,比如这里的集合,就可以使用上面的代码。没有任何问题。 - André

7
虽然问题是:“如何在启动应用程序之前及时获取控制权并发出错误消息和退出?”但我回答的问题是:“如何在启动应用程序之前及时获得控制权以发出错误消息?”与其他帖子不同,我的回答方式有很大不同。到目前为止,看起来所有的答案都试图从 Python 内部解决您的问题。我建议,在启动 Python 之前进行版本检查。我看到您的路径是 Linux 或 unix。但我只能提供 Windows 脚本。我认为将其适配到 Linux 脚本语法并不会太难。以下是带有 2.7 版本的 DOS 脚本:
@ECHO OFF
REM see http://ss64.com/nt/for_f.html
FOR /F "tokens=1,2" %%G IN ('"python.exe -V 2>&1"') DO ECHO %%H | find "2.7" > Nul
IF NOT ErrorLevel 1 GOTO Python27
ECHO must use python2.7 or greater
GOTO EOF
:Python27
python.exe tern.py
GOTO EOF
:EOF

这不会运行您应用程序的任何部分,因此不会引发Python异常。它不会创建任何临时文件或添加任何操作系统环境变量。由于不同版本语法规则的异常,它不会将您的应用程序结束为一个例外。这是三个可能的安全访问点的减少。

FOR /F 行是关键。

FOR /F "tokens=1,2" %%G IN ('"python.exe -V 2>&1"') DO ECHO %%H | find "2.7" > Nul

如果您需要检查多个Python版本,请访问以下网址:http://www.fpschultze.de/modules/smartfaq/faq.php?faqid=17

以下是我的hack版本:

[MS脚本;Python版本检查Python模块的预发射]http://pastebin.com/aAuJ91FQ


对于那些给负评的人,请不要害怕解释原因。 - DevPlayer
正是我正在寻找的。谢谢!%%H 是什么意思? - Clocker
@Clocker python.exe -V 会返回一个“Python 2.7”的字符串。控制台将“Python”字符串放入%%G中,自动创建的os变量%%H(在G之后的下一个字母)中包含“2.7”字符串。Echo %%H | find "2.7" 将“2.7”传输到DOS命令find “2.7”中,如果在“2.7”中找到了%%H,则将错误级别设置为1。使用DOS查找命令得到的错误级别为1,将允许我们跳转到DOS批处理标签:Python27。 - DevPlayer

3
import sys
sys.version

您将得到以下答案

'2.7.6(默认,2016年10月26日,20:30:19)\n [GCC 4.8.4]'

这里的2.7.6是版本号


2
将以下内容放在文件的最顶端:
import sys

if float(sys.version.split()[0][:3]) < 2.7:
    print "Python 2.7 or higher required to run this code, " + sys.version.split()[0] + " detected, exiting."
    exit(1)

然后继续正常编写Python代码:
import ...
import ...
other code...

1
使用 sys.version_info < (2, 7) - Antti Haapala -- Слава Україні
@AnttiHaapala 对我来说这非常完美,您能否解释一下为什么将sys.version_info类型与一个元组进行比较是有效的? - jjj
@jjj sys.version_info 曾经是 一个元组,例如(2, 4, 6, 'final', 0);直到Python 3和2.7版本将其更改为独立类型,但仍可与元组进行比较。 - Antti Haapala -- Слава Україні
@AnttiHaapala 我喜欢这种方法比我的更好...谢谢! - jml

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接