If your code is not overly dependent on the run-time performance in exception handlers, you might even get away without having a separate branch for Py3. I've managed to keep one version of pyparsing for all of my Py2.x versions, although I've had to stick with a "lowest common denominator" approach, meaning that I have to forego using some constructs like generator expressions, and to your point, context managers. I use dicts in place of sets, and all my generator expressions get wrapped as list comprehensions, so they will still work going back to Python 2.3. I have a block at the top of my code that takes care of a number of 2vs3 issues (contributed by pyparsing user Robert A Clark):
_PY3K = sys.version_info[0] > 2
if _PY3K:
_MAX_INT = sys.maxsize
basestring = str
unichr = chr
unicode = str
_str2dict = set
alphas = string.ascii_lowercase + string.ascii_uppercase
else:
_MAX_INT = sys.maxint
range = xrange
def _str2dict(strg):
return dict( [(c,0) for c in strg] )
alphas = string.lowercase + string.uppercase
The biggest difficulty I've had has been with the incompatible syntax for catching exceptions, that was introduced in Py3, changing from
except exceptiontype,varname:
to
except exceptionType as varname:
Of course, if you don't really need the exception variable, you can just write:
except exceptionType:
and this will work on Py2 or Py3. But if you need to access the exception, you can still come up with a cross-version compatible syntax as:
except exceptionType:
exceptionvar = sys.exc_info()[1]
This has a minor run-time penalty, which makes this unusable in some places in pyparsing, so I still have to maintain separate Py2 and Py3 versions. For source merging, I use the utility WinMerge, which I find very good for keeping directories of source code in synch.
So even though I keep two versions of my code, some of these unification techniques help me to keep the differences down to the absolute incompatible minimum.