71

How do you gracefully handle failed future feature imports? If a user is running using Python 2.5 and the first statement in my module is:

from __future__ import print_function

Compiling this module for Python 2.5 will fail with a:

  File "__init__.py", line 1
    from __future__ import print_function
SyntaxError: future feature print_function is not defined

I'd like to inform the user that they need to rerun the program with Python >= 2.6 and maybe provide some instructions on how to do so. However, to quote PEP 236:

The only lines that can appear before a future_statement are:

  • The module docstring (if any).
  • Comments.
  • Blank lines.
  • Other future_statements.

So I can't do something like:

import __future__

if hasattr(__future__, 'print_function'):
    from __future__ import print_function
else:
    raise ImportError('Python >= 2.6 is required')

Because it yields:

  File "__init__.py", line 4
    from __future__ import print_function
SyntaxError: from __future__ imports must occur at the beginning of the file

This snippet from the PEP seems to give hope of doing it inline:

Q: I want to wrap future_statements in try/except blocks, so I can use different code depending on which version of Python I'm running. Why can't I?

A: Sorry! try/except is a runtime feature; future_statements are primarily compile-time gimmicks, and your try/except happens long after the compiler is done. That is, by the time you do try/except, the semantics in effect for the module are already a done deal. Since the try/except wouldn't accomplish what it looks like it should accomplish, it's simply not allowed. We also want to keep these special statements very easy to find and to recognize.

Note that you can import __future__ directly, and use the information in it, along with sys.version_info, to figure out where the release you're running under stands in relation to a given feature's status.

Ideas?

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
cdleary
  • 69,512
  • 53
  • 163
  • 191
  • 3
    I just bumped into this problem, and I cannot stop asking myself: if "future_statements are primarily compile-time gimmicks" - then why doesn't Python introduce `#ifdef` as a compile-time gimmick, so I can handle all this `__future__` stuff gracefully in a single file? AAAARRGGGHHH..... – sdaau Apr 26 '13 at 17:05
  • You can use simple `if` at run-time. Also for `import`. – rundekugel Jan 20 '16 at 13:54

3 Answers3

60

"I'd like to inform the user that they need to rerun the program with Python >= 2.6 and maybe provide some instructions on how to do so."

Isn't that what a README file is for?

Here's your alternative. A "wrapper": a little blob of Python that checks the environment before running your target aop.

File: appwrapper.py

import sys
major, minor, micro, releaselevel, serial = sys.version_info
if (major,minor) <= (2,5):
    # provide advice on getting version 2.6 or higher.
    sys.exit(2)
import app
app.main()

What "direct import" means. You can examine the contents of __future__. You're still bound by the fact the a from __future__ import print_function is information to the compiler, but you can poke around before importing the module that does the real work.

import __future__, sys
if hasattr(__future__, 'print_function'): 
    # Could also check sys.version_info >= __future__. print_function.optional
    import app
    app.main()
else:
    print "instructions for upgrading"
Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • Perhaps test "if major < 2 or major == 2 and minor <= 5", else this would not fail on a hypothetical version 1.7. – Greg Hewgill Dec 23 '08 at 03:51
  • 20
    Or even better, "if (major, minor) <= (2, 5)" – Greg Hewgill Dec 23 '08 at 03:56
  • 10
    Why not "if sys.version_info[:2] <= (2, 5)" More to the point, I think, and I dislike creating variables that are used just once (or not at all for everything after "minor" in this example) – Jürgen A. Erhard Mar 04 '13 at 13:30
  • 1
    @JürgenA.Erhard I think generally intermediate variables add clarity about what you're doing, and since this will only be done once it doesn't really hurt. – porglezomp Oct 12 '14 at 01:32
  • 3
    52 upvotes and no one pointed out that `'print_function' in __future__` does not actually work... should be `hasattr(__future__, 'print_function')` – Antti Haapala -- Слава Україні Feb 06 '15 at 19:24
  • @porglezomp, I agree, that explicit is better than implicit. In a case like this I think it is more pythonic to do `major, minor = sys.version_info[:2]` though I often see underscore used for throw away variables `major, minor, _, _, _ = sys.version_info` – Bruno Bronosky Apr 06 '15 at 15:58
48

A rather hacky but simple method I've used before is to exploit the fact that byte literals were introduced in Python 2.6 and use something like this near the start of the file:

b'This module needs Python 2.6 or later. Please do xxx.'

This is harmless in Python 2.6 or later, but a SyntaxError in any earlier versions. Anyone trying to compile your file will still get an error, but they also get whatever message you want to give.

You might think that as you will have to have this line after your from __future__ import print_function then it will be the import that generates the SyntaxError and you won't get to see the useful error message, but strangely enough the later error takes precedence. I suspect that as the error from the import isn't really a syntax error in itself it isn't raised on the first compilation pass, and so real syntax errors get raised first (but I'm guessing).

This might not meet you criteria for being 'graceful', and it is very Python 2.6 specific, but it is quick and easy to do.

Scott Griffiths
  • 21,438
  • 8
  • 55
  • 85
  • 7
    Elegant! I've used this without an assignment just under my shebang line: `b'This script requires Python 2.6, this line is a SyntaxError in earlier versions'` which is a pretty clear message to the user reading an exception trace and to the programmer reading the source. – RobM Aug 13 '10 at 12:20
  • @RobM: Glad you like it, and you're right that the assignment is not needed - I'll remove it. – Scott Griffiths Aug 13 '10 at 12:54
  • 1
    I think the reason this works is because it interprets the first string literal as a docstring for the module, which is the only thing permitted before a "from future" statement. I was using this clever technique and then discovered it didn't work any more when I added a proper docstring, because then it became a second string literal before "from future". My solution was to put the notice about Python 2.6 in friendly language as the last line of the docstring, with """ trailing on the same line; this makes it still show up in the SyntaxError, and as harmless info in the docstring. – Ivan X May 18 '14 at 11:52
41

Just put a comment on the same line with the "from __future__ import ...", like this:

from __future__ import print_function, division  # We require Python 2.6 or later

Since Python displays the line containing the error, if you try to run the module with Python 2.5 you'll get a nice, descriptive error:

    from __future__ import print_function, division  # We require Python 2.6 or later
SyntaxError: future feature print_function is not defined
Dave Burton
  • 2,960
  • 29
  • 19