Как я могу проверить версию Python в программе, которая использует новые возможности языка?

239

Если у меня есть сценарий Python, для которого требуется хотя бы определенная версия Python, как правильно выполнить корректный сбой при использовании более ранней версии Python для запуска сценария?

Как получить контроль достаточно рано, чтобы выдать сообщение об ошибке и выйти?

Например, у меня есть программа, которая использует оператор ternery (новый в 2.5) и блоки «with» (новый в 2.6). Я написал простую небольшую подпрограмму проверки версии интерпретатора, которая является первой вещью, которую вызовет сценарий ... за исключением того, что она не зашла так далеко. Вместо этого сценарий завершается ошибкой во время компиляции Python, прежде чем мои процедуры будут даже вызваны. Таким образом, пользователь сценария видит некоторые очень неясные трассировки ошибок Synax, которые в значительной степени требуют от эксперта, чтобы сделать вывод, что это просто случай запуска неправильной версии 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

(Ченнелинг для коллеги.)

Марк Харрисон
источник
3
«Проверьте версию Python. Проблема в том, что некоторый синтаксис является недопустимым в старых версиях Python». Я не понимаю, как это проблема. Если вы можете проверить версию, вы можете избежать синтаксической ошибки. Как проверка версий не применяется к синтаксису? Вы можете уточнить свой вопрос?
S.Lott
4
@ S.Lott Нет, вы не ошибаетесь, просто сложность заключается в том, чтобы включить код где-то, где он также не будет прочитан (проанализирован) и не выполнен - ​​это не сразу видно, как показывают ответы.
Брендан
7
С. Лот, вы не можете выполнить свой тест в старой версии python, потому что он не компилируется. Вместо этого вы получаете общую синтаксическую ошибку. Попробуйте пример кода с интерпретатором 2.4, и вы увидите, что не можете пройти тест версии.
Марк Харрисон
7
@ S.Lott Ну, это зависит от того, что вы считаете тривиальным - лично я бы не стал создавать отдельные файлы для разных версий Python или создавать дополнительные процессы тривиально. Я бы сказал, что этот вопрос является ценным, особенно если учесть, что Python полон изящных и часто удивительных трюков - я пришел сюда из Google, чтобы узнать, был ли точный ответ
Брендан,
7
Я думаю, что мы достигли конца этой дискуссии. Я задал вопрос о том, что я не знал, как сделать, и получил ответ, в котором говорилось, как это сделать. Я ничего не предлагаю, я просто принял ответ Орипа, который отлично работает для меня (на самом деле коллега, для которого я ченнелинг). Viva Le Stack переполнен!
Марк Харрисон

Ответы:

111

Вы можете проверить, используя eval:

try:
  eval("1 if True else 2")
except SyntaxError:
  # doesn't have ternary

Кроме того , with это доступно в Python 2.5, просто добавить 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 *
orip
источник
10
это фантастический ответ. главная проблема вопроса, требующего решения, заключается в том, что программа должна быть синтаксически правильной, чтобы эта версия python даже начала выполняться, поэтому использование нового синтаксиса не позволяет программе запускаться на более старых версиях интерпретатора. Eval работает вокруг этого
Autoplectic
7
Если пакет устанавливается программой установки, тогда байт-компиляция исходных файлов завершится неудачно. Кроме того, все искажения, приводящие к появлению сообщения об ошибке во время выполнения, кажутся немного бессмысленными - почему бы просто не задокументировать требования и оставить все как есть?
Джон Мачин
2
Обратите внимание, что если вы пытаетесь проверить выражение, а не простое утверждение, вам нужно использовать execвместо eval. У меня было это при попытке написать функцию, которая будет печатать в stderr как в py2k, так и в py3k.
Сюн Чямов
2
Я думаю, что более чистой версией этого решения было бы поместить ваши «чеки» в отдельный модуль и импортировать их (оберните importстанцию ​​в try / исключением). Обратите внимание, что вам может потребоваться проверить другие вещи, кроме SyntaxErrorкак (например, встроенные функции или дополнения к стандартной библиотеке)
Стивен
103

Имейте обертку вокруг своей программы, которая делает следующее.

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."

Вы также можете рассмотреть возможность использования sys.version(), если вы планируете встретить людей, которые используют интерпретаторы Python до 2.0, но тогда у вас есть некоторые регулярные выражения, которые нужно сделать.

И могут быть более элегантные способы сделать это.

Эд Каррел
источник
7
К вашему сведению, "cur_version> = req_version" должен работать как условный.
orip
4
sys.version_infoэто не функция.
nh2
3
Помещать код в условное выражение успешно - довольно плохая практика, так как это ненужный отступ и добавление логики. Просто выполните: if sys.version_info [: 2] <req_version: print "old"; sys.exit () - и в противном случае продолжить как обычно.
Timss
1
Это похоже на то, что Тим Питерс говорит в «Дзен Питона»: «Флэт лучше, чем вложенный». (Вы можете увидеть это, набрав «импортировать это» в python)
Кристофер Шроба
1
@ChristopherShroba Спасибо за import this. Прекрасная диверсия.
Сэмюэль Хармер
32

Пытаться

импортная платформа
platform.python_version ()

Должен дать вам строку типа «2.3.1». Если это не совсем то, что вам нужно, есть богатый набор данных, доступных через встроенную платформу. То, что вы хотите, должно быть где-то там.

Джеймс Андерсон
источник
4
-1: это не работает, как объяснено в обновленном вопросе. Если вы используете какой-либо синтаксис из более новой версии Python, то ваш файл не будет скомпилирован, а если он не скомпилируется, он не сможет запустить и проверить версию!
Скотт Гриффитс
1
@ScottGriffiths Run print(platform.python_version())вместо platform.python_version()!
Сурья
@ScottGriffiths Также зацените мой ответ: stackoverflow.com/a/40633458/5157221 .
Сурья
22

Вероятно, лучший способ сделать это сравнение версий - использовать sys.hexversion. Это важно, потому что сравнение кортежей версий не даст желаемого результата во всех версиях Python.

import sys
if sys.hexversion < 0x02060000:
    print "yep!"
else:
    print "oops!"
Сорин
источник
Я думаю, что это наиболее элегантно, но, вероятно, не так просто понять другим разработчикам.
Ник Болтон
5
Можете ли вы объяснить, при каких обстоятельствах сравнение версий кортежей не даст желаемого результата?
SpoonMeiser
Кортежи версии также могут содержать буквенно-цифровые значения.
сорин
8
-1: это не работает, как объяснено в обновленном вопросе. Если вы используете какой-либо синтаксис из более новой версии Python, то ваш файл не будет скомпилирован, а если он не скомпилируется, он не сможет запустить и проверить версию!
Скотт Гриффитс
На какой версии / платформе это терпит неудачу?
сорин
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

Ответ от Nykakin в AskUbuntu :

Вы также можете проверить версию Python из самого кода, используя platformмодуль из стандартной библиотеки.

Есть две функции:

  • 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')

Вывод Python с CGI через сервер WAMP в Windows 10:

Снимок экрана 2016-11-16 14.39.01 от Сурия Кудо


Полезные ресурсы

Suriyaa
источник
7

Наборы стали частью основного языка в Python 2.4, чтобы оставаться обратно совместимыми. Я сделал это тогда, что будет работать и для вас:

if sys.version_info < (2, 4):
    from sets import Set as set
Андре
источник
3
лучше проверить функцию вместо версии, нет? try: set except NameError: from sets import Set as set
orip
@orip: почему? Если вы знаете, в какой версии была представлена ​​функция, например, наборы, просто используйте приведенный выше код. Ничего плохого в этом нет.
Андре
7

Хотя вопрос таков: как получить контроль достаточно рано, чтобы выдать сообщение об ошибке и выйти ?

Вопрос, на который я отвечаю: как получить контроль достаточно рано, чтобы выдать сообщение об ошибке перед запуском приложения ?

Я могу ответить на это по-другому, чем другие посты. Кажется, ответы до сих пор пытаются решить ваш вопрос изнутри Python.

Я говорю, сделайте проверку версии перед запуском Python. Я вижу, ваш путь - это Linux или Unix. Однако я могу предложить вам только сценарий Windows. Я представляю, что адаптировать его к синтаксису сценариев Linux не будет слишком сложно.

Вот сценарий DOS с версией 2.7:

@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 проверьте URL: http://www.fpschultze.de/modules/smartfaq/faq.php?faqid=17

И моя версия взлома:

[MS скрипт; Проверка версии Python перед запуском модуля Python] http://pastebin.com/aAuJ91FQ

DevPlayer
источник
За эти отрицательные голоса, пожалуйста, не бойтесь объяснять причины.
DevPlayer
Именно я искал. Спасибо! Что такое %% H?
Clocker
@Clocker python.exe -V возвращает строку «Python 2.7». Консоль помещает строку «Python» в %% G и строку «2.7» в автоматически создаваемом os var %% H (следующая буква после G). Эхо %% H | найти «2.7» и «2.7» в команду DOS найти «2.7», которая устанавливает уровень ошибки равным 1, если %% H найден в «2.7». Этот уровень ошибки, приводящий к 1 при использовании команды поиска DOS, позволит нам перейти к метке пакета DOS: Python27
DevPlayer
3
import sys
sys.version

будет получать ответ, как это

'2.7.6 (по умолчанию, 26 октября 2016 г., 20:30:19) \ n [GCC 4.8.4]'

здесь 2.7.6 - версия

Джанартанан Раму
источник
2

Как отмечено выше, синтаксические ошибки возникают во время компиляции, а не во время выполнения. Хотя Python является «интерпретируемым языком», код Python фактически не интерпретируется напрямую; он компилируется в байтовый код, который затем интерпретируется. Существует этап компиляции, который происходит, когда модуль импортируется (если нет уже скомпилированной версии в виде файла .pyc или .pyd), и именно тогда вы получаете ошибку, а не (совершенно точно), когда ваш код работает

Вы можете отложить шаг компиляции и сделать это во время выполнения для одной строки кода, если хотите, используя eval, как отмечено выше, но я лично предпочитаю избегать этого, потому что это заставляет Python выполнять потенциально ненужная компиляция во время выполнения, с одной стороны, и с другой, создает то, что для меня ощущается как беспорядок в коде. (Если вы хотите, вы можете сгенерировать код, который генерирует код, который генерирует код - и у вас есть совершенно невероятное время, чтобы изменить и отладить его через 6 месяцев.) Поэтому вместо этого я бы порекомендовал нечто подобное:

import sys
if sys.hexversion < 0x02060000:
    from my_module_2_5 import thisFunc, thatFunc, theOtherFunc
else:
    from my_module import thisFunc, thatFunc, theOtherFunc

... что я бы сделал, даже если бы у меня была только одна функция, использующая новый синтаксис, и она была очень короткой. (На самом деле я бы принял все разумные меры, чтобы минимизировать количество и размер таких функций. Я мог бы даже написать такую ​​функцию, как ifTrueAElseB (cond, a, b) с этой единственной строкой синтаксиса.)

Еще одна вещь, на которую стоит обратить внимание (на которую я немного удивлен, пока никто не указал), это то, что хотя более ранние версии Python не поддерживали такой код, как

value = 'yes' if MyVarIsTrue else 'no'

..это действительно поддерживает код как

value = MyVarIsTrue and 'yes' or 'no'

Это был старый способ написания троичных выражений. У меня еще не установлен Python 3, но, насколько мне известно, этот «старый» способ работает до сих пор, так что вы можете сами решить, стоит ли его условно использовать новый синтаксис, если вам нужно поддерживать использование более старых версий Python.

Shavais
источник
2
Шутки в сторону? Дублируйте свой код только для того, чтобы вы могли изменить некоторые второстепенные структуры? Тьфу. Очень противный А что касается a and b or cвместо b if a else c, это не эквивалентно; если bэто ложь, то она потерпит неудачу, производя, aа не b.
Крис Морган
1
Я не предлагаю дублировать код, я предлагаю создавать функции-оболочки для кода, зависящего от версии, подписи которого не меняются в разных версиях, и помещать эти функции в модули, зависящие от версии. Я говорю о функциях длиной от 1 до 5 строк. Это правда, что a и b или c не совпадают с b, если a else c в случаях, когда b может принимать значение false. Поэтому я предполагаю, что ifAThenBElseC (a, b, c) в common_ops_2_4.py будет иметь длину 2 или 3 строки вместо 1. Этот метод на самом деле сокращает весь ваш код путем инкапсуляции общих идиом в функции.
Shavais
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...
JML
источник
1
скорее используйтеsys.version_info < (2, 7)
Антти Хаапала
@ AnttiHaapala это идеально подходит для меня, вы можете объяснить, почему работает сравнение sys.version_infoтипа с кортежем?
JJJ
@jjj sys.version_info раньше был кортежем; например (2, 4, 6, 'final', 0); только в Python 3 и 2.7 он был изменен на отдельный тип, который, тем не менее, сопоставим с кортежами.
Антти Хаапала
@AnttiHaapala Мне нравится такой подход больше, чем мой ... спасибо!
JML
1

Я думаю, что лучший способ - это проверить функциональность, а не версии. В некоторых случаях это тривиально, а в других - нет.

например:

try :
    # Do stuff
except : # Features weren't found.
    # Do stuff for older versions.

Пока вы достаточно конкретны в использовании блоков try / Кроме того, вы можете охватить большинство ваших баз.

Сикора
источник
2
Ты прав. Это то, что он спросил, как это сделать - иногда тестирование функций в версии Y даже не компилируется в байт-код в версии X, поэтому это нельзя сделать напрямую.
orip
1

Я только нашел этот вопрос после быстрого поиска, пытаясь решить проблему самостоятельно, и я придумал гибрид, основанный на нескольких предложениях выше.

Мне нравится идея DevPlayer об использовании скрипта-обертки, но недостатком является то, что в итоге вы поддерживаете несколько оберток для разных ОС, поэтому я решил написать обертку на python, но использовать ту же базовую логику «захватить версию, запустив exe» и придумал это.

Я думаю, что это должно работать на 2,5 и далее. Я проверил это на 2.66, 2.7.0 и 3.1.2 на Linux и 2.6.1 на OS X до сих пор.

import sys, subprocess
args = [sys.executable,"--version"]

output, error = subprocess.Popen(args ,stdout = subprocess.PIPE, stderr = subprocess.PIPE).communicate()
print("The version is: '%s'"  %error.decode(sys.stdout.encoding).strip("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLMNBVCXZ,.+ \n") )

Да, я знаю, что окончательная линия декодирования / полосы ужасна, но я просто хотела быстро получить номер версии. Я собираюсь уточнить это.

Пока это работает достаточно хорошо для меня, но если кто-то сможет улучшить его (или скажите мне, почему это ужасная идея), это тоже будет круто.

Стив
источник
1

Для автономных сценариев Python работает следующий трюк docstring модуля для принудительного применения версии Python (здесь v2.7.x) (протестировано на * nix).

#!/bin/sh
''''python -V 2>&1 | grep -q 2.7 && exec python -u -- "$0" ${1+"$@"}; echo "python 2.7.x missing"; exit 1 # '''

import sys
[...]

Это также должно обрабатывать отсутствующий исполняемый файл Python, но зависит от grep. Смотрите здесь для фона.

Ахан
источник
0

Вы можете проверить с помощью sys.hexversionили sys.version_info.

sys.hexversionне очень удобен для человека, потому что это шестнадцатеричное число. sys.version_infoэто кортеж, поэтому он более дружелюбен к человеку.

Проверьте для Python 3.6 или новее с sys.hexversion:

import sys, time
if sys.hexversion < 0x30600F0:
    print("You need Python 3.6 or greater.")
    for _ in range(1, 5): time.sleep(1)
    exit()

Проверьте для Python 3.6 или новее с sys.version_info:

import sys, time
if sys.version_info[0] < 3 and sys.version_info[1] < 6:
    print("You need Python 3.6 or greater.")
    for _ in range(1, 5): time.sleep(1)
    exit()

sys.version_infoболее дружественный к человеку, но принимает больше персонажей. Я бы порекомендовал sys.hexversion, хотя это менее дружелюбно к человеку.

Я надеюсь, что это помогло вам!

Pb2007
источник
0

Я расширяю превосходный ответ Ахана, который печатает полезное сообщение еще до того, как скрипт Python будет скомпилирован.

Если вы хотите убедиться, что скрипт выполняется с Python 3.6 или новее, добавьте эти две строки в начало вашего скрипта Python:

#!/bin/sh
''''python3 -c 'import sys; sys.exit(sys.version_info < (3, 6))' && exec python3 -u -- "$0" ${1+"$@"}; echo 'This script requires Python 3.6 or newer.'; exit 1 # '''

(Примечание. Вторая строка начинается с четырех одинарных кавычек и заканчивается тремя одинарными кавычками. Это может показаться странным, но это не опечатка.)

Преимущество этого решения в том, что подобный код print(f'Hello, {name}!')не будет вызывать, SyntaxErrorесли используется версия Python старше 3.6. Вместо этого вы увидите это полезное сообщение:

This script requires Python 3.6 or newer.

Конечно, это решение работает только на Unix-подобных оболочках и только тогда, когда скрипт вызывается напрямую (например ./script.py,:), и с установленными битами разрешений eXecute.

JL
источник
-2

Как насчет этого:

import sys

def testPyVer(reqver):
  if float(sys.version[:3]) >= reqver:
    return 1
  else:
    return 0

#blah blah blah, more code

if testPyVer(3.0) == 1:
  #do stuff
else:
  #print python requirement, exit statement
Pb2007
источник
5
-1: это не работает, как объяснено в обновленном вопросе. Если вы используете какой-либо синтаксис из более новой версии Python, то ваш файл не будет скомпилирован, а если он не скомпилируется, он не сможет запустить и проверить версию!
Скотт Гриффитс
-3

Проблема довольно проста. Вы проверили, была ли версия меньше 2,4, не меньше или равна . Так что, если версия Python 2.4, она не меньше 2.4. Что вы должны были иметь:

    if sys.version_info **<=** (2, 4):

не

    if sys.version_info < (2, 4):
Питер Мортенсен
источник
4
прочитайте параграф 3 и обновите. Вы не собираетесь выполнять этот код, потому что ваш код не скомпилируется на 2.4, если вы используете новые языковые конструкции.
Марк Харрисон
Тем не менее, все было в порядке, просто пропустил Eval.
Крейг