251

If I have a Python script that requires at least a particular version of Python, what is the correct way to fail gracefully when an earlier version of Python is used to launch the script?

How do I get control early enough to issue an error message and exit?

For example, I have a program that uses the ternery operator (new in 2.5) and "with" blocks (new in 2.6). I wrote a simple little interpreter-version checker routine which is the first thing the script would call ... except it doesn't get that far. Instead, the script fails during python compilation, before my routines are even called. Thus the user of the script sees some very obscure synax error tracebacks - which pretty much require an expert to deduce that it is simply the case of running the wrong version of Python.

I know how to check the version of Python. The issue is that some syntax is illegal in older versions of Python. Consider this 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

When run under 2.4, I want this result

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

and not this result:

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

(Channeling for a coworker.)

Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
Mark Harrison
  • 297,451
  • 125
  • 333
  • 465
  • 3
    "check the version of python. The issue is that some syntax is illegal in older versions of python." I don't get how this is a problem. If you can check the version, you can avoid the syntax error. How does version checking not apply to syntax? Can you clarify your question? – S.Lott Jan 15 '09 at 11:55
  • You know that you can get 'with' blocks in Python 2.5 by using `from __future__ import with_statement`. – asmeurer Jan 04 '11 at 03:09
  • 1
    @S.Lott Syntax errors mean the interpreter does not understand the structure of the file. They raise exceptions regardless of whether they are inside a conditional. – Brendan Jun 29 '11 at 21:57
  • @Brendan: While true, one can (1) determine the version of Python and (2) determine what syntax is allowed. Am I wrong about that sequence of events? – S.Lott Jun 29 '11 at 22:14
  • 4
    @S.Lott No you are not wrong, it just that the difficulty is in including the code somewhere where it will also not be _read_ (parsed) as well as not executed - this isn't immediately apparent as the answers show. – Brendan Jun 30 '11 at 00:04
  • @Brendan: Difficulty? The question seems trivial. Check `sys.version` and write a message has the exact behavior required. If the version is proper, either use `execfile` or spawn a separate Python subprocess. I still don't see how this is a problem, or even interesting. – S.Lott Jun 30 '11 at 01:17
  • 7
    S.Lott, you can't execute your test in the old version of python because it doesn't compile. Instead, you get a generic syntax error. Try the example code with a 2.4 interpreter and you'll see that you can't get to the version test. – Mark Harrison Jun 30 '11 at 06:07
  • 1
    @Mark Harrison: What? A three line, `import sys; if sys.version_info > ( 2, 4, 0, None, None ): execfile('the real script')` seems like it would work in just about every version of Python ever shipped. What's going to get a syntax error in that three-line wrapper? I'm totally lost. – S.Lott Jun 30 '11 at 09:45
  • 7
    @S.Lott Well it depends on what you consider trivial - personally I wouldn't consider creating separate files for different versions of Python or spawning extra processes trivial. I would say this question is valuable, especially when you consider Python is full of neat and often surprising tricks - I came here from Google wanting to know if there was a neat answer – Brendan Jun 30 '11 at 11:28
  • @Brendan: Two files **is** trivial. That's why I can't understand the question. – S.Lott Jul 01 '11 at 11:41
  • @S.Lott, everything works fine using the solution in orip's answer. – Mark Harrison Jul 01 '11 at 19:08
  • @Mark Harrison: Does that mean that the question is simply "How to check the version of Python in a script?" If so, it seems trivial. Further, it seems like @orip solution is too complex, since the version number would provide this information more clearly prior to doing the import. I guess I'm unable to understand why a trivial check against the version number can't be done, and a somewhat more complex `try` block is better. – S.Lott Jul 01 '11 at 19:41
  • 3
    @S.Lott, the first conditional in my program is checking the version number, so the question is not how to check the version number. Type in the example program, run it with python 2.4, then fix the program so you don't get SyntaxError. Post your answer and I'll follow up there if there's any more confusion. – Mark Harrison Jul 05 '11 at 17:28
  • @Mark Harrison: The example program is ill-advised and cannot ever work. A simple change to write a wrapper leads to a program which can work. Since the example can't possibly work, and the wrapper is something you've already proposed, I'm unable to get the question. – S.Lott Jul 05 '11 at 17:31
  • 7
    I think we've reached an end of this discussion. I asked a question about something I didn't know how to do, and got an answer telling me how to do it. I'm not proposing anything, I just accepted orip's answer which is working great for me (actually the coworker for whom I'm channeling). Viva Le Stack Overflow! – Mark Harrison Jul 05 '11 at 22:59

19 Answers19

118

You can test using eval:

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

Also, with is available in Python 2.5, just add from __future__ import with_statement.

EDIT: to get control early enough, you could split it into different .py files and check compatibility in the main file before importing (e.g. in __init__.py in a 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 *
martineau
  • 119,623
  • 25
  • 170
  • 301
orip
  • 73,323
  • 21
  • 116
  • 148
  • 11
    this is a fantastic answer. the main issue from the question that needed addressing is that a program must be syntactically correct for that version of python to even begin executing, so using new syntax precludes a program from starting on older versions of the interpreter. eval works around that – Autoplectic Jan 15 '09 at 08:59
  • 7
    If the package is being installed by setuptools, byte-compiling the source files will fail then. Also, all the contortions to produce a run-time error message seem a little pointless -- why not just document the requirements and leave it at that? – John Machin Sep 14 '09 at 23:50
  • 2
    Note that if attempting to check an expression, rather than a simple statement, you need to use `exec` instead of `eval`. I had this come up while trying to write a function that would print to stderr in both py2k and py3k. – Xiong Chiamiov Nov 19 '10 at 09:52
  • 2
    I think a cleaner version of this solution would be to put your "checks" in a separate module and import that (wrap the `import` station in try/except). Note that you might need to check for other things than `SyntaxError` as well (eg. builtin functions or additions to the standard library) – Steven Sep 13 '11 at 10:15
109

Have a wrapper around your program that does the following.

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

You can also consider using sys.version(), if you plan to encounter people who are using pre-2.0 Python interpreters, but then you have some regular expressions to do.

And there might be more elegant ways to do this.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ed Carrel
  • 4,154
  • 1
  • 25
  • 17
  • 7
    FYI, "cur_version >= req_version" should work as the conditional. – orip Aug 05 '09 at 14:55
  • 5
    `sys.version_info` is not a function. – nh2 Aug 06 '11 at 22:23
  • 4
    Putting code inside the successfull conditional like that is pretty bad practice as it is an unecessary indentation and addition of logic. Just do a: if sys.version_info[:2] < req_version: print "old"; sys.exit() - and otherwise continue as usual. – timss Apr 10 '13 at 02:31
  • 2
    It's like Tim Peters says in "The Zen of Python": "Flat is better than nested." (You can see this by typing "import this" in python) – Christopher Shroba Oct 27 '15 at 22:55
  • 1
    @ChristopherShroba Thank you for `import this`. A lovely diversion. – Samuel Harmer Dec 22 '17 at 08:52
34

Try

import platform
platform.python_version()

Should give you a string like "2.3.1". If this is not exactly waht you want there is a rich set of data available through the "platform" build-in. What you want should be in there somewhere.

James Anderson
  • 27,109
  • 7
  • 50
  • 78
  • 5
    -1: This doesn't work, as explained in the updated question. If you use any syntax from a newer version of Python then your file won't compile, and if it doesn't compile it can't run and check the version! – Scott Griffiths Apr 08 '12 at 14:32
  • 1
    @ScottGriffiths Run `print(platform.python_version())` instead of `platform.python_version()`! – Suriyaa Nov 16 '16 at 13:25
  • @ScottGriffiths Also checkout my answer: http://stackoverflow.com/a/40633458/5157221. – Suriyaa Nov 16 '16 at 13:32
22

Probably the best way to do do this version comparison is to use the sys.hexversion. This is important because comparing version tuples will not give you the desired result in all python versions.

import sys
if sys.hexversion < 0x02060000:
    print "yep!"
else:
    print "oops!"
sorin
  • 161,544
  • 178
  • 535
  • 806
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")
martineau
  • 119,623
  • 25
  • 170
  • 301
Erick Wendel
  • 402
  • 6
  • 14
  • 7
    Be aware that in Python 2.6 and below, `sys.version_info` is **not** a named tuple. You'll need to use `sys.version_info[0]` for the major version number, and `sys.version_info[1]` for the minor. – coredumperror Dec 11 '15 at 00:43
9

Answer from Nykakin at AskUbuntu:

You can also check Python version from code itself using platform module from standard library.

There are two functions:

  • platform.python_version() (returns string).
  • platform.python_version_tuple() (returns tuple).

The Python code

Create a file for example: version.py)

Easy method to check version:

import platform

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

You can also use the eval method:

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

Run the Python file in a command line:

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

The output of Python with CGI via a WAMP Server on Windows 10:

Screenshot 2016-11-16 14.39.01 by Suriyaa Kudo


Helpful resources

jww
  • 97,681
  • 90
  • 411
  • 885
Suriyaa
  • 2,222
  • 2
  • 25
  • 44
8

Sets became part of the core language in Python 2.4, in order to stay backwards compatible. I did this back then, which will work for you as well:

if sys.version_info < (2, 4):
    from sets import Set as set
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
André
  • 12,971
  • 3
  • 33
  • 45
  • 3
    better to check for the feature instead of the version, no? `try: set except NameError: from sets import Set as set` – orip Sep 06 '11 at 18:48
  • @orip: Why? If you know in which version a feature has been introduced, like sets here, just use above code. Nothing wrong with that. – André Sep 07 '11 at 04:41
7

Although the question is: How do I get control early enough to issue an error message and exit?

The question that I answer is: How do I get control early enough to issue an error message before starting the app?

I can answer it a lot differently then the other posts. Seems answers so far are trying to solve your question from within Python.

I say, do version checking before launching Python. I see your path is Linux or unix. However I can only offer you a Windows script. I image adapting it to linux scripting syntax wouldn't be too hard.

Here is the DOS script with 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

This does not run any part of your application and therefore will not raise a Python Exception. It does not create any temp file or add any OS environment variables. And it doesn't end your app to an exception due to different version syntax rules. That's three less possible security points of access.

The FOR /F line is the key.

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

For multiple python version check check out url: http://www.fpschultze.de/modules/smartfaq/faq.php?faqid=17

And my hack version:

[MS script; Python version check prelaunch of Python module] http://pastebin.com/aAuJ91FQ

Jean-Francois T.
  • 11,549
  • 7
  • 68
  • 107
DevPlayer
  • 5,393
  • 1
  • 25
  • 20
  • For those down votes please don't be afraid to explain reasons why. – DevPlayer Jun 23 '12 at 19:17
  • Exactly was I looking for. Thanks! Whats the %%H? – Clocker Oct 18 '16 at 19:38
  • @Clocker python.exe -V would return a "Python 2.7" string. The console puts "Python" string into %%G and the "2.7" string in the automatically created os var %%H (the next letter after G). Echo %%H | find "2.7" pipes "2.7" into DOS command find "2.7" which sets the error level to 1 if %%H is found in "2.7". That error level, resulting in a 1, from using DOS find command, will allow us to branch to DOS batch label :Python27 – DevPlayer Oct 22 '16 at 02:01
3
import sys
sys.version

will be getting answer like this

'2.7.6 (default, Oct 26 2016, 20:30:19) \n[GCC 4.8.4]'

here 2.7.6 is version

Janarthanan Ramu
  • 1,331
  • 16
  • 17
2

Put the following at the very top of your 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)

Then continue on with the normal Python code:

import ...
import ...
other code...
jml
  • 53
  • 1
  • 7
2

As noted above, syntax errors occur at compile time, not at run time. While Python is an "interpreted language", Python code is not actually directly interpreted; it's compiled to byte code, which is then interpreted. There is a compile step that happens when a module is imported (if there is no already-compiled version available in the form of a .pyc or .pyd file) and that's when you're getting your error, not (quite exactly) when your code is running.

You can put off the compile step and make it happen at run time for a single line of code, if you want to, by using eval, as noted above, but I personally prefer to avoid doing that, because it causes Python to perform potentially unnecessary run-time compilation, for one thing, and for another, it creates what to me feels like code clutter. (If you want, you can generate code that generates code that generates code - and have an absolutely fabulous time modifying and debugging that in 6 months from now.) So what I would recommend instead is something more like this:

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

.. which I would do even if I only had one function that used newer syntax and it was very short. (In fact I would take every reasonable measure to minimize the number and size of such functions. I might even write a function like ifTrueAElseB(cond, a, b) with that single line of syntax in it.)

Another thing that might be worth pointing out (that I'm a little amazed no one has pointed out yet) is that while earlier versions of Python did not support code like

value = 'yes' if MyVarIsTrue else 'no'

..it did support code like

value = MyVarIsTrue and 'yes' or 'no'

That was the old way of writing ternary expressions. I don't have Python 3 installed yet, but as far as I know, that "old" way still works to this day, so you can decide for yourself whether or not it's worth it to conditionally use the new syntax, if you need to support the use of older versions of Python.

Shavais
  • 2,476
  • 1
  • 27
  • 25
  • 2
    Seriously? Duplicate your code just so that you can change some minor structures? Yuck. Very yuck. And as for `a and b or c` instead of `b if a else c`, it's not equivalent; if `b` is falsy it will fail, producing `a` rather than `b`. – Chris Morgan Sep 13 '11 at 02:50
  • 1
    I'm not suggesting duplicating code, I'm suggesting creating wrapper functions for version-specific code, whose signatures don't change across versions, and putting those functions in version-specific modules. I'm talking about functions that are maybe 1 to 5 lines long. It's true that a and b or c is not the same as b if a else c in cases where b may evaluate to false. So I guess ifAThenBElseC(a,b,c) in common_ops_2_4.py, would have to be 2 or 3 lines long instead of 1. This method actually reduces your over all code by encapsulating common idioms into functions. – Shavais Apr 12 '12 at 20:31
1

I just found this question after a quick search whilst trying to solve the problem myself and I've come up with a hybrid based on a few of the suggestions above.

I like DevPlayer's idea of using a wrapper script, but the downside is that you end up maintaining multiple wrappers for different OSes, so I decided to write the wrapper in python, but use the same basic "grab the version by running the exe" logic and came up with this.

I think it should work for 2.5 and onwards. I've tested it on 2.66, 2.7.0 and 3.1.2 on Linux and 2.6.1 on OS X so far.

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

Yes, I know the final decode/strip line is horrible, but I just wanted to quickly grab the version number. I'm going to refine that.

This works well enough for me for now, but if anyone can improve it (or tell me why it's a terrible idea) that'd be cool too.

Steve
  • 154
  • 1
  • 11
1

I think the best way is to test for functionality rather than versions. In some cases, this is trivial, not so in others.

eg:

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

As long as you're specific in enough in using the try/except blocks, you can cover most of your bases.

sykora
  • 96,888
  • 11
  • 64
  • 71
  • 2
    You're right. That's what he asked how to do - sometimes testing for features in version Y doesn't even compile to bytecode in version X, so it can't be done directly. – orip Jan 15 '09 at 08:55
1

For standalone python scripts, the following module docstring trick to enforce a python version (here v2.7.x) works (tested on *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
[...]

This should handle missing python executable as well but has a dependency on grep. See here for background.

akhan
  • 2,952
  • 2
  • 22
  • 13
1

I'm expanding on akhan's excellent answer, which prints a helpful message before the Python script is even compiled.

If you want to ensure that the script is being run with Python 3.6 or newer, add these two lines to the top of your Python script:

#!/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 # '''

(Note: The second line starts with four single-quotes and ends with three single-quotes. This may look strange, but it is not a typo.)

The advantage of this solution is that code like print(f'Hello, {name}!') won't cause a SyntaxError if a Python version older than 3.6 is used. You'll see this helpful message instead:

This script requires Python 3.6 or newer.

Of course, this solution only works on Unix-like shells, and only when the script is invoked directly (such as: ./script.py), and with the proper eXecute permission bits set.

J-L
  • 1,786
  • 10
  • 13
1

A simple way to print a useful message if the user is running your Python 3 script with a Python 2 exe:

Put this as the first line of code:

f' Error: This script requires Python 3.6 or later.'

It does nothing in Python 3.6+ (when f-strings were introduced) but fails to compile in Python 2, and prints on the console:

  File "test.py", line 34
    f' Error: Error: This script requires Python 3.6 or later.'
                                                                         ^
SyntaxError: invalid syntax
tim_hutton
  • 838
  • 9
  • 9
0

You can check with sys.hexversion or sys.version_info.

sys.hexversion isn't very human-friendly because it's a hexadecimal number. sys.version_info is a tuple, so it's more human-friendly.

Check for Python 3.6 or newer with 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()

Check for Python 3.6 or newer with 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 is more human-friendly, but takes more characters. I would reccomend sys.hexversion, even though it is less human-friendly.

I hope this helped you!

Pb2007
  • 17
  • 6
-2

How about this:

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
  • 17
  • 6
  • 5
    -1: This doesn't work, as explained in the updated question. If you use any syntax from a newer version of Python then your file won't compile, and if it doesn't compile it can't run and check the version! – Scott Griffiths Apr 08 '12 at 14:35
-3

The problem is quite simple. You checked if the version was less than 2.4, not less than or equal to. So if the Python version is 2.4, it's not less than 2.4. What you should have had was:

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

, not

    if sys.version_info < (2, 4):
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 4
    read paragraph 3, and the update. you don't to the point of executing this code because your code won't compile on 2.4 if you are using the new language constructs. – Mark Harrison Apr 09 '10 at 22:15
  • The less than was fine, just missing the eval. – Craig Mar 01 '14 at 19:47