Comment puis-je vérifier la version Python dans un programme qui utilise de nouvelles fonctionnalités de langage?

239

Si j'ai un script Python qui nécessite au moins une version particulière de Python, quelle est la bonne façon d'échouer correctement lorsqu'une version antérieure de Python est utilisée pour lancer le script?

Comment obtenir le contrôle suffisamment tôt pour émettre un message d'erreur et quitter?

Par exemple, j'ai un programme qui utilise l'opérateur de ternerie (nouveau en 2.5) et des blocs "avec" (nouveau en 2.6). J'ai écrit une petite routine simple de vérificateur de version d'interpréteur qui est la première chose que le script appellerait ... sauf qu'il ne va pas aussi loin. Au lieu de cela, le script échoue pendant la compilation de python, avant même que mes routines soient appelées. Ainsi, l'utilisateur du script voit des traces d'erreurs de synchronisation très obscures - qui nécessitent à peu près un expert pour déduire qu'il s'agit simplement d'exécuter la mauvaise version de Python.

Je sais comment vérifier la version de Python. Le problème est qu'une syntaxe est illégale dans les anciennes versions de Python. Considérez ce programme:

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

Lorsque exécuté sous 2.4, je veux ce résultat

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

et non ce résultat:

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

(Canalisation pour un collègue.)

Mark Harrison
la source
3
"vérifiez la version de python. Le problème est qu'une syntaxe est illégale dans les anciennes versions de python." Je ne comprends pas comment c'est un problème. Si vous pouvez vérifier la version, vous pouvez éviter l'erreur de syntaxe. Comment la vérification de version ne s'applique-t-elle pas à la syntaxe? Pouvez-vous clarifier votre question?
S.Lott
4
@ S.Lott Non, vous n'avez pas tort, c'est juste que la difficulté est d'inclure le code quelque part où il ne sera pas non plus lu (analysé) ni exécuté - ce n'est pas immédiatement apparent comme le montrent les réponses.
Brendan
7
S.Lott, vous ne pouvez pas exécuter votre test dans l'ancienne version de python car il ne compile pas. Au lieu de cela, vous obtenez une erreur de syntaxe générique. Essayez l'exemple de code avec un interpréteur 2.4 et vous verrez que vous ne pouvez pas accéder au test de version.
Mark Harrison
7
@ S.Lott Eh bien, cela dépend de ce que vous considérez comme trivial - personnellement, je n'envisagerais pas de créer des fichiers séparés pour différentes versions de Python ou de générer des processus supplémentaires triviaux. Je dirais que cette question est précieuse, surtout lorsque vous considérez que Python est plein de trucs soignés et souvent surprenants - je suis venu ici de Google pour savoir s'il y avait une bonne réponse
Brendan
7
Je pense que nous avons atteint la fin de cette discussion. J'ai posé une question sur quelque chose que je ne savais pas faire et j'ai obtenu une réponse me disant comment le faire. Je ne propose rien, j'ai juste accepté la réponse d'Orip qui fonctionne très bien pour moi (en fait le collègue pour qui je canalise). Viva Le Stack Overflow!
Mark Harrison

Réponses:

111

Vous pouvez tester en utilisant eval:

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

Aussi, with est disponible en Python 2.5, il suffit d'ajouter from __future__ import with_statement.

EDIT: pour obtenir le contrôle suffisamment tôt, vous pouvez le diviser en différents .pyfichiers et vérifier la compatibilité dans le fichier principal avant l'importation (par exemple __init__.pydans un package):

# __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
la source
10
c'est une réponse fantastique. le problème principal de la question qui devait être adressée est qu'un programme doit être syntaxiquement correct pour cette version de python pour même commencer à s'exécuter, donc l'utilisation d'une nouvelle syntaxe empêche un programme de démarrer sur des versions plus anciennes de l'interpréteur. eval fonctionne autour de cela
Autoplectic
7
Si le package est installé par setuptools, la compilation d'octets des fichiers source échouera alors. De plus, toutes les contorsions pour produire un message d'erreur d'exécution semblent un peu inutiles - pourquoi ne pas simplement documenter les exigences et en rester là?
John Machin, le
2
Notez que si vous essayez de vérifier une expression, plutôt qu'une simple instruction, vous devez utiliser à la execplace de eval. J'ai eu ce problème en essayant d'écrire une fonction qui s'imprimerait sur stderr dans py2k et py3k.
Xiong Chiamiov
2
Je pense qu'une version plus propre de cette solution serait de mettre vos "chèques" dans un module séparé et d'importer cela (envelopper la importstation dans try / except). Notez que vous devrez peut-être vérifier autre chose que SyntaxErrorpar exemple (par exemple, les fonctions intégrées ou les ajouts à la bibliothèque standard)
Steven
103

Ayez un wrapper autour de votre programme qui fait ce qui suit.

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

Vous pouvez également envisager d'utiliser sys.version(), si vous prévoyez de rencontrer des personnes qui utilisent des interprètes Python antérieurs à 2.0, mais vous avez alors quelques expressions régulières à faire.

Et il pourrait y avoir des façons plus élégantes de le faire.

Ed Carrel
la source
7
Pour info, "cur_version> = req_version" devrait fonctionner comme conditionnel.
orip
4
sys.version_infon'est pas une fonction.
nh2
3
Mettre du code dans le conditionnel réussi comme ça est une très mauvaise pratique car c'est une indentation et un ajout de logique inutiles. Faites juste un: if sys.version_info [: 2] <req_version: print "old"; sys.exit () - et sinon continuez comme d'habitude.
timss
1
C'est comme Tim Peters le dit dans "Le Zen de Python": "L'appartement est mieux que imbriqué". (Vous pouvez le voir en tapant "importer ceci" en python)
Christopher Shroba
1
@ChristopherShroba Merci pour import this. Une belle diversion.
Samuel Harmer
32

Essayer

plateforme d'importation
platform.python_version ()

Devrait vous donner une chaîne comme "2.3.1". Si ce n'est pas exactement ce que vous voulez, il existe un riche ensemble de données disponibles via le build-in "plateforme". Ce que vous voulez devrait être quelque part.

James Anderson
la source
4
-1: Cela ne fonctionne pas, comme expliqué dans la question mise à jour. Si vous utilisez une syntaxe d'une version plus récente de Python, votre fichier ne sera pas compilé, et s'il ne compile pas, il ne pourra pas s'exécuter et vérifier la version!
Scott Griffiths
1
@ScottGriffiths Run print(platform.python_version())au lieu de platform.python_version()!
Suriyaa
@ScottGriffiths Consultez également ma réponse: stackoverflow.com/a/40633458/5157221 .
Suriyaa
22

La meilleure façon de faire cette comparaison de versions est probablement d'utiliser le sys.hexversion. Ceci est important car la comparaison des tuples de version ne vous donnera pas le résultat souhaité dans toutes les versions de python.

import sys
if sys.hexversion < 0x02060000:
    print "yep!"
else:
    print "oops!"
Sorin
la source
Je pense que c'est le plus élégant, mais probablement pas le plus facile à comprendre par les autres développeurs.
Nick Bolton
5
Pouvez-vous expliquer dans quelles circonstances la comparaison des tuples de version ne donnera pas le résultat souhaité?
SpoonMeiser
les tuples de version peuvent également contenir des valeurs alphanumériques.
sorin
8
-1: Cela ne fonctionne pas, comme expliqué dans la question mise à jour. Si vous utilisez une syntaxe d'une version plus récente de Python, votre fichier ne sera pas compilé, et s'il ne compile pas, il ne pourra pas s'exécuter et vérifier la version!
Scott Griffiths le
Sur quelle version / plateforme cela échoue-t-il?
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")
Erick Wendel
la source
7
Sachez que dans Python 2.6 et inférieur, sys.version_infon'est pas un tuple nommé. Vous devrez utiliser sys.version_info[0]pour le numéro de version majeur et sys.version_info[1]pour le mineur.
coredumperror
9

Réponse de Nykakin à AskUbuntu :

Vous pouvez également vérifier la version Python à partir du code lui-même en utilisant le platformmodule de la bibliothèque standard.

Il y a deux fonctions:

  • platform.python_version() (renvoie une chaîne).
  • platform.python_version_tuple() (retourne un tuple).

Le code Python

Créez un fichier par exemple: version.py)

Méthode simple pour vérifier la version:

import platform

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

Vous pouvez également utiliser la evalméthode:

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

Exécutez le fichier Python dans une ligne de commande:

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

La sortie de Python avec CGI via un serveur WAMP sous Windows 10:

Capture d'écran 2016-11-16 14.39.01 par Suriyaa Kudo


Ressources utiles

Suriyaa
la source
7

Les ensembles sont devenus une partie du langage de base de Python 2.4, afin de rester rétrocompatible. Je l'ai fait à l'époque, ce qui fonctionnera également pour vous:

if sys.version_info < (2, 4):
    from sets import Set as set
André
la source
3
préférable de vérifier la fonctionnalité plutôt que la version, non? try: set except NameError: from sets import Set as set
orip
@orip: Pourquoi? Si vous savez dans quelle version une fonctionnalité a été introduite, comme les ensembles ici, utilisez simplement le code ci-dessus. Aucun problème avec ça.
André
7

Bien que la question soit: comment obtenir le contrôle suffisamment tôt pour émettre un message d'erreur et quitter ?

La question à laquelle je réponds est: comment obtenir le contrôle suffisamment tôt pour émettre un message d'erreur avant de démarrer l'application ?

Je peux y répondre très différemment des autres articles. Il semble que les réponses tentent jusqu'à présent de résoudre votre question depuis Python.

Je dis, faites une vérification de version avant de lancer Python. Je vois que votre chemin est Linux ou unix. Cependant je ne peux que vous proposer un script Windows. Je pense que l'adapter à la syntaxe de script Linux ne serait pas trop difficile.

Voici le script DOS avec la version 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

Cela n'exécute aucune partie de votre application et ne déclenchera donc pas d'exception Python. Il ne crée aucun fichier temporaire et n'ajoute aucune variable d'environnement OS. Et cela ne met pas votre application à une exception en raison de règles de syntaxe de version différentes. C'est trois points d'accès de sécurité moins possibles.

La FOR /Fligne est la clé.

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

Pour plusieurs versions de python, consultez l'url: http://www.fpschultze.de/modules/smartfaq/faq.php?faqid=17

Et ma version hack:

[Script MS; Vérification de la version Python avant le lancement du module Python] http://pastebin.com/aAuJ91FQ

DevPlayer
la source
Pour ces votes négatifs, n'hésitez pas à expliquer pourquoi.
DevPlayer
Exactement ce que je cherchais. Merci! Quel est le %% H?
Clocker
@Clocker python.exe -V retournerait une chaîne "Python 2.7". La console place la chaîne "Python" dans %% G et la chaîne "2.7" dans l'os créé automatiquement %% H (la lettre suivante après G). Écho %% H | trouver "2.7" dirige "2.7" dans la commande DOS find "2.7" qui définit le niveau d'erreur à 1 si %% H se trouve dans "2.7". Ce niveau d'erreur, résultant en un 1, à l'aide de la commande DOS find, nous permettra de nous connecter au label de lot DOS: Python27
DevPlayer
3
import sys
sys.version

obtiendra une réponse comme celle-ci

'2.7.6 (par défaut, 26 octobre 2016, 20:30:19) \ n [GCC 4.8.4]'

ici 2.7.6 est la version

Janarthanan Ramu
la source
2

Comme indiqué ci-dessus, des erreurs de syntaxe se produisent au moment de la compilation et non au moment de l'exécution. Alors que Python est un "langage interprété", le code Python n'est pas réellement directement interprété; il est compilé en code octet, qui est ensuite interprété. Il y a une étape de compilation qui se produit lorsqu'un module est importé (s'il n'y a pas de version déjà compilée disponible sous la forme d'un fichier .pyc ou .pyd) et c'est là que vous obtenez votre erreur, pas (tout à fait exactement) quand votre code est en cours d'exécution.

Vous pouvez différer l'étape de compilation et la faire se produire au moment de l'exécution pour une seule ligne de code, si vous le souhaitez, en utilisant eval, comme indiqué ci-dessus, mais je préfère personnellement éviter de le faire, car cela fait que Python fonctionne potentiellement compilation inutile au moment de l'exécution, pour une chose et pour une autre, cela crée ce qui me semble être un encombrement de code. (Si vous le souhaitez, vous pouvez générer du code qui génère du code qui génère du code - et avoir un temps absolument fabuleux à modifier et à déboguer dans 6 mois.) Donc, je recommanderais plutôt quelque chose comme ceci:

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

.. ce que je ferais même si je n'avais qu'une seule fonction qui utilisait une syntaxe plus récente et c'était très court. (En fait, je prendrais toutes les mesures raisonnables pour minimiser le nombre et la taille de ces fonctions. Je pourrais même écrire une fonction comme ifTrueAElseB (cond, a, b) avec cette seule ligne de syntaxe.)

Une autre chose qui mérite d'être soulignée (que je suis un peu étonné que personne n'ait encore souligné) est que, même si les versions antérieures de Python ne prenaient pas en charge le code comme

value = 'yes' if MyVarIsTrue else 'no'

..il a pris en charge le code comme

value = MyVarIsTrue and 'yes' or 'no'

C'était l'ancienne façon d'écrire des expressions ternaires. Je n'ai pas encore installé Python 3, mais pour autant que je sache, cette "ancienne" méthode fonctionne toujours à ce jour, vous pouvez donc décider vous-même si cela vaut la peine d'utiliser conditionnellement la nouvelle syntaxe, si vous avez besoin pour prendre en charge l'utilisation des anciennes versions de Python.

Shavais
la source
2
Sérieusement? Dupliquez votre code juste pour pouvoir changer certaines structures mineures? Beurk. Très beurk. Et quant à la a and b or cplace de b if a else c, ce n'est pas équivalent; si elle best fausse, elle échouera, produisant aplutôt que b.
Chris Morgan
1
Je ne suggère pas de dupliquer du code, je suggère de créer des fonctions d'encapsuleur pour du code spécifique à la version, dont les signatures ne changent pas entre les versions, et de placer ces fonctions dans des modules spécifiques à la version. Je parle de fonctions qui font peut-être 1 à 5 lignes. Il est vrai que a et b ou c ne sont pas identiques à b si a else c dans les cas où b peut être évalué à faux. Donc je suppose que ifAThenBElseC (a, b, c) dans common_ops_2_4.py, devrait être de 2 ou 3 lignes au lieu de 1. Cette méthode réduit en fait votre code global en encapsulant des idiomes communs dans des fonctions.
Shavais
2

Mettez ce qui suit tout en haut de votre fichier:

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)

Continuez ensuite avec le code Python normal:

import ...
import ...
other code...
jml
la source
1
utilisez plutôtsys.version_info < (2, 7)
Antti Haapala
@AnttiHaapala c'est parfait pour moi, pouvez-vous expliquer pourquoi comparer le sys.version_infotype à un tuple fonctionne?
jjj
@jjj sys.version_info était un tuple; par exemple (2, 4, 6, 'final', 0); ce n'est qu'en Python 3 et 2.7 qu'il a été changé en un type distinct, qui est néanmoins comparable aux tuples.
Antti Haapala
@AnttiHaapala J'aime cette approche mieux que la mienne ... merci!
jml
1

Je pense que la meilleure façon est de tester les fonctionnalités plutôt que les versions. Dans certains cas, c'est trivial, pas dans d'autres.

par exemple:

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

Tant que vous êtes suffisamment précis pour utiliser les blocs try / except, vous pouvez couvrir la plupart de vos bases.

sykora
la source
2
Vous avez raison. C'est ce qu'il a demandé comment faire - parfois, tester les fonctionnalités de la version Y ne se compile même pas en bytecode dans la version X, donc cela ne peut pas être fait directement.
orip
1

Je viens de trouver cette question après une recherche rapide tout en essayant de résoudre le problème moi-même et j'ai trouvé un hybride basé sur quelques-unes des suggestions ci-dessus.

J'aime l'idée de DevPlayer d'utiliser un script d'encapsuleur, mais l'inconvénient est que vous finissez par maintenir plusieurs encapsuleurs pour différents systèmes d'exploitation, j'ai donc décidé d'écrire l'encapsuleur en python, mais utilisez la même logique de base "saisir la version en exécutant l'exe". et est venu avec cela.

Je pense que cela devrait fonctionner pour 2.5 et au-delà. Je l'ai testé sur 2.66, 2.7.0 et 3.1.2 sur Linux et 2.6.1 sur OS X jusqu'à présent.

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

Oui, je sais que la ligne finale de décodage / strip est horrible, mais je voulais juste saisir rapidement le numéro de version. Je vais affiner cela.

Cela fonctionne assez bien pour moi pour l'instant, mais si quelqu'un peut l'améliorer (ou me dire pourquoi c'est une terrible idée), ce serait cool aussi.

Steve
la source
1

Pour les scripts python autonomes , l'astuce docstring de module suivante pour appliquer une version python (ici v2.7.x) fonctionne (testée sur * 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
[...]

Cela devrait également gérer l'exécutable python manquant mais a une dépendance sur grep. Voir ici pour le contexte.

akhan
la source
0

Vous pouvez vérifier avec sys.hexversionou sys.version_info.

sys.hexversionn'est pas très convivial pour les humains car c'est un nombre hexadécimal. sys.version_infoest un tuple, il est donc plus convivial pour l'homme.

Vérifiez Python 3.6 ou plus récent avec 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()

Vérifiez Python 3.6 ou plus récent avec 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_infoest plus convivial pour l'homme, mais prend plus de personnages. Je recommanderais sys.hexversion, même si c'est moins convivial pour l'homme.

J'espère que cela vous a aidé!

Pb2007
la source
0

Je développe l'excellente réponse d'Akhan, qui affiche un message utile avant même que le script Python ne soit compilé.

Si vous voulez vous assurer que le script est exécuté avec Python 3.6 ou plus récent, ajoutez ces deux lignes en haut de votre 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 # '''

(Remarque: la deuxième ligne commence par quatre guillemets simples et se termine par trois guillemets simples. Cela peut sembler étrange, mais ce n'est pas une faute de frappe.)

L'avantage de cette solution est que le code comme print(f'Hello, {name}!')ne causera pas de SyntaxErrorsi une version Python plus ancienne que 3.6 est utilisée. Vous verrez plutôt ce message utile:

This script requires Python 3.6 or newer.

Bien sûr, cette solution ne fonctionne que sur des shells de type Unix, et uniquement lorsque le script est appelé directement (tel que ./script.py:), et avec les bons bits d'autorisation eXecute définis.

JL
la source
-2

Que dis-tu de ça:

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
la source
5
-1: Cela ne fonctionne pas, comme expliqué dans la question mise à jour. Si vous utilisez une syntaxe d'une version plus récente de Python, votre fichier ne sera pas compilé, et s'il ne compile pas, il ne pourra pas s'exécuter et vérifier la version!
Scott Griffiths le
-3

Le problème est assez simple. Vous avez vérifié si la version était inférieure à 2,4, pas inférieure ou égale à . Donc, si la version Python est 2.4, elle n'est pas inférieure à 2.4. Vous auriez dû avoir:

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

, ne pas

    if sys.version_info < (2, 4):
Peter Mortensen
la source
4
lire le paragraphe 3 et la mise à jour. vous n'êtes pas au point d'exécuter ce code car votre code ne se compilera pas sur 2.4 si vous utilisez les nouvelles constructions de langage.
Mark Harrison
Le moins que c'était bien, il manquait juste l'eval.
Craig