Best way to write Python 2 and 3 compatible code using nothing but the standard library
The simple, "Don't Make Me Think!" solution I use is to start simple scripts with:
#!/usr/bin/env python
# just make sure that Python 3 code runs fine with 2.7+ too ~98% of the time :)
from __future__ import (division, print_function, absolute_import,
unicode_literals)
from builtins import int
try:
from future_builtins import ascii, filter, hex, map, oct, zip
except:
pass
import sys
if sys.version_info.major > 2:
xrange = range
(Extra tip to stop most pep8 linters for unnecessarily yelling at you for this: move last 3 lines inside and at the top of the try
block above)
But the only case I use this is basically "shell scripts that were too large and hairy so I quickly rewrote them to Python and I just want them to run under both Python 2 and 3 with 0 dependencies". Please do NOT use this in real application/library code until you know exactly what are the consequences of all the lines above, and if they are enough for your use case.
Also, the "solution" in this case for .iteritems
is "just don't use it", ignore memory use optimizations and just always use .items
instead - if this matters, it means you're not writing a "0 dependencies simple script" anymore, so just pick Python 3 and code for it (or Python 2 if you need to pretend we're in 2008).
Also, check these resources to get a proper understanding:
- http://python-future.org/compatible_idioms.html
- http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/
- https://wiki.python.org/moin/PortingToPy3k/BilingualQuickRef
(NOTE: I'm answering this already answered question mainly because the accepted answers roughly translates to "you are stupid and this is dumb" and I find this very rude for an SO answer: no matter how dumb the question, and how "wrong" to actually answer it, a question deserves a real answer._
import sys
if sys.version_info.major > 2:
xrange = range
But as Wim implies, this is basically rewriting six yourself.
And as you can see, six
does a lot more that handling range
. Just e.g. look at the _moved_attributes
list in the six source code.
And while Python comes with "batteries included", its standard library is not and cannot be all-encompassing. Nor is it devoid of flaws.
Sometimes there are better batteries out there, and it would be a waste not to use them. Just compare urllib2
with requests
. The latter is much nicer to work with.
I would recommend writing for py2 or py3 in your projects's modules, but not mix them together and not include any sort of 2/3 checks at all. Your program's logic shouldn't have to care about its version of python, except maybe for avoiding functions on builtin objects that conflict.
Instead, import * from your own compatiblity layer that fixes the differences between your framework and use shadowing to make it transparent to your actual project's module.
For instance, in the compatibility module, you can write Roland Smith's substition for range/xrange, and in your other modules you add "from compatibility import *". Doing this, every module can use "xrange" and the compatibility layer will manage the 2/3 differences.
Unfortunately it won't solve existing objects functions such as dict.iteritems; typically you would monkey-patch the dict methods, but it is not possible on builtin types (see https://stackoverflow.com/a/192857/1741414). I can imagine some workarounds:
- Function wrappers (essentially sobolevn's answer)
- Don't use .items() functions at all; use simple loops on keys and then access the dictionary with those keys:
for key in my_dict: value = my_dict[key] # rest of code goes here