¿Cómo puedo verificar la versión de Python en un programa que usa nuevas características de lenguaje?

239

Si tengo un script de Python que requiere al menos una versión particular de Python, ¿cuál es la forma correcta de fallar con gracia cuando se usa una versión anterior de Python para iniciar el script?

¿Cómo obtengo el control lo suficientemente temprano como para emitir un mensaje de error y salir?

Por ejemplo, tengo un programa que usa el operador ternery (nuevo en 2.5) y bloques "con" (nuevo en 2.6). Escribí una pequeña rutina de verificación de versión de intérprete que es lo primero que llamaría el script ... excepto que no llega tan lejos. En cambio, el script falla durante la compilación de Python, incluso antes de que se llame a mis rutinas. Por lo tanto, el usuario del script ve algunos rastreos de error de sinax muy oscuros, que requieren que un experto deduzca que es simplemente el caso de ejecutar la versión incorrecta de Python.

Sé cómo verificar la versión de Python. El problema es que cierta sintaxis es ilegal en versiones anteriores de Python. Considere este programa:

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

Cuando se ejecuta por debajo de 2.4, quiero este resultado

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

y no este resultado:

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

(Canalizando para un compañero de trabajo).

Mark Harrison
fuente
3
"verifique la versión de python. El problema es que cierta sintaxis es ilegal en versiones anteriores de python". No entiendo cómo esto es un problema. Si puede verificar la versión, puede evitar el error de sintaxis. ¿Cómo no se aplica la verificación de versiones a la sintaxis? ¿Puedes aclarar tu pregunta?
S.Lott
44
@ S.Lott No, no está equivocado, solo que la dificultad está en incluir el código en algún lugar donde tampoco se leerá (analizará) ni se ejecutará; esto no es evidente de inmediato como muestran las respuestas.
Brendan
77
S.Lott, no puedes ejecutar tu prueba en la versión anterior de python porque no se compila. En cambio, obtienes un error de sintaxis genérico. Pruebe el código de ejemplo con un intérprete 2.4 y verá que no puede acceder a la prueba de versión.
Mark Harrison
77
@ S.Lott Bueno, depende de lo que consideres trivial: personalmente no consideraría crear archivos separados para diferentes versiones de Python o generar procesos adicionales triviales. Diría que esta pregunta es valiosa, especialmente cuando consideras que Python está lleno de trucos ingeniosos y a menudo sorprendentes. Vine aquí de Google que quería saber si había una respuesta ordenada
Brendan
77
Creo que hemos llegado al final de esta discusión. Hice una pregunta sobre algo que no sabía cómo hacer y obtuve una respuesta que me decía cómo hacerlo. No estoy proponiendo nada, solo acepté la respuesta de Orip, que está funcionando muy bien para mí (en realidad, el compañero de trabajo para el que estoy canalizando). Viva Le Stack Desbordamiento!
Mark Harrison el

Respuestas:

111

Puedes probar usando eval:

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

Además, with está disponible en Python 2.5, solo agregue from __future__ import with_statement.

EDITAR: para obtener el control lo suficientemente temprano, puede dividirlo en diferentes .pyarchivos y verificar la compatibilidad en el archivo principal antes de importar (por ejemplo, __init__.pyen un paquete):

# __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
fuente
10
Esta es una respuesta fantástica. El problema principal de la pregunta que necesitaba abordarse es que un programa debe ser sintácticamente correcto para que esa versión de Python comience a ejecutarse, por lo que el uso de una nueva sintaxis impide que un programa se inicie en versiones anteriores del intérprete. eval funciona alrededor de eso
Autoplectic
77
Si setuptools está instalando el paquete, la compilación de bytes de los archivos fuente fallará. Además, todas las contorsiones para producir un mensaje de error en tiempo de ejecución parecen un poco inútiles, ¿por qué no simplemente documentar los requisitos y dejarlo así?
John Machin
2
Tenga en cuenta que si intenta verificar una expresión, en lugar de una simple declaración, debe usarla en execlugar de eval. Se me ocurrió esto al intentar escribir una función que se imprimiera en stderr tanto en py2k como en py3k.
Xiong Chiamiov
2
Creo que una versión más limpia de esta solución sería colocar sus "cheques" en un módulo separado e importarlo (envolver la importestación en try / except). Tenga en cuenta que es posible que también necesite verificar otras cosas SyntaxError(por ejemplo, funciones incorporadas o adiciones a la biblioteca estándar)
Steven
103

Tenga un envoltorio alrededor de su programa que haga lo siguiente.

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

También puede considerar usar sys.version(), si planea encontrarse con personas que usan intérpretes de Python anteriores a 2.0, pero luego tiene algunas expresiones regulares que hacer.

Y puede haber formas más elegantes de hacer esto.

Ed Carrel
fuente
77
Para su información, "cur_version> = req_version" debería funcionar como condicional.
orip
44
sys.version_infoNo es una función.
nh2
3
Poner código dentro del condicional exitoso de esa manera es una práctica bastante mala, ya que es una sangría innecesaria y una adición de lógica. Simplemente haga un: if sys.version_info [: 2] <req_version: print "old"; sys.exit () - y de lo contrario continuará como de costumbre.
timss
1
Es como Tim Peters dice en "The Zen of Python": "Flat es mejor que anidado". (Puedes ver esto escribiendo "importar esto" en python)
Christopher Shroba
1
@ChristopherShroba Gracias por import this. Una encantadora diversión.
Samuel Harmer
32

Tratar

plataforma de importación
platform.python_version ()

Debería darle una cadena como "2.3.1". Si esto no es exactamente lo que desea, hay un amplio conjunto de datos disponibles a través de la "plataforma" incorporada. Lo que quieras debe estar allí en alguna parte.

James Anderson
fuente
44
-1: Esto no funciona, como se explica en la pregunta actualizada. Si usa cualquier sintaxis de una versión más reciente de Python, entonces su archivo no se compilará, y si no se compila, ¡no se puede ejecutar y verificar la versión!
Scott Griffiths
1
@ScottGriffiths ¡Corre en print(platform.python_version())lugar de platform.python_version()!
Suriyaa
@ScottGriffiths También verifique mi respuesta: stackoverflow.com/a/40633458/5157221 .
Suriyaa
22

Probablemente la mejor manera de hacer esta comparación de versiones es usar el sys.hexversion. Esto es importante porque comparar tuplas de versión no le dará el resultado deseado en todas las versiones de Python.

import sys
if sys.hexversion < 0x02060000:
    print "yep!"
else:
    print "oops!"
Sorin
fuente
Creo que esto es más elegante, pero probablemente no sea el más fácil de entender por otros desarrolladores.
Nick Bolton
55
¿Puede explicar en qué circunstancias comparar tuplas de versión no dará el resultado deseado?
SpoonMeiser
Las tuplas de versión también pueden contener valores alfanuméricos.
sorin
8
-1: Esto no funciona, como se explica en la pregunta actualizada. Si usa cualquier sintaxis de una versión más reciente de Python, entonces su archivo no se compilará, y si no se compila, ¡no se puede ejecutar y verificar la versión!
Scott Griffiths
¿En qué versión / plataforma falla esto?
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
fuente
77
Tenga en cuenta que en Python 2.6 y versiones posteriores, nosys.version_info es una tupla con nombre. Deberá usarlo para el número de versión principal y para el menor. sys.version_info[0]sys.version_info[1]
coredumperror
9

Respuesta de Nykakin en AskUbuntu :

También puede verificar la versión de Python desde el propio código utilizando el platformmódulo de la biblioteca estándar.

Hay dos funciones:

  • platform.python_version() (devuelve cadena).
  • platform.python_version_tuple() (devuelve tupla).

El código de Python

Crear un archivo por ejemplo: version.py)

Método fácil para verificar la versión:

import platform

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

También puedes usar el evalmétodo:

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

Ejecute el archivo Python en una línea de comando:

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

La salida de Python con CGI a través de un servidor WAMP en Windows 10:

Captura de pantalla 2016-11-16 14.39.01 por Suriyaa Kudo


Recursos útiles

Suriyaa
fuente
7

Los conjuntos se convirtieron en parte del lenguaje central en Python 2.4, para seguir siendo compatibles con versiones anteriores. Hice esto en ese entonces, lo que también funcionará para usted:

if sys.version_info < (2, 4):
    from sets import Set as set
André
fuente
3
Es mejor verificar la función en lugar de la versión, ¿no? try: set except NameError: from sets import Set as set
orip
@orip: ¿Por qué? Si sabe en qué versión se ha introducido una característica, como los conjuntos aquí, solo use el código anterior. Nada de malo con eso.
André
7

Aunque la pregunta es: ¿cómo obtengo el control lo suficientemente temprano como para emitir un mensaje de error y salir ?

La pregunta que respondo es: ¿Cómo obtengo el control lo suficientemente temprano como para emitir un mensaje de error antes de iniciar la aplicación ?

Puedo responderlo de manera muy diferente a las otras publicaciones. Parece que las respuestas hasta ahora están tratando de resolver su pregunta desde Python.

Digo, verifique la versión antes de iniciar Python. Veo que tu camino es Linux o Unix. Sin embargo, solo puedo ofrecerle un script de Windows. Imagino que adaptarlo a la sintaxis de scripting de Linux no sería demasiado difícil.

Aquí está el script de DOS con la versión 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

Esto no ejecuta ninguna parte de su aplicación y, por lo tanto, no generará una excepción de Python. No crea ningún archivo temporal ni agrega ninguna variable de entorno del sistema operativo. Y no finaliza su aplicación con una excepción debido a las diferentes reglas de sintaxis de versión. Esos son tres puntos de acceso de seguridad menos posibles.

La FOR /Flínea es la clave.

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

Para ver varias versiones de Python, consulte la URL: http://www.fpschultze.de/modules/smartfaq/faq.php?faqid=17

Y mi versión pirateada:

[Secuencia de comandos MS; Verificación previa de versión de Python del módulo Python] http://pastebin.com/aAuJ91FQ

DevPlayer
fuente
Para esos votos negativos, no tenga miedo de explicar los motivos.
DevPlayer
Exactamente lo estaba buscando. ¡Gracias! ¿Cuál es el %% H?
Clocker
@Clocker python.exe -V devolvería una cadena "Python 2.7". La consola coloca la cadena "Python" en %% G y la cadena "2.7" en el os var %% H creado automáticamente (la siguiente letra después de G). Eco %% H | find "2.7" canaliza "2.7" en el comando DOS find "2.7" que establece el nivel de error en 1 si %% H se encuentra en "2.7". Ese nivel de error, que resulta en un 1, al usar el comando de búsqueda de DOS, nos permitirá pasar a la etiqueta de lote de DOS: Python27
DevPlayer
3
import sys
sys.version

recibirá una respuesta como esta

'2.7.6 (predeterminado, 26 de octubre de 2016, 20:30:19) \ n [GCC 4.8.4]'

aquí 2.7.6 es la versión

Janarthanan Ramu
fuente
2

Como se señaló anteriormente, los errores de sintaxis se producen en tiempo de compilación, no en tiempo de ejecución. Si bien Python es un "lenguaje interpretado", el código de Python no se interpreta directamente; se compila en código de bytes, que luego se interpreta. Hay un paso de compilación que ocurre cuando se importa un módulo (si no hay una versión ya compilada disponible en forma de archivo .pyc o .pyd) y es cuando obtiene su error, no (exactamente) cuando Tu código se está ejecutando.

Puede aplazar el paso de compilación y hacer que suceda en tiempo de ejecución para una sola línea de código, si lo desea, utilizando eval, como se indicó anteriormente, pero personalmente prefiero evitar hacerlo, ya que hace que Python funcione potencialmente compilación innecesaria en tiempo de ejecución, por una parte, y por otra, crea lo que para mí se siente como desorden de código. (Si lo desea, puede generar código que genere código que genere código, y pasar un tiempo absolutamente fabuloso modificando y depurando eso en 6 meses a partir de ahora). Entonces, lo que recomendaría en su lugar es algo más como esto:

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

.. lo que haría incluso si solo tuviera una función que usara una sintaxis más nueva y fuera muy corta. (De hecho, tomaría todas las medidas razonables para minimizar el número y el tamaño de dichas funciones. Incluso podría escribir una función como ifTrueAElseB (cond, a, b) con esa única línea de sintaxis).

Otra cosa que vale la pena señalar (que estoy un poco sorprendido de que nadie haya señalado todavía) es que, si bien las versiones anteriores de Python no admitían código como

value = 'yes' if MyVarIsTrue else 'no'

.. soportaba código como

value = MyVarIsTrue and 'yes' or 'no'

Esa era la vieja forma de escribir expresiones ternarias. Todavía no tengo Python 3 instalado, pero que yo sepa, esa forma "antigua" todavía funciona hasta el día de hoy, por lo que puede decidir por sí mismo si vale la pena usar condicionalmente la nueva sintaxis, si es necesario. para admitir el uso de versiones anteriores de Python.

Shavais
fuente
2
¿Seriamente? ¿Duplicar su código solo para que pueda cambiar algunas estructuras menores? Yuck Muy asqueroso Y en a and b or clugar de b if a else c, no es equivalente; si bes falso, fallará, produciendo amás que b.
Chris Morgan
1
No sugiero duplicar el código, sugiero crear funciones de contenedor para el código específico de la versión, cuyas firmas no cambian entre versiones, y poner esas funciones en módulos específicos de la versión. Estoy hablando de funciones que pueden tener de 1 a 5 líneas de largo. Es cierto que a y b o c no son lo mismo que b si a else c en los casos en que b puede evaluarse como falso. Entonces supongo que si AThenBElseC (a, b, c) en common_ops_2_4.py, tendría que tener 2 o 3 líneas de largo en lugar de 1. Este método en realidad reduce su código general al encapsular modismos comunes en funciones.
Shavais
2

Ponga lo siguiente en la parte superior de su archivo:

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)

Luego continúe con el código Python normal:

import ...
import ...
other code...
jml
fuente
1
prefiero usarsys.version_info < (2, 7)
Antti Haapala
@AnttiHaapala esto es perfecto para mí, ¿puedes explicar por qué funciona la comparación del sys.version_infotipo con una tupla?
jjj
@jjj sys.version_info solía ser una tupla; por ejemplo (2, 4, 6, 'final', 0); solo en Python 3 y 2.7 se cambió a un tipo separado, que sin embargo es comparable con las tuplas.
Antti Haapala
@AnttiHaapala Me gusta ese enfoque mejor que el mío ... ¡gracias!
jml
1

Creo que la mejor manera es probar la funcionalidad en lugar de las versiones. En algunos casos, esto es trivial, no en otros.

p.ej:

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

Mientras sea lo suficientemente específico en el uso de los bloques try / except, puede cubrir la mayoría de sus bases.

sykora
fuente
2
Tienes razón. Eso es lo que preguntó cómo hacer: a veces, probar las características en la versión Y ni siquiera se compila para bytecode en la versión X, por lo que no se puede hacer directamente.
orip
1

Acabo de encontrar esta pregunta después de una búsqueda rápida mientras intentaba resolver el problema por mí mismo y se me ocurrió un híbrido basado en algunas de las sugerencias anteriores.

Me gusta la idea de DevPlayer de usar un script de contenedor, pero la desventaja es que terminas manteniendo múltiples contenedores para diferentes sistemas operativos, así que decidí escribir el contenedor en python, pero utilicé la misma lógica básica de "agarrar la versión ejecutando el exe" y se le ocurrió esto.

Creo que debería funcionar para 2.5 en adelante. Lo he probado en 2.66, 2.7.0 y 3.1.2 en Linux y 2.6.1 en OS X hasta ahora.

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í, sé que la línea de decodificación / tira final es horrible, pero solo quería obtener rápidamente el número de versión. Voy a refinar eso.

Esto funciona bastante bien para mí por ahora, pero si alguien puede mejorarlo (o decirme por qué es una idea terrible), también sería genial.

Steve
fuente
1

Para los scripts de Python independientes , el siguiente truco de la cadena de documentación del módulo para aplicar una versión de Python (aquí v2.7.x) funciona (probado en * 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
[...]

Esto también debería manejar el ejecutable faltante de Python, pero depende de grep. Ver aquí para el fondo.

akhan
fuente
0

Puedes consultar con sys.hexversiono sys.version_info.

sys.hexversionno es muy amigable para los humanos porque es un número hexadecimal. sys.version_infoes una tupla, por lo que es más amigable para los humanos.

Verifique Python 3.6 o más reciente 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()

Verifique Python 3.6 o más reciente 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_infoes más amigable para los humanos, pero toma más personajes. Lo recomendaría sys.hexversion, aunque es menos amigable para los humanos.

¡Espero que esto te ayude!

Pb2007
fuente
0

Estoy ampliando excelente respuesta de Akhan, que imprime un mensaje útil antes de la secuencia de comandos de Python es aún compilado.

Si desea asegurarse de que el script se ejecuta con Python 3.6 o posterior, agregue estas dos líneas en la parte superior del script de 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 segunda línea comienza con cuatro comillas simples y termina con tres comillas simples. Esto puede parecer extraño, pero no es un error tipográfico).

La ventaja de esta solución es que el código como print(f'Hello, {name}!')no causará una SyntaxErrorsi se usa una versión de Python anterior a 3.6. Verás este útil mensaje en su lugar:

This script requires Python 3.6 or newer.

Por supuesto, esta solución solo funciona en shells similares a Unix, y solo cuando el script se invoca directamente (como ./script.py:), y con los bits de permiso eXecute establecidos.

JL
fuente
-2

Qué tal esto:

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
fuente
55
-1: Esto no funciona, como se explica en la pregunta actualizada. Si usa cualquier sintaxis de una versión más reciente de Python, entonces su archivo no se compilará, y si no se compila, ¡no se puede ejecutar y verificar la versión!
Scott Griffiths
-3

El problema es bastante simple. Usted verificó si la versión era menor que 2.4, no menor o igual que . Entonces, si la versión de Python es 2.4, no es menor que 2.4. Lo que deberías haber tenido fue:

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

no

    if sys.version_info < (2, 4):
Peter Mortensen
fuente
44
lea el párrafo 3 y la actualización. no lo hace hasta el punto de ejecutar este código porque su código no se compilará en 2.4 si está utilizando las nuevas construcciones de lenguaje.
Mark Harrison el
Menos de lo que estaba bien, solo faltaba la evaluación.
Craig