How to make your Python code compatible with version 2.7 and 3.3 (and higher)

While the current ROS distributions are using Python 2 we are striving to support Python 2.7 as well as Python 3.3 (and higher). Especially since some platforms other than Ubuntu are already using Python 3 only.

On this page you will find several tips and common code snippets how to make your Python code compatible with version 2.7 as well as 3.3.

Common rules

Whenever calling print the arguments must be enclosed in parenthesis, e.g.:

Toggle line numbers
   1 # <<< instead of
   2 print 'hello world'
   3 # --- use
   4 print('hello world')
   5 # >>>

Whenever you pass multiple arguments to print you must use the future import at the very beginning of your file (in order to not change the behavior):

Toggle line numbers
   1 # <<< instead of
   2 print 'hello', 'world'
   3 # --- use
   4 from __future__ import print_function
   5 print('hello', 'world')
   6 # >>>

Also whenever you print to a specific file-like object you must use the future import at the very beginning of your file and pass the file-like object as a keyword argument to the print function:

Toggle line numbers
   1 # <<< instead of
   2 print >> sys.stderr 'hello world'
   3 # --- use
   4 from __future__ import print_function
   5 print('hello world', file=sys.stderr)
   6 # >>>

Strings and encodings / unicode

In Python 2 bytes and strings are not treated differently and can be used with all APIs. It is completely up to the developer to take care to *decode* (from bytes to strings) and *encode* (from strings to binary) them correctly.

In Python 3 these kind of strings have different types: bytes and str. Most of the API is type specific, e.g. a file opened in binary mode will return bytes on read().

E.g. in Python 3 the subprocess module will return the output of executed programs as binary which usually needs to be decoded explicitly.

For more information on unicode please see http://docs.python.org/3.3/howto/unicode.html.

Relative imports

Relative imports must start with a . (dot), e.g.:

Toggle line numbers
   1 from .my_sibling_module import MyClass

In general absolute imports are recommended (see https://www.python.org/dev/peps/pep-0008/#imports).

Integer division

While integer division resulted in an int before in Python 3 the result is actually a float. So if the rounding was intentional you usually want to cast it explicitly, e.g.:

Toggle line numbers
   1 # <<< instead of
   2 i / j
   3 # --- use
   4 int(i / j)
   5 # >>>

You can also use the following approach:

Toggle line numbers
   1 from __future__ import division
   2 1 / 2 --> 0.5
   3 4 / 2 --> 2.0
   4 1 // 2 --> 0
   5 4 // 2 --> 2

Octal numbers

Octal numbers must contain an o between the leading zero and the rest of the number, e.g.:

Toggle line numbers
   1 my_number = 0o1234

Various imports

Commonly the following snippets will try to import the new Python 3 style packages / modules / classes and fall back to the Python 2 version.

Whenever you use a class / function / variable like this:

Toggle line numbers
   1 import package
   2 
   3 package.my_name(...)

you should import the specific class / function / variable conditionally like this:

Toggle line numbers
   1 try:
   2     from python3_package import my_name
   3 except ImportError:
   4     from python2_package import my_name
   5 
   6 my_name(...)

In the following you will find snippets for commonly used packages / modules.

queue

Toggle line numbers
   1 try:
   2     from queue import Queue
   3 except ImportError:
   4     from Queue import Queue

urllib

Toggle line numbers
   1 try:
   2     from urllib.parse import urlparse
   3 except ImportError:
   4     from urlparse import urlparse

Toggle line numbers
   1 try:
   2     from urllib.request import urlretrieve
   3 except ImportError:
   4     from urllib import urlretrieve

urllib2

Toggle line numbers
   1 try:
   2     from urllib.request import urlopen
   3 except ImportError:
   4     from urllib2 import urlopen

Toggle line numbers
   1 try:
   2     from urllib.error import HTTPError
   3     from urllib.error import URLError
   4 except ImportError:
   5     from urllib2 import HTTPError
   6     from urllib2 import URLError

xmlrpc

Toggle line numbers
   1 try:
   2     from xmlrpc.client import ServerProxy
   3 except ImportError:
   4     from xmlrpclib import ServerProxy

pickle

For pickle you usually use the c implementation in Python 2. In Python 3 there is not differentiation necessary anymore:

Toggle line numbers
   1 try:
   2     import cPickle as pickle
   3 except ImportError:
   4     import pickle

StringIO

For StringIO the conditional import should first check for the c implementation in Python 2 and then fall back to the Python 3 module. This is recommended since Python 2 does have an io module but its implementation of StringIO is subtle different.

Toggle line numbers
   1 try:
   2     from cStringIO import StringIO
   3 except ImportError:
   4     from io import StringIO

Functions

execfile()

Toggle line numbers
   1 # <<< instead of
   2 execfile(filename)
   3 # --- use
   4 exec(open(filename).read())
   5 # >>>

unichr()

Toggle line numbers
   1 # <<< instead of
   2 unichr(...)
   3 # --- use
   4 try:
   5     unichr(...)
   6 except NameError:
   7     chr(...)
   8 # >>>

xrange()

Toggle line numbers
   1 # <<< instead of
   2 xrange(...)
   3 # --- use
   4 range(...)
   5 # >>>

zip()

zip() does not return a list but an iterable in Python 3. Therefore you can not use index based access on this anymore. If you need index based access you should wrap the invocation in list(..).

Toggle line numbers
   1 # <<< instead of
   2 zip(...)[i]
   3 # --- use
   4 list(zip(...))[i]
   5 # >>>

The same applies to others functions like map, range etc.

Methods

dict.iter*()

Toggle line numbers
   1 # <<< instead of
   2 d.iteritems()
   3 d.iterkeys()
   4 d.itervalues()
   5 # --- use
   6 d.items()
   7 d.keys()
   8 d.values()
   9 # >>>

Since keys() and values() does not return a plain list but a iterable from which you can not access specific indices directly you must explicitly convert the result to a list:

Toggle line numbers
   1 # <<< instead of
   2 d.keys()[i]
   3 d.values()[i]
   4 # --- use
   5 list(d.keys())[i]
   6 list(d.values())[i]
   7 # >>>

dict.has_key()

Toggle line numbers
   1 # <<< instead of
   2 dictionary.has_key(key)
   3 # --- use
   4 key in dictionary
   5 # >>>

iter.next()

Toggle line numbers
   1 # <<< instead of
   2 iterator.next()
   3 # --- use
   4 next(iterator)
   5 # >>>

Types

string

To test if a variable is a normal string (rather than a unicode string):

Toggle line numbers
   1 # <<< instead of
   2 is_instance(obj, basestring)
   3 # --- use
   4 try:
   5     return isinstance(obj, basestring)
   6 except NameError:
   7     return isinstance(obj, str)
   8 # >>>

types.ListType

Toggle line numbers
   1 # <<< instead of
   2 types.ListType
   3 # --- use
   4 list
   5 # >>>

Wiki: python_2_and_3_compatible_code (last edited 2015-06-08 20:12:00 by DirkThomas)