If your code does not depend too much on runtime performance in exception handlers, you can even leave without a separate branch for Py3. I managed to save one version of pyparsing for all my versions of Py2.x, although I had to adhere to the "lowest common denominator" approach, which means that I should refuse to use some constructs, such as generator expressions, and your point, context managers. I use dicts instead of sets, and all my generator expressions get wrapped like lists, so they will still work, returning to Python 2.3. I have a block at the top of my code that takes care of several 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 ran into was the incompatible syntax for throwing exceptions, which was introduced in Py3, changing from
except exceptiontype,varname:
to
except exceptionType as varname:
Of course, if you really don't need an exception variable, you can simply write:
except exceptionType:
and this will work on Py2 or Py3. But if you need to access the exception, you can find cross-compatible syntax like:
except exceptionType: exceptionvar = sys.exc_info()[1]
This is a minor punishment at runtime, which makes it unusable in some places in pyparsing, so I still have to maintain separate versions of Py2 and Py3. To merge the source, I use the WinMerge utility, which I find very useful for synchronizing source code directories.
So, although I keep two versions of my code, some of these unification methods help me keep the differences to an absolute incompatible minimum.