With Python 2 nearing the end of its life, it’s time to migrate your project from Python 2 to Python 3.

Python 2.x will soon lose official support, but migrating from Python 2 to Python 3 is not as difficult as it might seem. I spent one night last week migrating a 3D renderer’s front end code and its PySide counterpart to Python 3. In retrospect, the process was surprisingly easy compared to the painful refactoring, even though there were inevitably some minor tweaking involved.

Everyone had no choice but to migrate for a variety of reasons: maybe they felt they had delayed too long, or maybe they relied on a module that was no longer maintained under Python 2. But if you just want to do something to contribute to open source, migrating a Python 2 application to Python 3 is simple and makes sense.

Whatever your reason for moving from Python 2 to Python 3, this is an important task. Follow these three steps to get your tasks done more clearly.

1. Use 2to3

Since a few years ago, Python has, unbeknownst to you, come with a script called 2to3 that will help you automatically convert most of your code from Python 2to Python 3.

Here is some code written in Python 2.6:

#! /usr/bin/env python
# -*- coding: utf-8 -*-
mystring = u"Abcde"
print ord(mystring[-1])
Copy the code

Execute the 2to3 script on it:

$ 2to3 example.py RefactoringTool: Refactored example.py -- example.py (original) +++ example.py (Refactored) @@ -1,5 +1,5 @@#! /usr/bin/env python
 # -*- coding: utf-8 -*-
 
-mystring = u"Abcde"
-print ord(mystring[-1])
+mystring = "Abcde"
+print(ord(mystring[-1]))
RefactoringTool: Files that need to be modified:
RefactoringTool: example.py
Copy the code

By default, 2to3 only flags code that must be changed when moving to Python 3. Python 3 code shown in the output is directly available, but you can add -w or –write to 2to3. This allows it to modify your Python 2 code files directly according to the given scheme.

$ 2to3 -w example.py [...]  RefactoringTool: Files that were modified: RefactoringTool: example.pyCopy the code

The 2to3 script doesn’t just work for a single file; you can use it for all Python files in a directory, and it will recursively work for all Python files in subdirectories.

Use Pylint or Pyflakes

It is not uncommon for some bad code to run without exception in Python 2 and report more or less an error in Python 3. Because these bad code cannot be fixed by syntactic conversions, 2to3 has no effect on them, but will generate an error when run with Python 3.

To figure this out, you’ll need tools like Pylint, Pyflakes (or Flake8 wrappers). I prefer Pyflakes, which are different from Pylint in that it ignores code style differences. While beautiful code is one of Python’s defining features, when it comes to code migration, “making code function consistent” is far more important than “making code style consistent.”

This is an example of Pyflakes output:

$ pyflakes example/maths
example/maths/enum.py:19: undefined name 'cmp'
example/maths/enum.py:105: local variable 'e' is assigned to but never used
example/maths/enum.py:109: undefined name 'basestring'
example/maths/enum.py:208: undefined name 'EnumValueCompareError'
example/maths/enum.py:208: local variable 'e' is assigned to but never used
Copy the code

This output of Pyflakes above clearly shows the issues that need fixing in the code. By comparison, Pylint prints a whopping 143 lines, mostly for trivial issues like code indentation.

Note the misleading error in line 19. From the output you might expect CMP to be an undefined variable before it is used. CMP is actually a built-in function in Python 2, which was removed in Python 3. This code is placed in the try statement block, and the problem is easily overlooked unless the output value of this code is carefully checked.

    try:
        result = cmp(self.index, other.index)
    except:
        result = 42
       
    return result
Copy the code

In the process of code migration, you will find that many functions that normally work in Python 2 are changed or even removed from Python 3. For example, PySide’s binding has changed, importlib has replaced IMP, and so on. Such problems can only be solved one at a time, and it is up to you to weigh whether the features involved need to be refactored or discarded. But for now, most of the problems are known and well documented. So the hard part is not fixing the problem, but finding it, and in that sense using Pyflake is necessary.

3. Fix broken Python 2 code

While 2to3 scripts can help you adapt your code to Python 3-compatible forms, they are a little weak for a complete code base, as some older code may require a different structure in Python 3. In such cases, modifications can only be made manually.

For example, the following code works in Python 2.6:

class CLOCK_SPEED:
        TICKS_PER_SECOND = 16
        TICK_RATES = [int(i * TICKS_PER_SECOND)
                      for i in(0.5, 1, 2, 3, 4, 6, 8, 11, 20)] class FPS: STATS_UPDATE_FREQUENCY = clock_spee.ticks_per_secondCopy the code

Automated tools such as 2to3 and Pyflakes won’t be able to spot the problem, but if the code is run using Python 3, the interpreter will consider clock_speedi.ticks_per_second undefined. Therefore, we need to change the code to an object-oriented structure:

class CLOCK_SPEED:
        def TICKS_PER_SECOND():
                TICKS_PER_SECOND = 16
                TICK_RATES = [int(i * TICKS_PER_SECOND)
                        for i in (0.5, 1, 2, 3, 4, 6, 8, 11, 20)]
                return TICKS_PER_SECOND

class FPS:
        STATS_UPDATE_FREQUENCY = CLOCK_SPEED.TICKS_PER_SECOND()
Copy the code

You might think that writing TICKS_PER_SECOND() as a constructor (with the default __init__ function) would make the code look cleaner, But this would require changing the method’s call form from clock_spee.ticks_per_second () to CLOCK_SPEED(), which would have more or less unknown effects on the entire library. If you know the structure of the entire codebase by heart, you can make these changes as much as you want. But I generally think that whenever I make a change, it’s likely to affect at least three places in the rest of the code, so I prefer not to change the structure of the code.

Adhere to the faith

If you are trying to migrate a large project from Python 2 to Python 3, you may find it a long process. You may not be able to find a useful error message after all your efforts, in which case you may even be tempted to tear the code down and rebuild. On the other hand, the code already runs in Python 2, and all you need to do to make it work in Python 3 is transform it a little.

But once you’re done migrating, you’ve got Python version 3 of the module or the entire application, plus official Python long-term support.


Via: opensource.com/article/19/…

By Seth Kenlon (lujun9972

This article is originally compiled by LCTT and released in Linux China