Jak mogę sprawdzić wersję Pythona w programie, który korzysta z nowych funkcji językowych?


239

Jeśli mam skrypt w języku Python, który wymaga co najmniej konkretnej wersji języka Python, jaki jest prawidłowy sposób, by upaść z wdziękiem, gdy do uruchomienia skryptu używana jest wcześniejsza wersja języka Python?

Jak uzyskać kontrolę wystarczająco wcześnie, aby wysłać komunikat o błędzie i wyjść?

Na przykład mam program, który używa operatora ternery (nowy w wersji 2.5) i „z” blokami (nowy w wersji 2.6). Napisałem prostą, małą procedurę sprawdzania wersji interpretera, która jest pierwszą rzeczą, którą skrypt mógłby wywołać ... tyle że nie zaszła tak daleko. Zamiast tego skrypt nie działa podczas kompilacji Pythona, zanim moje procedury zostaną nawet wywołane. W związku z tym użytkownik skryptu widzi pewne bardzo niejasne mechanizmy śledzenia błędów syx - które w zasadzie wymagają od eksperta wywnioskowania, że ​​po prostu chodzi o uruchomienie niewłaściwej wersji Pythona.

Wiem, jak sprawdzić wersję Pythona. Problem polega na tym, że niektóre składnie są nielegalne w starszych wersjach Pythona. Rozważ ten program:

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

Gdy działa poniżej 2.4, chcę ten wynik

$ ~/bin/python2.4 tern.py 
must use python 2.5 or greater

a nie ten wynik:

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

(Kanalizacja dla współpracownika.)


3
„sprawdź wersję Pythona. Problemem jest to, że niektóre składnie są nielegalne w starszych wersjach Pythona.” Nie rozumiem, jak to jest problem. Jeśli możesz sprawdzić wersję, możesz uniknąć błędu składniowego. W jaki sposób sprawdzanie wersji nie dotyczy składni? Czy możesz wyjaśnić swoje pytanie?
S.Lott,

4
@ S.Lott Nie, nie mylisz się, po prostu trudność polega na włączeniu kodu w miejsce, w którym nie będzie on czytany (analizowany) ani wykonywany - nie jest to od razu widoczne, jak pokazują odpowiedzi.
Brendan

7
S.Lott, nie możesz wykonać testu w starej wersji Pythona, ponieważ nie można go skompilować. Zamiast tego pojawia się ogólny błąd składniowy. Wypróbuj przykładowy kod z interpreterem 2.4, a zobaczysz, że nie możesz przejść do testu wersji.
Mark Harrison

7
@ S.Lott Cóż, to zależy od tego, co uważasz za trywialne - osobiście nie rozważałbym tworzenia osobnych plików dla różnych wersji Pythona ani odradzania dodatkowych procesów jako trywialnych. Powiedziałbym, że to pytanie jest cenne, zwłaszcza jeśli uważasz, że Python jest pełen schludnych i często zaskakujących sztuczek - przyszedłem tutaj z Google, aby dowiedzieć się, czy jest trafna odpowiedź
Brendan

7
Myślę, że doszliśmy do końca tej dyskusji. Zadałem pytanie o coś, czego nie wiedziałem, jak to zrobić, i otrzymałem odpowiedź, która mówi mi, jak to zrobić. Nic nie proponuję, po prostu zaakceptowałem odpowiedź orip, która działa świetnie dla mnie (właściwie współpracownika, dla którego przekazuję). Przepełnienie stosu Viva Le Stack!
Mark Harrison,

Odpowiedzi:


111

Możesz przetestować za pomocą eval:

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

Ponadto, with jest dostępny w Pythonie 2.5, wystarczy dodać from __future__ import with_statement.

EDYCJA: aby uzyskać kontrolę wystarczająco wcześnie, możesz podzielić ją na różne .pypliki i sprawdzić zgodność w głównym pliku przed importem (np. W __init__.pypakiecie):

# __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 *

10
to fantastyczna odpowiedź. głównym problemem z pytania, które wymagało adresowania, jest to, że program musi być poprawny pod względem składniowym, aby ta wersja Pythona mogła nawet zacząć się uruchamiać, więc użycie nowej składni uniemożliwia uruchomienie programu w starszych wersjach interpretera. eval działa wokół tego
Autoplectic

7
Jeśli pakiet jest instalowany przez setuptools, kompilacja bajtów plików źródłowych zakończy się niepowodzeniem. Poza tym wszystkie próby wygenerowania komunikatu o błędzie w czasie wykonywania wydają się trochę bezcelowe - dlaczego po prostu nie udokumentować wymagań i na tym pozwolić?
John Machin

2
Zauważ, że jeśli próbujesz sprawdzić wyrażenie zamiast prostej instrukcji, musisz użyć execzamiast eval. Wymyśliłem to podczas próby napisania funkcji, która wypisze na stderr zarówno py2k, jak i py3k.
Xiong Chiamiov

2
Myślę, że czystszą wersją tego rozwiązania byłoby umieszczenie swoich „kontroli” w osobnym module i zaimportowanie tego (zawinięcie importstacji w try / wyjątkiem). Pamiętaj, że może być konieczne sprawdzenie innych rzeczy SyntaxError(np. Wbudowanych funkcji lub dodatków do standardowej biblioteki)
Steven

103

Mają opakowanie wokół programu, który wykonuje następujące czynności.

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

Możesz również rozważyć użycie sys.version(), jeśli planujesz spotkać ludzi, którzy używają interpreterów języka Python w wersjach wcześniejszych niż 2.0, ale wtedy musisz zrobić kilka wyrażeń regularnych.

I mogą być bardziej eleganckie sposoby na zrobienie tego.


7
Do Twojej wiadomości, „cur_version> = req_version” powinno działać jako warunkowe.
orip

4
sys.version_infonie jest funkcją.
nh2

3
Umieszczanie kodu w takich pomyślnych warunkach jest dość złą praktyką, ponieważ jest to niepotrzebne wcięcie i dodanie logiki. Po prostu wykonaj: if sys.version_info [: 2] <req_version: print "old"; sys.exit () - i poza tym kontynuuj jak zwykle.
timss

1
To tak, jak mówi Tim Peters w „Zen of Python”: „Mieszkanie jest lepsze niż zagnieżdżone”. (Możesz to zobaczyć, wpisując „import this” w python)
Christopher Shroba

1
@ChristopherShroba Dziękujemy za import this. Urocza odmiana.
Samuel Harmer,

32

Próbować

platforma importowa
platform.python_version ()

Powinien dać ci ciąg taki jak „2.3.1”. Jeśli nie jest to dokładnie takie, jak chcesz, istnieje bogaty zestaw danych dostępnych za pośrednictwem wbudowanej „platformy”. To, czego chcesz, powinno gdzieś tam być.


4
-1: To nie działa, jak wyjaśniono w zaktualizowanym pytaniu. Jeśli użyjesz dowolnej składni z nowszej wersji Pythona, plik się nie skompiluje, a jeśli się nie skompiluje, nie będzie mógł uruchomić się i sprawdzić wersji!
Scott Griffiths,

1
@ScottGriffiths Run print(platform.python_version())zamiast platform.python_version()!
Suriyaa

@ScottGriffiths Zapoznaj się również z moją odpowiedzią: stackoverflow.com/a/40633458/5157221 .
Suriyaa

22

Prawdopodobnie najlepszym sposobem na porównanie tej wersji jest użycie sys.hexversion. Jest to ważne, ponieważ porównanie krotek wersji nie da pożądanego wyniku we wszystkich wersjach Pythona.

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

Myślę, że jest to najbardziej eleganckie, ale prawdopodobnie nie najłatwiejsze do zrozumienia przez innych twórców.
Nick Bolton

5
Czy możesz wyjaśnić, w jakich okolicznościach porównanie krotek wersji nie da pożądanego rezultatu?
SpoonMeiser,

krotki wersji mogą również zawierać wartości alfanumeryczne.
sorin

8
-1: To nie działa, jak wyjaśniono w zaktualizowanym pytaniu. Jeśli użyjesz dowolnej składni z nowszej wersji Pythona, plik się nie skompiluje, a jeśli się nie skompiluje, nie będzie mógł uruchomić się i sprawdzić wersji!
Scott Griffiths,

Na jakiej wersji / platformie to się nie udaje?
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
Należy pamiętać, że w Pythonie 2.6 i poniżej, sys.version_infoto nie nazwana krotki. Musisz użyć sys.version_info[0]dla głównego numeru wersji i sys.version_info[1]dla mniejszego.
coredumperror

9

Odpowiedź od Nykakina z AskUbuntu :

Możesz również sprawdzić wersję Pythona z samego kodu za pomocą platformmodułu ze standardowej biblioteki.

Istnieją dwie funkcje:

  • platform.python_version() (zwraca ciąg znaków).
  • platform.python_version_tuple() (zwraca krotkę).

Kod Python

Utwórz plik na przykład version.py:)

Łatwa metoda sprawdzenia wersji:

import platform

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

Możesz także użyć evalmetody:

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

Uruchom plik Python w wierszu polecenia:

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

Dane wyjściowe Pythona z CGI za pośrednictwem serwera WAMP w systemie Windows 10:

Zrzut ekranu 16.11.2016 14.39.01 autor: Suriyaa Kudo


Pomocne zasoby


7

Zestawy stały się częścią podstawowego języka Python 2.4, aby zachować kompatybilność wsteczną. Zrobiłem to wtedy, co również zadziała dla ciebie:

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

3
lepiej sprawdzić funkcję zamiast wersji, nie? try: set except NameError: from sets import Set as set
orip

@orip: Dlaczego? Jeśli wiesz, w której wersji została wprowadzona funkcja, np. Tutaj, użyj powyższego kodu. Nic w tym złego.
André

7

Chociaż pytanie brzmi: jak uzyskać kontrolę wystarczająco wcześnie, aby wysłać komunikat o błędzie i wyjść ?

Pytanie, na które odpowiadam brzmi: Jak uzyskać kontrolę wystarczająco wcześnie, aby wydać komunikat o błędzie przed uruchomieniem aplikacji ?

Mogę odpowiedzieć na to znacznie inaczej niż na inne posty. Wygląda na to, że jak dotąd odpowiedzi próbują rozwiązać twoje pytanie z poziomu Pythona.

Mówię, sprawdź wersję przed uruchomieniem Pythona. Widzę, że twoja ścieżka to Linux lub Unix. Mogę jednak zaoferować tylko skrypt systemu Windows. Wyobrażam sobie, że dostosowanie go do składni skryptów linuksowych nie byłoby zbyt trudne.

Oto skrypt DOS z wersją 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

To nie uruchamia żadnej części twojej aplikacji i dlatego nie wywoła wyjątku Python. Nie tworzy żadnych plików tymczasowych ani nie dodaje żadnych zmiennych środowiskowych systemu operacyjnego. I to nie kończy twojej aplikacji na wyjątku z powodu różnych reguł składni wersji. To trzy mniej prawdopodobne punkty bezpieczeństwa dostępu.

The FOR /FLinia jest kluczem.

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

W przypadku wielu wersji Pythona sprawdź adres URL: http://www.fpschultze.de/modules/smartfaq/faq.php?faqid=17

I moja wersja hacka:

[Skrypt MS; Sprawdzanie wersji Python przed uruchomieniem modułu Python] http://pastebin.com/aAuJ91FQ


W przypadku głosów negatywnych proszę nie obawiaj się wyjaśniać powody.
DevPlayer

Właśnie tego szukałem. Dzięki! Jaki jest %% H?
Zegar

@Clocker python.exe -V zwróci ciąg „Python 2.7”. Konsola umieszcza ciąg „Python” w %% G, a ciąg „2.7” w automatycznie utworzonym os var %% H (następna litera po G). Echo %% H | znajdź „2.7” potoki „2.7” w poleceniu DOS znajdź „2.7”, który ustawia poziom błędu na 1, jeśli %% H zostanie znaleziony w „2.7”. Ten poziom błędu, powodujący 1, przy użyciu polecenia DOS find, pozwoli nam rozgałęzić się do etykiety partii DOS: Python27
DevPlayer

3
import sys
sys.version

dostanę taką odpowiedź

„2.7.6 (domyślnie, 26 października 2016 r., 20:30:19) \ n [GCC 4.8.4]”

tutaj 2.7.6 jest wersją


2

Jak wspomniano powyżej, błędy składniowe występują w czasie kompilacji, a nie w czasie wykonywania. Podczas gdy Python jest „językiem interpretowanym”, kod Pythona nie jest w rzeczywistości bezpośrednio interpretowany; jest kompilowany do kodu bajtowego, który jest następnie interpretowany. Jest krok kompilacji, który ma miejsce, gdy moduł jest importowany (jeśli nie ma już skompilowanej wersji dostępnej w postaci pliku .pyc lub .pyd) i wtedy pojawia się błąd, a nie (całkiem dokładnie) kiedy Twój kod działa.

Możesz odłożyć krok kompilacji i sprawić, by stało się to w czasie wykonywania pojedynczego wiersza kodu, jeśli chcesz, używając eval, jak wspomniano powyżej, ale ja osobiście wolę tego unikać, ponieważ powoduje to, że Python może potencjalnie działać niepotrzebna kompilacja w czasie wykonywania, z jednej strony, a z drugiej, tworzy coś, co wydaje mi się bałaganem kodu. (Jeśli chcesz, możesz wygenerować kod, który generuje kod, który generuje kod - i mieć absolutnie fantastyczny czas na modyfikowanie i debugowanie tego za 6 miesięcy od teraz.) Więc zamiast tego poleciłbym coś takiego:

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

.. co zrobiłbym, nawet gdybym miał tylko jedną funkcję, która używałaby nowszej składni i była ona bardzo krótka. (W rzeczywistości podjąłbym wszelkie rozsądne środki, aby zminimalizować liczbę i rozmiar takich funkcji. Mógłbym nawet napisać funkcję taką jak ifTrueAElseB (cond, a, b) z tą pojedynczą linią składni.)

Kolejną rzeczą, na którą warto zwrócić uwagę (jestem trochę zaskoczony, że nikt jeszcze nie zauważył) jest to, że chociaż wcześniejsze wersje Pythona nie obsługiwały kodu takiego jak

value = 'yes' if MyVarIsTrue else 'no'

.. obsługuje kod jak

value = MyVarIsTrue and 'yes' or 'no'

To był stary sposób pisania wyrażeń potrójnych. Nie mam jeszcze zainstalowanego Pythona 3, ale o ile wiem, ten „stary” sposób nadal działa do dziś, więc możesz sam zdecydować, czy warto warunkowo użyć nowej składni, jeśli potrzebujesz do obsługi korzystania ze starszych wersji Pythona.


2
Poważnie? Zduplikować kod, aby móc zmienić niektóre drobne struktury? Fuj Bardzo fuj. A jeśli chodzi a and b or co b if a else c, to nie jest równoważne; jeśli bjest fałszem, zawiedzie, produkując, aa nie b.
Chris Morgan

1
Nie sugeruję powielania kodu, sugeruję utworzenie funkcji opakowania dla kodu specyficznego dla wersji, której podpisy nie zmieniają się w różnych wersjach, i umieszczenie tych funkcji w modułach specyficznych dla wersji. Mówię o funkcjach, które mogą mieć od 1 do 5 linii. Prawdą jest, że aib lub c nie są tym samym co b, jeśli a c w przypadkach, w których b może dać wynik fałszywy. Sądzę więc, że ifAThenBElseC (a, b, c) w pliku common_ops_2_4.py musiałby mieć długość 2 lub 3 wierszy zamiast 1. Ta metoda faktycznie ogranicza cały kod przez enkapsulowanie wspólnych idiomów do funkcji.
Shavais,

2

Umieść u góry pliku następujące informacje:

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)

Następnie przejdź do normalnego kodu Python:

import ...
import ...
other code...

1
raczej użyjsys.version_info < (2, 7)
Antti Haapala

@AnttiHaapala jest to dla mnie idealne, czy możesz wyjaśnić, dlaczego porównanie sys.version_infotypu z krotką działa?
jjj

@jjj sys.version_info stosuje się krotki; na przykład (2, 4, 6, 'final', 0); tylko w Pythonie 3 i 2.7 został zmieniony na osobny typ, który jest jednak porównywalny z krotkami.
Antti Haapala

@AnttiHaapala Podoba mi się to podejście lepiej niż moje ... dzięki!
jml

1

Myślę, że najlepszym sposobem jest przetestowanie funkcjonalności, a nie wersji. W niektórych przypadkach jest to banalne, w innych nie.

na przykład:

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

Tak długo, jak jesteś wystarczająco zaawansowany w korzystaniu z bloków try / wyjątkiem, możesz pokryć większość swoich baz.


2
Masz rację. Tak zapytał, jak to zrobić - czasami testowanie funkcji w wersji Y nawet nie kompiluje się do kodu bajtowego w wersji X, więc nie można tego zrobić bezpośrednio.
orip

1

Właśnie znalazłem to pytanie po szybkim wyszukiwaniu, próbując samodzielnie rozwiązać problem, i opracowałem hybrydę opartą na kilku powyższych sugestiach.

Podoba mi się pomysł DevPlayera na użycie skryptu opakowania, ale wadą jest to, że ostatecznie utrzymujesz wiele opakowań dla różnych systemów operacyjnych, więc postanowiłem napisać opakowanie w pythonie, ale użyj tego samego podstawowego „pobierz wersję, uruchamiając exe” i wymyślił to.

Myślę, że powinno działać od wersji 2.5 i nowszych. Do tej pory przetestowałem go w wersjach 2.66, 2.7.0 i 3.1.2 w systemie Linux i 2.6.1 w systemie 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") )

Tak, wiem, że ostatnia linia dekodowania / stripowania jest okropna, ale chciałem tylko szybko pobrać numer wersji. Udoskonalę to.

Na razie działa mi to wystarczająco dobrze, ale jeśli ktoś może to poprawić (lub powiedzieć mi, dlaczego to okropny pomysł), to też byłoby fajne.


1

W przypadku samodzielnych skryptów Pythona działa następująca sztuczka docstring modułu w celu wymuszenia wersji Pythona (tutaj v2.7.x) (testowana na * 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
[...]

Powinno to również obsługiwać brakujący plik wykonywalny Pythona, ale ma zależność od grep. Zobacz tutaj dla tła.


0

Możesz to sprawdzić za pomocą sys.hexversionlub sys.version_info.

sys.hexversionnie jest zbyt przyjazny dla człowieka, ponieważ jest liczbą szesnastkową. sys.version_infojest krotką, więc jest bardziej przyjazna dla człowieka.

Sprawdź Python 3.6 lub nowszy za pomocą 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()

Sprawdź Python 3.6 lub nowszy za pomocą 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_infojest bardziej przyjazny dla człowieka, ale wymaga więcej postaci. Polecamsys.hexversion , choć jest mniej przyjazny dla człowieka.

Mam nadzieję, że to ci pomogło!


0

Rozwijam doskonałą odpowiedź Akhana, która drukuje przydatną wiadomość przed skompilowaniem skryptu Python.

Jeśli chcesz się upewnić, że skrypt jest uruchamiany w Pythonie 3.6 lub nowszym, dodaj te dwa wiersze u góry skryptu 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 # '''

(Uwaga: Drugi wiersz zaczyna się od czterech pojedynczych cudzysłowów, a kończy na trzech pojedynczymi cudzysłowami. Może to wyglądać dziwnie, ale nie jest to literówka.)

Zaletą tego rozwiązania jest to, że kod podobny print(f'Hello, {name}!')nie spowoduje, SyntaxErrorże zostanie użyta wersja Python starsza niż 3.6. Zamiast tego zobaczysz tę pomocną wiadomość:

This script requires Python 3.6 or newer.

Oczywiście to rozwiązanie działa tylko na powłokach uniksopodobnych i tylko wtedy, gdy skrypt jest wywoływany bezpośrednio (na przykład ./script.py:) i przy ustawionych odpowiednich bitach uprawnień eXecute.


-2

Co powiesz na to:

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

5
-1: To nie działa, jak wyjaśniono w zaktualizowanym pytaniu. Jeśli użyjesz dowolnej składni z nowszej wersji Pythona, plik się nie skompiluje, a jeśli się nie skompiluje, nie będzie mógł uruchomić się i sprawdzić wersji!
Scott Griffiths,

-3

Problem jest dość prosty. Sprawdziłeś, czy wersja była mniejsza niż 2,4, nie mniejsza niż lub równa . Więc jeśli wersja Pythona to 2.4, to nie mniej niż 2.4. Co powinieneś był mieć:

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

, nie

    if sys.version_info < (2, 4):

4
przeczytaj ust. 3 i aktualizację. nie musisz wykonywać tego kodu, ponieważ kod nie będzie się kompilował w wersji 2.4, jeśli używasz nowych konstrukcji języka.
Mark Harrison

Mniej niż było w porządku, po prostu brakuje eval.
Craig
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.