I had this on the someday section of my todo list, but was prompted into action by a pull request. In the end, getting Python 2 and 3 compatible code was easier than I thought.
I started with the pull request code and got it so the tests were mostly passing in 3 and then worked to make it compatible with 2 again. I guess this is all old news now for most (then again, maybe not as Python 2 shows no signs of dying), but if you’ve only got something small I wouldn’t bother with things like 2to3 (which produced worse results than the pull request) and instead just port things manually. Then make everything work for both by simply using sys.version
conditionals instead of using compatibility libraries like six or future.
Some examples:
Import conditionally and use Python 2 names
So I could continue to use urllib2
throughout the code, for Python 3, urllib.request
gets imported as urllib2
and urllib.parse
as urllib
:
import sys
if sys.version_info > (3, 0):
import urllib.request as urllib2
import urllib.error
from urllib.error import HTTPError
import urllib.parse as urllib
else:
import urllib2
from urllib2 import HTTPError
import urllib
Dummy Monkey Patching / Subclassing
Python 2 still requires (will always) monkey patching to add HTTP DELETE to urllib2. But Python 3 doesn’t. How to do for one, but not the other? Simply pass through. This might be obvious to some, but, for me, I thought this was a great little trick.
class Request(urllib2.Request):
if sys.version_info < (3, 0):
#Code here
return urllib2.Request.get_method(self)
else:
pass
Yes, there are better HTTP libraries that would make all this easier on Python 2 and 3, but simplenote.py needs to work with the base Python install.
Use Exceptions as an alternative to version checking
Yeah, as I write this, I guess I should have just done this one way (sys.version
), but you can do things like this:
try:
#For Python 2
values = base64.b64encode(bytes(auth_params,'utf-8'))
except TypeError:
#For Python 3
values = base64.encodestring(auth_params)
and this:
try:
#For Python 2
return str(self.token,'utf-8')
except TypeError:
#For Python 3
return self.token
One really odd thing I found was that under Python 3.4 expectedFailures passing would lead to the test run failing.
Don’t Expect Failures if you don’t expect failures?
This has surely got to be one of those programming oddities as opposed to a proper bug* in Python 3, but I had to remove the use of @unittest.expectedFailure
as when the test passed it caused the whole test run to fail in Python 3 whereas it would work as intended in Python 2. Fortunately another pull request had much improved the testing making this particular test much more stable.
* - Sometimes you just need to get something working and move on, rather than try to get to the bottom of a rabbit hole that’s probably still being dug by a hundred rabbits in all directions.