[Tutor] 2.x and 3.x in one code base [was Re: PYTHONHASHSEED, -R]
Steven D'Aprano
steve at pearwood.info
Mon Jul 29 19:51:24 CEST 2013
On 29/07/13 19:29, Albert-Jan Roskam wrote:
> The following question is almost new-thread-worthy, but: if I would like to make my app work for 2.x and 3.x, what is the best approach:
> (a) use "if sys.version_info.major...." throughout the code
> (b) use 2to3, hope for the best and fix manually whatever can't be fixed
It depends on many factors. How many things that you need have changed? Do you need new features that exist in Python 3 but not 2? But generally I prefer a single code base.
In most of my code, making it run in both is relatively simple. I usually support everything from 2.4 through 3.3 (and believe me, I can't wait to drop support for 2.4 -- on the other hand, I'm really grateful I don't need to support 2.3!), so I basically write code that mostly relies on 2.4 features. Then I add any extra, or changed, features in, at the top of the module.
I prefer to use the feature if Python already has it, and only fall back on my own back-ported version if I have to, e.g.:
try:
from functools import wraps
except ImportError:
# Probably 2.4, so use my own version.
from backports import wraps
So I have a module, "backports.py", collecting various re-implemented or backported functions.
Some things are trivial enough that I just put them at the top of my module:
if type(map(chr, '')) is list:
# Python 2.x
from itertools import imap as map
try:
enumerate([], 2) # Starting value for enumerate.
except TypeError:
# Python 2.4 or 2.5
_enumerate = enumerate
def enumerate(iterable, start=0):
for i, obj in _enumerate(iterable):
yield i+start
try:
strings = (str, unicode)
except NameError:
# No unicode, means Python 3.
strings = (str,)
Notice that I always test for the feature, not for the version number. This is defensive programming -- maybe some day my code will be running on an almost-but-not-quite compatible version of Python, and version checks will give the wrong idea. E.g. "AmazingPython 1.1" might have the same functionality of Python 3.3, but I'd never know it if I just check the version number.
About the most complex such test I have is this one, to pre-define a couple of named constants for special numeric values:
# Include special values. Prior to Python 2.6, this was messy, platform-
# dependent, and not guaranteed to work.
try:
INF = float('inf')
NAN = float('nan')
except ValueError:
# Possibly Windows prior to Python 2.6.
try:
INF = float('1.#INF')
NAN = float('1.#IND') # Indeterminate.
except ValueError:
# Desperate times require desperate measures...
try:
INF = 1e3000 # Hopefully, this should overflow to INF.
NAN = INF-INF # And this hopefully will give a NaN.
except (ValueError, OverflowError):
# Just give up.
print('*** warning: unable to define INF and/or NAN floats ***')
Very occasionally, I end up with *three* modules for some piece of functionality that uses completely different syntax, or is otherwise too hard to write as a single cross-version implementation. A trivial example:
# === printer2.py module ===
import sys
def do_print(obj, file=sys.stdout):
print >>>file, obj
# === printer3.py module ===
def do_print(obj, file=sys.stdout):
print(obj, file=file)
# === printer.py module ===
try:
from printer2 import do_print
except SyntaxError:
from printer3 import do_print
Then in the rest of my project, I just:
import printer
printer.do_print("something")
--
Steven
More information about the Tutor
mailing list