Porting 2.x code so that it runs on 2.x and 3.x from a single codebase
When porting Django so that it works from a single codebase in Python 2.x and 3.x, the following is a rough-and-ready guide which I (Vinay Sajip) followed, and these guidelines should also apply to the work of porting Django apps to work on the same basis.
- Do run
2to3
on the codebase, but pipe the output to a file so that you can examine what changes need to be made, But don't run2to3
to make inplace changes to your code, as the resulting code will typically not run under Python 2.x. Go through the piped output to see where you need to make changes. These will typically fall into a number of categories, as described below.
- In any module which uses Unicode or bytes literals (
u'foo'
orb'bar'
), do insertfrom django.utils.py3 import u, b
at the top of the module in the appropriate place, and do replaceu'foo'
withu('foo')
andb'bar'
withb('bar')
throughout the source. The same applies to the constants with double quotes (u"foo"
orb"bar"
, which should be replaced byu("foo")
orb("bar")
respectively). Note: this is needed for Python 2.5 compatibility only. For compatibility only with Python 2.6 and later, addfrom __future__ import unicode_literals
in all modules where you are using Unicode literals, and remove theu
prefix from the Unicode literals, as you don't need it. If you need native strings, usefrom django.utils.py3 import n
and replace those literals withn(...)
. This will returnstr
on both 2.x and 3.x. If you need byte strings, useb'foo'
. Unfortunately, it's hard to know where native strings are required: for example, some 2.x C-based APIs expect native strings and behave unexpectedly when passed Unicode strings.
- If you need to make any code conditional on 2.x vs. 3.x, you can do
from django.utils.py3 import PY3
and usePY3
as a condition (as you might expect, it's abool
which isTrue
on 3.x andFalse
on 2.x.
- If you see any long constants (such as
5L
), dofrom django.utils.py3 import long_type
and replace e.g.5L
withlong_type(5)
.
- If you see
(int, long)
or(long, int)
in the source, dofrom django.utils.py3 import integer_types
and replace the tuple withinteger_types
. Iflong
andint
appear in a tuple along with other values, remove them from the tuple and replace the tuple with tuple+ integer_types
.
- If you see octal constants (such as
0777
), replace them with the hex value (0x1ff
for the0777
case), and if possible, place the octal constant in a comment so anyone can see what it was originally. Note: this is only needed for 2.5 compatibility. For 2.6+ compatibility, you can use the 3.x octal form0o777
.
- If you see code of the type
except ExceptionTypeOrTupleOfExceptionTypes, name_to_bind_to:
, see ifname_to _bind_to
is used in the scope (exception handling clause, or later in the same function or method). If not used, just change theexcept:
statement toexcept ExceptionTypeOrTupleOfExceptionTypes:
and you're done. If used, do one more thing: put the codename_to_bind_to = sys.exc_info()[1]
just beforename_to_bind_to
is first used, and make sure that thesys
module is imported. Note: this is only needed for 2.5 compatibility. For 2.6+ compatibility, you can use the 3.x formexcept ExceptionTypeOrTupleOfExceptionTypes as name_to_bind_to
. Note that in 3.x, names bound like this are not in the local scope, and so not visible outside the exception clause.
- If
unicode
occurs in the source (i.e. not in comments), dofrom django.utils.py3 import text_type
and replaceunicode
withtext_type
.
- If
basestring
occurs in the source (i.e. not in comments), dofrom django.utils.py3 import string_types
and replacebasestring
withstring_types
.
- If
str
occurs in the source (i.e. not in comments), you may need to dofrom django.utils.py3 import binary_type
and replacestr
withbinary_type
. However, don't use this blindly:str()
is sometimes used to convert something to text for display, and these occurrences ofstr()
shouldn't need changing.
- If you have classes with metaclasses, change them to use the form
MyClassWithMetaClass(with_metaclass(MetaClass, BaseClass):
where you can omitBaseClass
if it isobject
. Remove the__metaclass__ = XXX
statement from the class body (replacing it withpass
if that's all there was in the class body).
- If you need to reraise an exception, do
from django.utils.py3 import reraise
and invoke it using the formreraise(type, instance, traceback)
.
- You will see that
2to3
putslist()
around calls to methods namedkeys
,values
oritems
. This is sometimes but not always necessary, so examine each on a case-by-case basis. In particular, ifvalues
is called on a queryset, you don't want to wrap withlist()
.
- There are functions
iterkeys
,itervalues
anditeritems
defined indjango.utils.py3
. Import them if there is any reference to those methods, and replace using the patternx.iterkeys()
->iterkeys(x)
(wherex
might be an expression rather than just a name).
- Replace
map
calls with the list comprehensions suggested by2to3
. If you seemap(None, ...)
you will need to import and useizip_longest
fromdjango.utils.py3
.
- If you see
xrange
in the source, just dofrom django.utils.py3 import xrange
.
- If you use anything from
urllib
,urllib2
,urlparse
, you will need to import the actual functions fromdjango.utils.py3
. You can grep the instances of those calls in the ported Django code to see how it's done.
- If you're using
StringIO
,BytesIO
, orpickle
, import them fromdjango.utils.py3
.
- If a class has a
next()
method which is used for iteration, add a line__next__ = next
just after it in the class. If it has a__nonzero__
method, add__bool__ = __nonzero__
in the same way. Where you seex.next()
used to iterate, dofrom django.utils.py3 import next
and replace using the patternx.next()
->next(x)
(wherex
might be an expression rather than just a name).
- If you define rich comparisons, review how they work in 2.x and 3.x and adapt your code accordingly.
- If you use
exec
orexecfile
, use the versionsexec_
andexecfile_
fromdjango.utils.py3
.
- Use
django.utils.py3.maxsize
in place ofsys.maxint
.
- Review
__repr__
,__str__
and__unicode__
methods to see what needs changing. On Python 3.x,__str__()
should not returnbytes
, and neither should__repr__()
.
- Take a look at
django.utils.py3
to see what else you might need to change in your code: everything in there is needed somewhere in the porting process.
Last modified
13 years ago
Last modified on Dec 26, 2011, 6:22:09 AM
Note:
See TracWiki
for help on using the wiki.