Come posso verificare la versione di Python in un programma che utilizza nuove funzionalità linguistiche?


239

Se ho uno script Python che richiede almeno una versione particolare di Python, qual è il modo corretto di fallire con grazia quando una versione precedente di Python viene utilizzata per avviare lo script?

Come posso ottenere il controllo abbastanza presto per emettere un messaggio di errore ed uscire?

Ad esempio, ho un programma che utilizza l'operatore ternery (nuovo in 2.5) e i blocchi "con" (nuovo in 2.6). Ho scritto una semplice routine di verifica della versione dell'interprete che è la prima cosa che lo script chiamerebbe ... tranne che non va così lontano. Invece, lo script fallisce durante la compilazione di Python, prima ancora che vengano chiamate le mie routine. Quindi l'utente dello script vede alcune tracce di errori di sinossi molto oscure - che praticamente richiedono un esperto per dedurre che è semplicemente il caso di eseguire la versione sbagliata di Python.

So come controllare la versione di Python. Il problema è che alcune sintassi sono illegali nelle versioni precedenti di Python. Considera questo programma:

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

Quando eseguito sotto 2.4, voglio questo risultato

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

e non questo risultato:

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

(Canalizzazione per un collega.)


3
"controlla la versione di Python. Il problema è che alcune sintassi sono illegali nelle versioni precedenti di Python." Non capisco come sia un problema. Se riesci a controllare la versione, puoi evitare l'errore di sintassi. In che modo il controllo della versione non si applica alla sintassi? Puoi chiarire la tua domanda?
S. Lott,

4
@ S.Lott No, non ti sbagli, è solo che la difficoltà sta nell'includere il codice da qualche parte dove non verrà nemmeno letto (analizzato) e non eseguito - questo non è immediatamente evidente come mostrano le risposte.
Brendan,

7
S.Lott, non è possibile eseguire il test nella vecchia versione di Python perché non viene compilato. Al contrario, viene visualizzato un errore di sintassi generico. Prova il codice di esempio con un interprete 2.4 e vedrai che non puoi arrivare al test della versione.
Mark Harrison,

7
@ S.Lott Beh, dipende da ciò che consideri banale - personalmente non prenderei in considerazione la creazione di file separati per diverse versioni di Python o la generazione di processi extra banali. Direi che questa domanda è preziosa, specialmente se consideri che Python è pieno di trucchi accurati e spesso sorprendenti - Sono venuto qui da Google per sapere se c'era una risposta chiara
Brendan,

7
Penso che abbiamo raggiunto la fine di questa discussione. Ho fatto una domanda su qualcosa che non sapevo come fare e ho ricevuto una risposta che mi diceva come farlo. Non sto proponendo nulla, ho appena accettato la risposta di Orip che funziona alla grande per me (in realtà il collega per il quale sto canalizzando). Viva Le Stack Overflow!
Mark Harrison,

Risposte:


111

Puoi provare usando eval:

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

Inoltre, with è disponibile in Python 2.5, basta aggiungere from __future__ import with_statement.

EDIT: per ottenere il controllo abbastanza presto, è possibile dividerlo in diversi .pyfile e verificare la compatibilità nel file principale prima dell'importazione (ad esempio __init__.pyin un pacchetto):

# __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
questa è una risposta fantastica il problema principale della domanda che era necessario affrontare è che un programma deve essere sintatticamente corretto affinché quella versione di Python possa persino iniziare l'esecuzione, quindi l'uso di una nuova sintassi impedisce a un programma di avviarsi su versioni precedenti dell'interprete. eval aggira il
problema

7
Se il pacchetto viene installato da setuptools, la compilazione in byte dei file di origine avrà esito negativo. Inoltre, tutte le contorsioni per produrre un messaggio di errore di runtime sembrano un po 'inutili: perché non documentare semplicemente i requisiti e lasciarlo a quello?
John Machin,

2
Si noti che se si tenta di controllare un'espressione, piuttosto che una semplice istruzione, è necessario utilizzare execinvece di eval. Ho avuto questa idea mentre cercavo di scrivere una funzione che avrebbe stampato su stderr sia in py2k che in py3k.
Xiong Chiamiov

2
Penso che una versione più pulita di questa soluzione sarebbe quella di mettere i tuoi "controlli" in un modulo separato e importarli (avvolgere la importstazione in try / tranne). Si noti che potrebbe essere necessario verificare altre cose SyntaxErroroltre (ad es. Funzioni integrate o aggiunte alla libreria standard)
Steven

103

Avere un wrapper attorno al programma che procede come segue.

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

Puoi anche prendere in considerazione l'uso sys.version(), se prevedi di incontrare persone che usano interpreti Python precedenti alla 2.0, ma hai alcune espressioni regolari da fare.

E potrebbero esserci modi più eleganti per farlo.


7
Cordiali saluti, "cur_version> = req_version" dovrebbe funzionare come condizionale.
orip

4
sys.version_infonon è una funzione.
nh2,

3
Inserire il codice all'interno del condizionale di successo in questo modo è una pratica piuttosto negativa in quanto è un rientro e un'aggiunta di logica non necessari. Basta fare un: if sys.version_info [: 2] <req_version: print "old"; sys.exit () - e altrimenti continua come al solito.
timss

1
È come dice Tim Peters in "The Zen of Python": "Flat è meglio di nidificato". (Puoi vederlo digitando "import this" in python)
Christopher Shroba,

1
@ChristopherShroba Grazie per import this. Una bella diversione.
Samuel Harmer,

32

Provare

piattaforma di importazione
platform.python_version ()

Dovrebbe darti una stringa come "2.3.1". Se questo non è esattamente ciò che desideri, è disponibile un ricco set di dati tramite il build-in "platform". Quello che vuoi dovrebbe essere lì da qualche parte.


4
-1: non funziona, come spiegato nella domanda aggiornata. Se usi una sintassi di una versione più recente di Python, il tuo file non verrà compilato e se non viene compilato non può essere eseguito e controllare la versione!
Scott Griffiths

1
@ScottGriffiths Run print(platform.python_version())invece di platform.python_version()!
Suriyaa,

@ScottGriffiths Controlla anche la mia risposta: stackoverflow.com/a/40633458/5157221 .
Suriyaa,

22

Probabilmente il modo migliore per fare questo confronto di versioni è usare il sys.hexversion. Questo è importante perché il confronto delle tuple di versione non ti darà il risultato desiderato in tutte le versioni di Python.

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

Penso che questo sia il più elegante, ma probabilmente non il più facile da capire da altri sviluppatori.
Nick Bolton,

5
Puoi spiegare in quali circostanze il confronto delle tuple di versione non darà il risultato desiderato?
SpoonMeiser,

le tuple di versione possono contenere anche valori alfanumerici.
Sorin,

8
-1: non funziona, come spiegato nella domanda aggiornata. Se usi una sintassi di una versione più recente di Python, il tuo file non verrà compilato e se non viene compilato non può essere eseguito e controllare la versione!
Scott Griffiths

Su quale versione / piattaforma fallisce?
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
Tieni presente che in Python 2.6 e versioni precedenti nonsys.version_info è una tupla con nome. Dovrai utilizzarlo per il numero di versione principale e per il minore. sys.version_info[0]sys.version_info[1]
coredumperror,

9

Risposta di Nykakin su AskUbuntu :

Puoi anche controllare la versione di Python dal codice stesso usando il platformmodulo dalla libreria standard.

Ci sono due funzioni:

  • platform.python_version() (restituisce una stringa).
  • platform.python_version_tuple() (restituisce tupla).

Il codice Python

Crea un file per esempio version.py:)

Metodo semplice per verificare la versione:

import platform

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

Puoi anche usare il evalmetodo:

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

Esegui il file Python in una riga di comando:

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

L'output di Python con CGI tramite un server WAMP su Windows 10:

Schermata 16/11/2016 14.39.01 di Suriyaa Kudo


Risorse utili


7

I set sono diventati parte del linguaggio principale in Python 2.4, al fine di rimanere retrocompatibili. L'ho fatto allora, che funzionerà anche per te:

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

3
meglio verificare la funzionalità invece della versione, no? try: set except NameError: from sets import Set as set
orip,

@orip: perché? Se sai in quale versione è stata introdotta una funzione, come i set qui, usa semplicemente il codice sopra. Niente di sbagliato in questo.
André,

7

Anche se la domanda è: come posso ottenere il controllo abbastanza presto per emettere un messaggio di errore ed uscire ?

La domanda a cui rispondo è: come posso ottenere il controllo abbastanza presto da inviare un messaggio di errore prima di avviare l'app ?

Posso rispondere molto diversamente dagli altri post. Sembra che le risposte finora stiano cercando di risolvere la tua domanda all'interno di Python.

Dico, fai il controllo della versione prima di avviare Python. Vedo che il tuo percorso è Linux o unix. Tuttavia, posso offrirti solo uno script di Windows. Immagino che adattarlo alla sintassi degli script di Linux non sarebbe troppo difficile.

Ecco lo script DOS con la versione 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

Questo non esegue alcuna parte dell'applicazione e quindi non genererà un'eccezione Python. Non crea alcun file temporaneo né aggiunge variabili di ambiente del sistema operativo. E non termina l'app con un'eccezione a causa delle diverse regole di sintassi della versione. Sono tre i possibili punti di accesso di sicurezza in meno.

La FOR /Flinea è la chiave.

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

Per più versioni di Python, controlla l'URL: http://www.fpschultze.de/modules/smartfaq/faq.php?faqid=17

E la mia versione hack:

[Sceneggiatura MS; Controllo della versione di Python pre-avvio del modulo Python] http://pastebin.com/aAuJ91FQ


Per quei voti negativi, non abbiate paura di spiegare i motivi.
DevPlayer il

Stavo esattamente cercando. Grazie! Qual è il %% H?
Clocker,

@Clocker python.exe -V restituisce una stringa "Python 2.7". La console inserisce la stringa "Python" in %% G e la stringa "2.7" nel sistema operativo var creato automaticamente var %% H (la lettera successiva dopo G). Eco %% H | trova "2.7" pipe "2.7" nel comando DOS trova "2.7" che imposta il livello di errore su 1 se %% H viene trovato in "2.7". Quel livello di errore, risultante in 1, dall'uso del comando find di DOS, ci permetterà di passare all'etichetta batch DOS: Python27
DevPlayer

3
import sys
sys.version

riceverà una risposta in questo modo

"2.7.6 (impostazione predefinita, 26 ottobre 2016, 20:30:19) \ n [GCC 4.8.4]"

qui 2.7.6 è la versione


2

Come notato sopra, gli errori di sintassi si verificano in fase di compilazione, non in fase di esecuzione. Mentre Python è un "linguaggio interpretato", il codice Python non viene effettivamente interpretato direttamente; viene compilato in codice byte, che viene quindi interpretato. Esiste un passaggio di compilazione che si verifica quando viene importato un modulo (se non è disponibile una versione già compilata sotto forma di file .pyc o .pyd) ed è quando si riceve l'errore, non (esattamente) il tuo codice è in esecuzione.

Puoi rimandare la fase di compilazione e farlo accadere in fase di esecuzione per una singola riga di codice, se lo desideri, usando eval, come notato sopra, ma personalmente preferisco evitare di farlo, perché fa sì che Python funzioni potenzialmente compilazione di runtime non necessaria, per una cosa e per un'altra, crea quello che a me sembra disordine del codice. (Se vuoi, puoi generare codice che genera codice che genera codice - e divertirti in modo assolutamente favoloso modificandolo e eseguendo il debug tra 6 mesi.) Quindi ciò che consiglierei invece è qualcosa di più simile a questo:

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

.. cosa farei anche se avessi una sola funzione che utilizzava la sintassi più recente ed era molto breve. (In effetti prenderei ogni misura ragionevole per ridurre al minimo il numero e la dimensione di tali funzioni. Potrei persino scrivere una funzione come ifTrueAElseB (cond, a, b) con quella singola riga di sintassi in essa.)

Un'altra cosa che potrebbe valere la pena sottolineare (che sono un po 'stupito che nessuno abbia ancora sottolineato) è che mentre le versioni precedenti di Python non supportavano codice come

value = 'yes' if MyVarIsTrue else 'no'

.. ha supportato il codice come

value = MyVarIsTrue and 'yes' or 'no'

Quello era il vecchio modo di scrivere espressioni ternarie. Non ho ancora installato Python 3, ma per quanto ne so, quel "vecchio" modo funziona ancora oggi, quindi puoi decidere da solo se valga la pena usare condizionalmente la nuova sintassi, se necessario per supportare l'uso di versioni precedenti di Python.


2
Sul serio? Duplica il tuo codice solo per poter modificare alcune strutture minori? Che schifo. Molto schifo. E per quanto riguarda a and b or cinvece di b if a else c, non è equivalente; se bè falsa, fallirà, producendo apiuttosto che b.
Chris Morgan,

1
Non sto suggerendo di duplicare il codice, sto suggerendo di creare funzioni wrapper per il codice specifico della versione, le cui firme non cambiano tra le versioni e di inserirle in moduli specifici della versione. Sto parlando di funzioni lunghe forse da 1 a 5 righe. È vero che a e b o c non sono uguali a b se a altro c nei casi in cui b può essere valutato come falso. Quindi immagino che ifAThenBElseC (a, b, c) in common_ops_2_4.py, dovrebbe essere lungo 2 o 3 righe invece di 1. Questo metodo in realtà riduce il tuo codice su tutto incapsulando idiomi comuni in funzioni.
Shavais,

2

Inserisci quanto segue all'inizio del tuo file:

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)

Quindi continua con il normale codice Python:

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

1
piuttosto usaresys.version_info < (2, 7)
Antti Haapala il

@AnttiHaapala questo è perfetto per me, puoi spiegare perché il confronto del sys.version_infotipo con una tupla funziona?
jjj,

@jjj sys.version_info era una tupla; per esempio (2, 4, 6, 'final', 0); solo in Python 3 e 2.7 è stato cambiato in un tipo separato, che è comunque paragonabile alle tuple.
Antti Haapala,

@AnttiHaapala Mi piace questo approccio meglio del mio ... grazie!
jml

1

Penso che il modo migliore sia testare la funzionalità piuttosto che le versioni. In alcuni casi, questo è banale, non così in altri.

per esempio:

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

Finché sei abbastanza specifico nell'uso dei blocchi try / tranne, puoi coprire la maggior parte delle tue basi.


2
Hai ragione. Questo è quello che ha chiesto come fare: a volte testare le funzionalità nella versione Y non si compila nemmeno in bytecode nella versione X, quindi non può essere eseguito direttamente.
orip

1

Ho appena trovato questa domanda dopo una rapida ricerca mentre cercavo di risolvere il problema da solo e ho trovato un ibrido basato su alcuni dei suggerimenti sopra.

Mi piace l'idea di DevPlayer di usare uno script wrapper, ma il rovescio della medaglia è che si finisce per mantenere più wrapper per diversi sistemi operativi, quindi ho deciso di scrivere il wrapper in Python, ma usare la stessa logica "prendi la versione eseguendo l'ex" e ho pensato a questo.

Penso che dovrebbe funzionare per 2.5 e versioni successive. Finora l'ho provato su 2.66, 2.7.0 e 3.1.2 su Linux e 2.6.1 su 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") )

Sì, so che la decodifica finale / la linea di striping è orribile, ma volevo solo prendere rapidamente il numero di versione. Lo perfezionerò.

Questo funziona abbastanza bene per me per ora, ma se qualcuno può migliorarlo (o dirmi perché è un'idea terribile) sarebbe anche bello.


1

Per gli script Python autonomi , il seguente trucco docstring del modulo per imporre una versione di Python (qui v2.7.x) funziona (testato su * 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
[...]

Questo dovrebbe gestire anche l'eseguibile python mancante, ma ha una dipendenza da grep. Vedi qui per lo sfondo.


0

Puoi verificare con sys.hexversiono sys.version_info.

sys.hexversionnon è molto amico degli umani perché è un numero esadecimale. sys.version_infoè una tupla, quindi è più amico dell'uomo.

Verifica di Python 3.6 o versioni successive con 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()

Verifica di Python 3.6 o versioni successive con 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è più amico dell'uomo, ma richiede più personaggi. Consigliereisys.hexversion , anche se è meno amico degli umani.

Spero che questo ti abbia aiutato!


0

Sto ampliando risposta eccellente del Akhan, che stampa un messaggio utile prima lo script Python sia compilato.

Se vuoi assicurarti che lo script sia eseguito con Python 3.6 o più recente, aggiungi queste due righe nella parte superiore dello script 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 # '''

(Nota: la seconda riga inizia con quattro virgolette singole e termina con tre virgolette singole. Potrebbe sembrare strano, ma non è un refuso.)

Il vantaggio di questa soluzione è che il codice simile print(f'Hello, {name}!')non causa un SyntaxErrorse viene utilizzata una versione di Python precedente alla 3.6. Vedrai invece questo utile messaggio:

This script requires Python 3.6 or newer.

Naturalmente, questa soluzione funziona solo su shell tipo Unix e solo quando lo script viene invocato direttamente (come ./script.py:) e con i bit di autorizzazione eXecute corretti impostati.


-2

Cosa ne pensi di questo:

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: non funziona, come spiegato nella domanda aggiornata. Se usi una sintassi di una versione più recente di Python, il tuo file non verrà compilato e se non viene compilato non può essere eseguito e controllare la versione!
Scott Griffiths

-3

Il problema è abbastanza semplice Hai verificato se la versione era inferiore a 2.4, non inferiore o uguale a . Quindi, se la versione di Python è 2.4, non è inferiore a 2.4. Quello che avresti dovuto avere era:

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

no

    if sys.version_info < (2, 4):

4
leggi il paragrafo 3 e l'aggiornamento. non sei al punto di eseguire questo codice perché il tuo codice non verrà compilato su 2.4 se stai usando i costrutti del nuovo linguaggio.
Mark Harrison,

Il meno che andava bene, mancava solo la valutazione.
Craig
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.