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.:

   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):

   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:

   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.:

   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.:

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

You can also use the following approach:

   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.:

   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:

   1 import package
   2 
   3 package.my_name(...)

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

   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

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

urllib

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

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

urllib2

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

   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

   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:

   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.

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

Functions

execfile()

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

unichr()

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

xrange()

   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(..).

   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*()

   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:

   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()

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

iter.next()

   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):

   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

   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)