Supporting both 2.x and 3.x in one code base [was Re: Python #ifdef]

Steven D'Aprano steve+comp.lang.python at pearwood.info
Tue May 28 20:57:10 EDT 2013


On Tue, 28 May 2013 18:25:59 -0400, Joel Goldstick wrote:

> I wonder
> if it is a good idea in general to try to write code like this.  --
> combined 2.x/3.x codebase can be a bear to maintain.

Not so much a bear as a tiny little kitten.


> I wouldn't do it
> unless there was some imposing reason that I must.  Its not just print()
> -- that isn't bad, but changes in module names (urllib), arithmetic, and
> unicode especially make this idea in general, very tricky.  Pity the
> next developer who needs to try to maintain it.

It's not that hard really. Well, like all things, it depends on the 
circumstances. If you're reliant on external modules, you *may* have a 
bad time if those modules are 2.x only or 3.x only, but the standard 
library and built-ins don't provide too much of a challenge.

There's no doubt that supporting 2.x and 3.x in one code base is more 
difficult that just supporting one or the other, but the difficulty is 
much less than often supposed. Python 2.7, and to a lesser extent, 2.6, 
are designed to be as easy to port to 3.x as possible, which has the 
happy side-effect that they are also relatively easy to write code for 
them that will also run under 3.x.

Many of the differences can be eliminated with a few __future__ imports:

from __future__ import division, print_function


Differences in behaviour of the built-ins can be eliminated:

from future_builtins import *


Built-ins such as reduce and cmp that have been moved, or eliminated, can 
easily be restored:

if sys.version >= '3':
    from functools import reduce


Or if you prefer a "Better To Ask Forgiveness Than Permission" approach:

try:
    reduce
except NameError:
    from functools import reduce


Name changes of modules are easy to deal with:

try:
    import configparser
except ImportError:
    import ConfigParser as configparser


The most difficult difference is the difference between strings in 2.x 
and 3.x, but if you drop support for Python 3.1 and 3.2, you can write 
code that works in both 2.7 and 3.3 by using the u"" syntax. Or just use 
ASCII literals, which work perfectly in both.

There are really only a very few things that cannot be shared between 2.x 
and 3.x: syntactical features that are only supported by 3.x. So if 
you're planning on writing code that runs in both 2.x and 3.x, you need 
to eschew the 3-only features like keyword-only function arguments and 
function annotations.

But that's no different than writing code to support *any* two versions 
that don't have identical syntax. E.g. 2.4 and 2.5: 2.5 supports ternary 
if, `a if condition else b`, while 2.4 does not, so if you need to 
support both, you can't use ternary if. Nearly all the code I write is 
for 2.4 or better, and I can assure you that the hardest part is 
supporting 2.4. Adding 3.x doesn't make it much harder.

(My hat goes off to those supporting 2.3 through 3.3 in one code base. 
That is, frankly, astonishing.)

Nobody *likes* to have to support really old versions missing the cool 
syntax that you want to use, but nobody says that you should even try. 
3.x doesn't change that.



-- 
Steven



More information about the Python-list mailing list