Como posso verificar a versão do Python em um programa que usa novos recursos de linguagem?

239

Se eu tenho um script Python que requer pelo menos uma versão específica do Python, qual é a maneira correta de falhar normalmente quando uma versão anterior do Python é usada para iniciar o script?

Como obtenho controle cedo o suficiente para emitir uma mensagem de erro e sair?

Por exemplo, eu tenho um programa que usa o operador ternery (novo no 2.5) e blocos "com" (novo no 2.6). Eu escrevi uma rotina simples de verificação de versão para intérpretes, que é a primeira coisa que o script chamaria ... exceto que não chega tão longe. Em vez disso, o script falha durante a compilação do python, antes mesmo de minhas rotinas serem chamadas. Assim, o usuário do script vê alguns rastreamentos de erros de sinaxia muito obscuros - o que exige que um especialista deduza que é simplesmente o caso de executar a versão errada do Python.

Eu sei como verificar a versão do Python. O problema é que algumas sintaxes são ilegais nas versões mais antigas do 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

Quando executado sob 2.4, quero esse resultado

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

e não este resultado:

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

(Canalizando para um colega de trabalho.)

Mark Harrison
fonte
3
"verifique a versão do python. O problema é que algumas sintaxes são ilegais nas versões mais antigas do python." Eu não entendo como isso é um problema. Se você pode verificar a versão, pode evitar o erro de sintaxe. Como a verificação de versão não se aplica à sintaxe? Você pode esclarecer sua pergunta?
S.Lott
4
@ S.Lott Não, você não está errado, apenas que a dificuldade está em incluir o código em algum lugar onde ele também não será lido (analisado) e não executado - isso não é imediatamente aparente, como mostram as respostas.
Brendan
7
S.Lott, você não pode executar seu teste na versão antiga do python porque ele não é compilado. Em vez disso, você recebe um erro de sintaxe genérico. Experimente o código de exemplo com um intérprete 2.4 e você verá que não pode acessar o teste da versão.
Mark Harrison
7
@ S.Lott Bem, isso depende do que você considera trivial - pessoalmente, eu não consideraria criar arquivos separados para diferentes versões do Python ou gerar processos extras triviais. Eu diria que esta questão é importante, especialmente quando você considera Python é cheio de truques surpreendentes limpo e muitas vezes - eu vim aqui do Google querendo saber se havia uma resposta pura
Brendan
7
Acho que chegamos ao fim desta discussão. Fiz uma pergunta sobre algo que não sabia como fazer e recebi uma resposta dizendo como fazê-lo. Não estou propondo nada, apenas aceitei a resposta do orip, que está funcionando muito bem para mim (na verdade, o colega de trabalho para quem estou canalizando). Viva Le Stack Overflow!
Mark-Harrison #

Respostas:

111

Você pode testar usando eval:

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

Além disso, with está disponível no Python 2.5, basta adicionar from __future__ import with_statement.

EDIT: para obter o controle cedo o suficiente, você pode dividi-lo em .pyarquivos diferentes e verificar a compatibilidade no arquivo principal antes de importar (por exemplo, __init__.pyem um pacote):

# __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
fonte
10
Esta é uma resposta fantástica. o principal problema da pergunta que precisava ser abordada é que um programa deve estar sintaticamente correto para que essa versão do python comece a executar; portanto, o uso de nova sintaxe impede que um programa seja iniciado em versões mais antigas do intérprete. eval funciona em torno disso
Autoplectic
7
Se o pacote estiver sendo instalado pelo setuptools, a compilação de bytes dos arquivos de origem falhará. Além disso, todas as contorções para produzir uma mensagem de erro em tempo de execução parecem um pouco inúteis - por que não apenas documentar os requisitos e deixar por isso mesmo?
John Machin
2
Observe que, ao tentar verificar uma expressão, em vez de uma simples declaração, você precisa usar em execvez de eval. Eu tive esse problema ao tentar escrever uma função que seria impressa em stderr em py2k e py3k.
Xiong Chiamiov
2
Eu acho que uma versão mais limpa dessa solução seria colocar seus "cheques" em um módulo separado e importá-los (embrulhe a importestação em try / except). Observe que você pode precisar verificar outras coisas além SyntaxErrordisso (por exemplo, funções internas ou adições à biblioteca padrão)
Steven
103

Tenha um wrapper ao redor do seu programa que faça o seguinte.

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

Você também pode considerar o uso sys.version(), se planeja encontrar pessoas que estão usando intérpretes Python anteriores à 2.0, mas ainda tem algumas expressões regulares a fazer.

E pode haver maneiras mais elegantes de fazer isso.

Ed Carrel
fonte
7
Para sua informação, "cur_version> = req_version" deve funcionar como condicional.
orip
4
sys.version_infonão é uma função.
Nh2 6/08/11
3
Colocar o código dentro do condicional bem-sucedido dessa maneira é uma prática muito ruim, pois é um recuo desnecessário e uma adição de lógica. Basta fazer um: se sys.version_info [: 2] <req_version: print "old"; sys.exit () - e continue como de costume.
TIMSS
1
É como Tim Peters diz em "O Zen do Python": "O apartamento é melhor que o aninhado". (Você pode ver isso digitando "importar isso" em python)
Christopher Shroba 27/10/2015
1
@ChristopherShroba Obrigado por import this. Uma diversão adorável.
Samuel Harmer
32

Experimentar

plataforma de importação
platform.python_version ()

Deve fornecer uma string como "2.3.1". Se isso não for exatamente o que você deseja, existe um rico conjunto de dados disponíveis por meio do build-in "plataforma". O que você quer deve estar lá em algum lugar.

James Anderson
fonte
4
-1: isso não funciona, conforme explicado na pergunta atualizada. Se você usar qualquer sintaxe de uma versão mais recente do Python, seu arquivo não será compilado e, se não for compilado, não poderá ser executado e verificar a versão!
22412 Scott Sciffiths #
1
@ScottGriffiths Corra em print(platform.python_version())vez de platform.python_version()!
Suriyaa
@ScottGriffiths Também verifique minha resposta: stackoverflow.com/a/40633458/5157221 .
Suriyaa
22

Provavelmente, a melhor maneira de fazer essa comparação de versão é usar o sys.hexversion. Isso é importante porque comparar as tuplas de versão não fornecerá o resultado desejado em todas as versões do python.

import sys
if sys.hexversion < 0x02060000:
    print "yep!"
else:
    print "oops!"
sorin
fonte
Eu acho que isso é mais elegante, mas provavelmente não é o mais fácil de entender por outros desenvolvedores.
Nick Bolton
5
Você pode explicar em que circunstâncias a comparação das tuplas da versão não fornecerá o resultado desejado?
SpoonMeiser 19/01/12
tuplas de versão também podem conter valores alfanuméricos.
sorin
8
-1: isso não funciona, conforme explicado na pergunta atualizada. Se você usar qualquer sintaxe de uma versão mais recente do Python, seu arquivo não será compilado e, se não for compilado, não poderá ser executado e verificar a versão!
9789 Scott Sciffiths
Em qual versão / plataforma isso falha?
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
fonte
7
Esteja ciente de que no Python 2.6 e abaixo, nãosys.version_info é uma tupla nomeada. Você precisará usar o número da versão principal e o menor. sys.version_info[0]sys.version_info[1]
Coredumperror
9

Resposta de Nykakin no AskUbuntu :

Você também pode verificar a versão do Python a partir do próprio código usando platform módulo da biblioteca padrão.

Existem duas funções:

  • platform.python_version() (retorna string).
  • platform.python_version_tuple() (retorna tupla).

O código Python

Crie um arquivo, por exemplo version.py:)

Método fácil de verificar a versão:

import platform

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

Você também pode usar o evalmétodo:

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

Execute o arquivo Python em uma linha de comando:

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

A saída do Python com CGI por meio de um servidor WAMP no Windows 10:

Screenshot 2016-11-16 14.39.01 por Suriyaa Kudo


Recursos úteis

Suriyaa
fonte
7

Os conjuntos se tornaram parte da linguagem principal do Python 2.4, a fim de permanecer compatível com versões anteriores. Eu fiz isso naquela época, o que também funcionará para você:

if sys.version_info < (2, 4):
    from sets import Set as set
André
fonte
3
melhor verificar o recurso em vez da versão, não? try: set except NameError: from sets import Set as set
orip 6/09/11
@orip: Por quê? Se você sabe em qual versão um recurso foi introduzido, como os conjuntos aqui, basta usar o código acima. Nada de errado com isso.
André
7

Embora a pergunta seja: como obtenho controle cedo o suficiente para emitir uma mensagem de erro e sair ?

A pergunta que respondo é: Como obtenho controle cedo o suficiente para emitir uma mensagem de erro antes de iniciar o aplicativo ?

Posso responder de maneira muito diferente das outras postagens. Parece que as respostas até agora estão tentando resolver sua pergunta no Python.

Eu digo, faça a verificação da versão antes de lançar o Python. Vejo que seu caminho é Linux ou unix. No entanto, só posso oferecer um script do Windows. Eu imagem adaptá-lo à sintaxe de script linux não seria muito difícil.

Aqui está o script do DOS com a versão 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

Isso não executa nenhuma parte do seu aplicativo e, portanto, não gera uma exceção do Python. Ele não cria nenhum arquivo temporário ou adiciona variáveis ​​de ambiente do SO. E isso não encerra seu aplicativo a uma exceção devido a diferentes regras de sintaxe da versão. São três pontos de acesso de segurança menos possíveis.

A FOR /Flinha é a chave.

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

Para várias versões de python, confira o URL: http://www.fpschultze.de/modules/smartfaq/faq.php?faqid=17

E minha versão hack:

[Script MS; Pré-lançamento da verificação da versão do Python do módulo Python] http://pastebin.com/aAuJ91FQ

DevPlayer
fonte
Para os votos negativos, não tenha medo de explicar os motivos.
DevPlayer
Exatamente eu estava procurando. Obrigado! Qual é o %% H?
Clocker
@Clocker python.exe -V retornaria uma string "Python 2.7". O console coloca a string "Python" em %% G e a string "2.7" no os var %% H criado automaticamente (a próxima letra após G). Eco %% H | find "2.7" pipes "2.7" no comando DOS find "2.7", que define o nível de erro como 1 se %% H for encontrado em "2.7". Esse nível de erro, resultando em um 1, usando o comando find do DOS, nos permitirá ramificar para o rótulo de lote do DOS: Python27
DevPlayer
3
import sys
sys.version

estará recebendo resposta como esta

'2.7.6 (padrão, 26 de outubro de 2016, 20:30:19) \ n [GCC 4.8.4]'

aqui 2.7.6 é a versão

Janarthanan Ramu
fonte
2

Como observado acima, erros de sintaxe ocorrem no tempo de compilação, não no tempo de execução. Enquanto Python é uma "linguagem interpretada", o código Python não é realmente diretamente interpretado; é compilado para código de bytes, que é então interpretado. Há uma etapa de compilação que acontece quando um módulo é importado (se ainda não existe uma versão compilada disponível na forma de um arquivo .pyc ou .pyd) e é nesse momento que você recebe o erro, não (exatamente) quando seu código está sendo executado.

Você pode adiar a etapa de compilação e fazê-la acontecer em tempo de execução para uma única linha de código, se você quiser, usando eval, conforme observado acima, mas eu pessoalmente prefiro evitar fazer isso, porque faz com que o Python execute potencialmente compilação desnecessária em tempo de execução, por um lado, e por outro, cria o que para mim parece desordem de código. (Se você quiser, pode gerar código que gera código que gera código - e ter um tempo absolutamente fabuloso modificando e depurando isso dentro de seis meses.) Portanto, o que eu recomendaria é algo mais ou menos assim:

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

.. o que eu faria mesmo se tivesse apenas uma função que usasse sintaxe mais recente e que fosse muito curta. (De fato, eu tomaria todas as medidas razoáveis ​​para minimizar o número e o tamanho de tais funções. Eu poderia até escrever uma função como ifTrueAElseB (cond, a, b) com essa única linha de sintaxe.)

Outra coisa que vale a pena ressaltar (que estou um pouco surpreso que ninguém apontou ainda) é que, enquanto as versões anteriores do Python não suportavam códigos como

value = 'yes' if MyVarIsTrue else 'no'

..ele suportava código como

value = MyVarIsTrue and 'yes' or 'no'

Essa era a maneira antiga de escrever expressões ternárias. Ainda não tenho o Python 3 instalado, mas até onde eu sei, essa maneira "antiga" ainda funciona até hoje, para que você possa decidir por si próprio se vale a pena usar condicionalmente a nova sintaxe, se necessário para suportar o uso de versões mais antigas do Python.

Shavais
fonte
2
Seriamente? Duplicar seu código apenas para que você possa alterar algumas estruturas menores? Que nojo. Muito eca. E quanto ao a and b or cinvés de b if a else c, não é equivalente; se bfor falso, falhará, produzindo ae não b.
21711 Chris
1
Não estou sugerindo a duplicação de código, estou sugerindo a criação de funções de invólucro para código específico da versão, cujas assinaturas não mudam entre as versões, e colocando essas funções em módulos específicos da versão. Estou falando de funções que talvez tenham de 1 a 5 linhas. É verdade que a e b ou c não são iguais a b se a else c nos casos em que b pode ser avaliado como falso. Portanto, acho que se AThenBElseC (a, b, c) em common_ops_2_4.py tivesse que ter 2 ou 3 linhas em vez de 1. Esse método realmente reduz seu código geral, encapsulando expressões comuns em funções.
Shavais
2

Coloque o seguinte na parte superior do seu arquivo:

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)

Continue com o código Python normal:

import ...
import ...
other code...
jml
fonte
1
prefiro usarsys.version_info < (2, 7)
Antti Haapala
@AnttiHaapala isso é perfeito para mim, você pode explicar por que comparar o sys.version_infotipo com uma tupla funciona?
Jjj 27/12
@jjj sys.version_info costumava ser uma tupla; por exemplo (2, 4, 6, 'final', 0); somente no Python 3 e 2.7 foi alterado para um tipo separado, que é, no entanto, comparável às tuplas.
Antti Haapala
@AnttiHaapala Gosto mais dessa abordagem que a minha ... obrigado!
JML
1

Eu acho que a melhor maneira é testar a funcionalidade em vez de versões. Em alguns casos, isso é trivial, não em outros.

por exemplo:

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

Contanto que você seja suficientemente específico ao usar os blocos try / except, poderá cobrir a maior parte de suas bases.

sykora
fonte
2
Você está certo. Foi o que ele perguntou como fazer - algumas vezes, o teste de recursos na versão Y nem é compilado no bytecode na versão X, portanto, não pode ser feito diretamente.
orip 15/01/09
1

Acabei de encontrar essa pergunta depois de uma pesquisa rápida enquanto tentava resolver o problema sozinho e criei um híbrido com base em algumas das sugestões acima.

Eu gosto da ideia do DevPlayer de usar um script de wrapper, mas a desvantagem é que você acaba mantendo vários wrappers para sistemas operacionais diferentes, então decidi escrever o wrapper em python, mas use a mesma lógica básica "pegue a versão executando o exe" e veio com isso.

Eu acho que deve funcionar para 2,5 em diante. Eu testei no 2.66, 2.7.0 e 3.1.2 no Linux e 2.6.1 no OS X até agora.

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

Sim, eu sei que a linha final de decodificação / tira é horrível, mas eu só queria pegar rapidamente o número da versão. Eu vou refinar isso.

Isso funciona bem o suficiente para mim por enquanto, mas se alguém puder melhorá-lo (ou me diga por que é uma péssima ideia), isso também seria legal.

Steve
fonte
1

Para scripts python independentes , o seguinte truque de documentação do módulo para impor uma versão python (aqui v2.7.x) funciona (testado em * 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
[...]

Isso também deve lidar com o executável python ausente, mas depende do grep. Veja aqui o plano de fundo.

Akhan
fonte
0

Você pode verificar com sys.hexversionou sys.version_info.

sys.hexversionnão é muito amigável ao ser humano porque é um número hexadecimal. sys.version_infoé uma tupla, por isso é mais amigável ao ser humano.

Verifique o Python 3.6 ou mais recente com 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 o Python 3.6 ou mais recente com 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é mais amigável ao ser humano, mas leva mais personagens. Eu recomendaria sys.hexversion, mesmo que seja menos amigável ao ser humano.

Espero que isso tenha ajudado!

Pb2007
fonte
0

Estou expandindo a excelente resposta do akhan, que imprime uma mensagem útil antes que o script Python seja compilado.

Se você deseja garantir que o script esteja sendo executado com o Python 3.6 ou mais recente, adicione estas duas linhas na parte superior do seu 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 # '''

(Observação: a segunda linha começa com quatro aspas simples e termina com três aspas simples. Isso pode parecer estranho, mas não é um erro de digitação.)

A vantagem desta solução é que código como print(f'Hello, {name}!')não causará um SyntaxErrorse uma versão Python anterior a 3.6 for usada. Você verá esta mensagem útil:

This script requires Python 3.6 or newer.

Obviamente, essa solução funciona apenas em shells semelhantes ao Unix, e somente quando o script é chamado diretamente (como ./script.py:) e com os bits de permissão eXecute adequados definidos.

JL
fonte
-2

Que tal agora:

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
fonte
5
-1: isso não funciona, conforme explicado na pergunta atualizada. Se você usar qualquer sintaxe de uma versão mais recente do Python, seu arquivo não será compilado e, se não for compilado, não poderá ser executado e verificar a versão!
9788 Scott Sciffiths
-3

O problema é bastante simples. Você verificou se a versão era menor que 2.4, não menor ou igual a . Portanto, se a versão Python for 2.4, não será menor que 2.4. O que você deveria ter tido era:

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

, não

    if sys.version_info < (2, 4):
Peter Mortensen
fonte
4
leia o parágrafo 3 e a atualização. você não precisa executar esse código porque seu código não será compilado na 2.4 se você estiver usando as novas construções de idioma.
Mark Harrison
O menos que estava bem, faltando apenas a avaliação.
Craig