Migrating Python from 2.x to 3.x

There is a lot of fear, uncertainty and doubt when it comes to migrating to a higher version of any language. Python 3, which is into existence for more than 7 years now, is a newer version of Python 2. Python 3 may or may not necessarily be compatible with the implementations done in Python 2. However, many of us might want to think about migrating to Python 3 as the deadline for Python 2 will approach soon.

The Python 2 end of life date is confirmed to be in the year 2020. Also, there were very few dependencies that were compatible with Python 2 only, apparently very small or those which had Python 3 we could move to. Currently, there is no “right” or “wrong” between “Python 2.x” and “Python 3.x” as long as you have the support for python libraries that you are planning to use in your project. But before you think to make a transition to this higher version of Python, you might want to know insights of this migration being introduced at first place.

There can be possibly 2 approaches for migration:

Supporting only Python 3 –

This is when the support for previous python version is to be removed entirely. This is a bit easier task and less cumbersome. For this, you can use Python 2to3 automatic conversion which will convert python code from 2 to 3 for most of the changes and rest of the code issues can be resolved manually in Python 3 code. However, in the end, it does require a code clean up and hence you would want to change the code all by yourself.

Supporting Python 2 and 3 both-

While maintaining both Python versions can be possible, two branches need to be maintained for that purpose. Maintaining the code which supports only Python 2 at one place and the one which supports only Python 3 at another place is doable but not recommended. If you decide to maintain both Python 2 and Python 3 version, you are increasing difficulty as only fewer bug fixes will be available for the former.

The main difference will not just be in syntax but the behavior of the code, absence/presence of some libraries in both the versions and its semantics. So basically, the migration strategy to be implemented is entirely dependent on the requirement.

Here’s a glimpse of the changes which are required for migrating Python 2.7 to Python 3.6.5-

Unicode and str:

In Python 2.x- str was used for both text and binary which used to make the code brittle and work inconsistently. However, now Python 3 has made a clear distinction between text and binary data. Unicode and str were different in Python 2, are now the same in Python 3. Implicit str type is ASCII in Python 2, while its Unicode in Python 3.

Syntax for “Print” statement:

The noticeably trivial change is in the syntax for “print”. For Python 2.x, there is no issue if additional parentheses used, however, Python 3.x would raise Syntax Error if double quotes are not enclosed with parentheses.

__future__ module:

With the introduction of Python 3.x, few keywords and features which are incompatible with Python 2.x are, e.g. integer division behavior, then one should import

__future__ module in Python 2.x in order to have Python 3.x support in your code.

from __future__ import division

Integer division:

Integer division behavior is changed slightly in the higher version of Python. While some changes might go unnoticed, here’s an example to help understand the change in Python 3.x.

Python 2.x-
3 / 2 = 1
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0

Python 3.x-

3 / 2 = 1.5
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0

Xrange:

Python 3 raises NameError for xrange because the dedicated xrange function does not exist anymore. Now, range() is implemented in Python 3.x just like the xrange() function in Python 2.x

Raising exceptions:

Python 3.x will raise a SyntaxError if the exception argument is not enclosed in parentheses unlike Python 2.x where this was acceptable. Also, unlike Python 2.x, there have been some improvements PEP- 3134 [*] to make debugging easier, as it puts less burden on developers for being traceback-aware. This is one of the most unpopular features of Python 3.x.

The next() function and .next() method:

.next() method is no more present in Python 3.x and raises AttributeError and the next() function remains same in Python 3.x as that of Python 2.x.

Python 2.x

print 'Python', python_version()
my_gen = (letter for letter in 'xyz')
next(my_gen)
my_gen.next()

 

Output-

Python 2.7.6
'y'

Python 3.x

print('Python', python_version())
my_gen = (letter for letter in 'xyz')

next(my_gen)
Output-

Python 3.4.1
'X'

my_gen.next()
--------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)

<ipython-input-14-125f388bb61b> in <module>()
----> 1 my_gen.next()

AttributeError: 'gen' object has no attribute 'next'

Parsing user inputs via input():

One issue in Python 2 was fixed in Python 3 with respect to input() function. In Python 2.x, in order to have other types than strings, we had to use raw_input(), which was a dangerous behavior. This was thankfully fixed in Python 3, so as to always store the user inputs as strobjects.

Returning iterable objects instead of lists:

Python 2.x used to return lists for some methods and functions , while now in Python 3.x returns iterable objects. But for those situations, wherein we really need the list-objects, list() function can be used to convert into list.

Python 2.x

print 'Python', python_version()
print range(5)
print type(range(5))

Output-

Python 2.7.6
[0, 1, 2,3,4,5]
<type 'list'>

Python 3.x

print('Python', python_version())
print(range(5))
print(type(range(5)))
print(list(range(5)))

Output-

Python 3.4.1
range(0, 5)
<class 'range'>
[0, 1, 2,3,4,5]

Here’s the list of few more functions and methods which are commonly used and don’t return lists anymore:

  • zip()
  • map()
  • .values() method in dictionary
  • .items() method in dictionary
  • .keys() method in dictionary

Banker’s Rounding:

There is a better way of rounding up to avoid partially with large numbers. In Python 3, now, round-off functionality is changed when it results it in a tie (that is, .5) at the last significant digits by rounding off to nearest even number. For more information, see the refer these Wikipedia articles and paragraphs:

Python 2.x

print 'Python', python_version()

Python 2.7.12

round(14.5)

15.0

round(17.5)

18.0

 

Python 3.x

print('Python', python_version())

Python 3.5.1

round(14.5)

14

round(17.5)

18

Summary

Switching from Python 2 to Python 3 can be worthwhile for Python programmers since there have been few improvements in Python 3. But before doing it, it’s fruitful to check that the libraries your requirement depends on are supported in Python 3 as well. As the days pass on, the proportion of usage of Python 3 is surely going to increase than Python 2. The migration is happening gradually, but it is underway. You can still successfully write efficient and useful code using any python version. But it is a nice idea to be aware of the material differences between Python 2 and Python 3.

If your code is a reusable package or a framework, then you can drop the support for older python version completely without losing its performance or functionality. This is not at all. A lot more of changes have been done and it’s a good practice to get a thorough idea through the documentation beforehand. I also suggest to check out Brett Cannon’s slides and this migration guide PDF mentioned in Philip Semanchuk’s white paper. Keep Calm and Keep Coding!!



Leave a Reply