Developer’s Guide to Python 3.0: Python 2.6 and Migrating From 2 to 3

Developer’s Guide to Python 3.0: Python 2.6 and Migrating From 2 to 3

his is the final article in the Developer’s Guide to Python 3.0 series. It covers the Python 2.6 language and how to migrate from Python 2.x to Python 3.0, and discusses:

  • The current state of Python 2.x libraries
  • The right time to migrate
  • How to migrate
  • What migration tools are available

Python 2.6

Python 2.6 (2.6.2 to be exact) is the latest Python 2.x release. It can run any Python 2.x program “as is.” With that said, Python 2.6 and 3.0 were developed in lockstep. Every backwardly compatible feature of Python 3.0 and its standard library modules has been back ported to Python 2.6. That makes Python 2.6 an excellent stepping stone if you want to migrate to Python 3.0. Here’s a list of features that Python 2.6 and 3.0 share:

  • PEP-343: The with statement
  • PEP-366: Explicit Relative Imports from a Main Module
  • PEP-370: Per-user Site-packages Directory
  • PEP-371: The Multi-processing Package
  • PEP-3101: Advanced String Formatting
  • PEP-3105: print as a Function
  • PEP-3110: Exception-handling Changes
  • PEP-3112: Byte Literals
  • PEP-3116: New I/O Library
  • PEP-3118: Revised Buffer Protocol
  • PEP-3119: Abstract Base Classes
  • PEP-3127: Integer Literal Support and Syntax
  • PEP-3129: Class Decorators
  • PEP-3141: A Type Hierarchy for Numbers
Author’s Note: You can read more about these features in the previous articles in the series (see the Related Resources section).

The ast, json and fractions modules are also available in Python 2.6. Many other changes covered in the earlier Python 3.0 articles are already available in Python 2.6. You can find a comprehensive reference here.

Overall, the Python 2.6/Python 3.0 combination is very impressive, and shows how much effort the Python core developers put into helping developers make the leap from Python 2.x to 3.0. You can safely stay in Python 2.x land, yet still take advantage of many deep 3.0 language-level features such as abstract base classes, class decorators, and even the new exception-handling syntax. Moreover, any efforts you make in that direction will also make migration (when you’re ready) that much easier.

Migration to Python 3.0

There are three different “flavors” of migration you should consider:

  • Start using Python 3.0 for new projects.
  • Port existing projects to Python 3.0.
  • Support both Python 2.x and 3.0 in your projects.

In addition, you should take these important factors into account:

  • How critical is your project?
  • What is the skill level of the developers?
  • What is the lifespan of your project?

To make an informed decision, you need to have a pretty good grasp of the following:

  • The current status of Python 3.0
    • Stability
    • Performance
    • The Python 3.0 porting status of third-party packages you use or depend on
    • Python 3.0 tool support
    • Python 3.0 books
    • Python 3.0 community support
  • The future of Python 3.x
    • Roadmap of Python 3 itself
    • Roadmap of Python 3 support of third-party packages
    • Roadmap of Python 3 tools
  • The future of Python 2.x

That’s a lot to consider, but this article will give you solid information and guidelines.

I’ll begin with Python 3 itself. The final release of Python 3.0 occurred on December 3, 2008, followed by a bug fix release (Python 3.01) on February 13, 2009. Python 3.0 is still in bug fix mode, so no new features will be added until Python 3.1. My own experience with Python 3.0 was very positive and I didn’t run into any bugs; however, Python 3 had very little field testing, so there might be bugs lurking in the parts of the language or (more likely) the standard library that impact your application. In addition, Python 3’s performance has a lot of room for improvement; overall, it’s slower than Python 2.

Python 3.1 has been released on June 27, 2009 exactly on schedule (according to the release schedule of PEP-375. Python 3.1 includes these main features and additions:

  • importlib
  • io rewritten in C
  • A reworked email package
  • An IP Address library added to stdlib
  • Update simplejson to the latest external version
  • An ordered dictionary collections class
  • A fix for contextlib.nested()
  • Auto-numbered replacement fields in str.format() strings
  • An upgraded xml.etree to the latest external version
  • The ability to nest with statements

As you can see, it’s a pretty low-key release. Rewriting the io module in C will probably improve the performance of I/O-bound applications. Python 3.1’s main purpose is to serve as a kind of a service pack for Python 3.0, which is why it will appear only seven months after Python 3.0. Python 3.2 will probably be released 18 to 24 months later, which is a more typical release rate for minor versions. See this blog for a better analysis. The release schedule for Python 2.7 is not finalized yet, but the idea is that it will follow the Python 3 development cycle closely, so it will be probably be released around the time Python 3.2 is released—again, it will serve as a migration step from Python 2 to Python 3.

Third-Party Packages

The schedule for Python itself seems pretty predictable (especially given Python’s unusual track record of delivering on time). But the picture for third-party packages is not as clear. As you will see later in this article, it’s usually pretty straightforward to port a Python 2 codebase to Python 3—especially if the Python 2 code uses modern idioms. However, many sophisticated Python packages depend on other Python packages and/or C extension modules. In such cases you won’t be able to port upstream packages until all the dependencies have been ported as well. This means that packages with deep dependency graphs might have to wait a long time until all their dependencies have been ported.

At PyCon 2009 in Chicago Guido Van Rossum commented on the porting question. He believes that most major packages will have been ported to Python 3 by the next PyCon (March 2010), which is probably a very good estimate. By the time of Python 3.1’s release, a lot of people will be using it, so there will be a lot of grass-roots pressure on the main third-party package developers to port their packages. Many package developers have already taken initial steps in this direction, as you can see by the Python 3 packages listed on the Python Package Index.

Note that among the important third-party packages still missing are: Numpy, PIL, wxPython, Twisted, and TurboGears.

There is a chicken and an egg problem here. The porting effort for big, mature packages and frameworks could be substantial. It is often not cost-effective for package developers to port immediately to Python 3 unless their users demand it. In fact, they actually benefit from waiting, both because they don’t have to maintain another port and because the porting tools and practices will get better over time. In addition, frameworks built on top of many other packages (such as TurboGears) must wait until their dependencies have been ported.

Python 3 Books, Tools, and Support

There are already several Python 3 books. Most successful Python books will get a new Python 3 edition. The list may grow by the time you read this article. If you are a seasoned Python 2 programmer I recommend the online Dive into Python 3 by Mark Pilgrim. This is an incomplete work in progress, but it has an informal and accessible writing style.

The tool support situation is not as bright. As far as I can tell, no Python development environment fully supports Python 3 yet. I use Komodo as my main Python IDE. I particularly like its debugger and it definitely irks me that I can’t use it for Python 3 coding.

Community support is always available on the mailing lists, and is bound to get better as more people move to Python 3 and run into your problem before you do and write about it.

With that information in hand, here are some concrete guidelines regarding the timing of migration. I recommend starting with or porting to Python 3 immediately if:

  • You are a newcomer that wants to learn Python for fun (and maybe profit in the future). You should read and start a little project of your own in Python 3.
  • You are a seasoned Python 2 programmer that wants to learn what’s up with Python 3. You should read, start a little project of your own in Python 3 and also port a little Python 2 project.
  • You are an open source developer that wants to support Python 3 developers.

In contrast, I recommend staying with Python 2 for now if:

  • You are responsible for an important project based on Python 2.
  • You have a tight schedule.
  • Your project is in maintenance only mode.

If you are unsure whether you should pick up Python 3 or stay with Python 2, stay with Python 2. However, if you do decide to stay with Python 2, I recommend porting to Python 2.6 if you can spare the cycles. That release includes many valuable additions and changes, and you will be ready for Python 3 migration when it’s appropriate. It should be painless.

The Migration Process

Assuming you’ve digested all the information, heard all the warnings and recommendations, and you have made up your mind to port your Python 2 project to Python 3. How would you go about it?

Before getting started, you should know that there’s a tool that can help, called 2to3. The 2to3 tool is a Python program that automatically converts Python 2.6 code to Python 3.0 code. It has a modular architecture and includes plug-ins that can fix specific issues. While it’s very useful, 2to3 can’t (or at least doesn’t) do everything for you—there are several issues that you need to fix manually.

Here are the step-by-step instructions:

  1. Make sure you have good test coverage and that all the tests pass (under Python 2.x).
  2. Port your code to Python 2.6.
  3. Make sure all the tests pass using that version.
  4. Run your code under Python 2.6 with the -3 flag.
  5. Fix any warnings.
  6. Run the tests again (still under Python 2.6).
  7. Run the 2to3 tool.
  8. Run your tests under Python 3.
  9. Fix any problems.
  10. Repeat steps 8 and 9 until all tests pass.

Here’s an example that uses this process to migrate a simple program that populates a dictionary with some values, prints them and writes the sorted values to a file. This program has some elements that are not compatible with Python 3. Here’s the code:

import osimport sysdef main():  print '====================================='  print 'Python 2.x to Python 3 Migration test'  print '====================================='  print 'Python version is:', sys.version  d = dict(a=1, b=2, c=3)  assert dict.has_key('a')  print 'The items of d:'  for k, v in d.iteritems():    print '%s: %s' % (k, v)  v = d.values()  print 'First value is', v[0]  if os.path.isfile('values.txt'):    os.remove('values.txt')  f = file('values.txt', 'w')  print>> f, sorted(v)  f.close()def test():  main()  s = open('values.txt', 'rb').read()  print s  assert s == '[1, 2, 3]
'if __name__=='__main__':  test()

The preceding program is self-testing. Running the script executes the test() function, which calls the main() function and verifies that the correct results are in a file. Test coverage is important because Python is a dynamic language, and the tools are not perfect. Unless your tests cover your entire codebase, you can’t be sure whether the migration was successful—it might fail at runtime when the interpreter gets to the untested areas of the code.

Step 1: Ensure All Tests Pass Under Python 2.x

Here’s the test output under Python 2.5

=====================================Python 2.x to Python 3 Migration test=====================================Python version is: 2.5.2 (r252:60911, Feb 22 2008, 07:57:53) [GCC 4.0.1 (Apple Computer, Inc. build 5363)]The items of d:a: 1c: 3b: 2First value is 1[1, 2, 3]

That’s the expected output. In addition, the assert statement in the test() function didn’t complain, so all the tests pass (just one test in this case).

Step 2: Port Your Code to Python 2.6

In this case, the code itself didn’t require changes, so I simply ran the program under Python 2.6.

Step 3: Make Sure All Tests Pass

Here’s the output:

=====================================Python 2.x to Python 3 Migration test=====================================Python version is: 2.6.2 (r262:71600, Apr 16 2009, 09:17:39) [GCC 4.0.1 (Apple Computer, Inc. build 5250)]The items of d:a: 1c: 3b: 2First value is 1[1, 2, 3]

So far, it works perfectly. Note that the program always prints the Python version it is running under. This can be useful during migration when you run the same code under several interpreters.

Step 4: Run Code in Python 2.6 Using the -3 Flag

The -3 flag warns about Python 3 compatibility issues that 2to3 can’t solve. These issues include: dict.has_key(), apply(), callable(), coerce(), execfile(), reduce() and reload(). I’m not sure why 2to3 can’t handle most of these issues automatically, but that’s how it is. When running the program with the -3 flag you get a nice deprecation warning that even tells you what the fix is: DeprecationWarning: dict.has_key() not supported in    3.x; use the in operator   assert d.has_key('a')

Step 5: Fix the Warnings

In this case, I replaced the code d.has_key('a') with 'a' in d.

Step 6: Repeat Tests (Still Under Python 2.6)

Everything works fine even with the -3 flag.

Step 7: Run the 2to3 Tool

Here is the result of running 2to3 on our program (use the -w switch to overwrite the original file):

> 2to3 -w article_5.pyRefactoringTool: Skipping implicit fixer: bufferRefactoringTool: Skipping implicit fixer: idiomsRefactoringTool: Skipping implicit fixer: set_literalRefactoringTool: Skipping implicit fixer: ws_comma--- (original)+++ (refactored)@@ -2,33 +2,33 @@ import sys  def main():-  print '====================================='-  print 'Python 2.x to Python 3 Migration test'-  print '====================================='-  print 'Python version is:', sys.version+  print('=====================================')+  print('Python 2.x to Python 3 Migration test')+  print('=====================================')+  print('Python version is:', sys.version)    d = dict(a=1, b=2, c=3)   assert 'a' in d -  print 'The items of d:'-  for k, v in d.iteritems():-    print '%s: %s' % (k, v)+  print('The items of d:')+  for k, v in d.items():+    print('%s: %s' % (k, v)) -  v = d.values()-  print 'First value is', v[0]+  v = list(d.values())+  print('First value is', v[0])    if os.path.isfile('values.txt'):     os.remove('values.txt')    # The 'file' function is NOT handled by 2to3   f = file('values.txt', 'w')-  print>> f, sorted(v)+  print(sorted(v), file=f)   f.close()  def test():   main()   s = open('values.txt', 'rb').read()-  print s+  print(s)   assert s == '[1, 2, 3]
'  if __name__=='__main__':RefactoringTool: Files that were modified:RefactoringTool:

All the print statements were fixed (including the print>>). The dict’s iteritems() method was fixed to simply items() (which returns an iterator in Python 3). It wrapped the d.values() call, which returns a list in Python 2.x, in a list. That’s pretty good. The original file was saved with a .bak extension and replaced with the refactored code.

You can run 2to3 on entire directories also and it has many command line options.

Usage: 2to3 [options] file|dir ...

Options:  -h, --help            show this help message and exit  -d, --doctests_only   Fix up doctests only  -f FIX, --fix=FIX     Each FIX specifies a transformation;                         default: all  -x NOFIX, --nofix=NOFIX                        Prevent a fixer from being run.  -l, --list-fixes      List available transformations (fixes/fix_*.py)  -p, --print-function  Modify the grammar so that print() is a function  -v, --verbose         More verbose logging  -w, --write           Write back modified files  -n, --nobackups       Don't write backups for modified files.

Step 8: Run Code in Python 3

Make sure you have Python 3 installed, and then run the tests again using Python 3. Running the sample program gives the following output:

=====================================Python 2.x to Python 3 Migration test=====================================Python version is: 3.0.1 (r301:69597, Feb 14 2009, 19:03:52) [GCC 4.0.1 (Apple Inc. build 5490)]The items of d:a: 1c: 3b: 2First value is 1Traceback (most recent call last):  File "", line 35, in     test()  File "", line 29, in test    main()  File "", line 24, in main    f = file('values.txt', 'w')NameError: global name 'file' is not defined

What happened? The program is using the file function that was a built-in function in Python 2.x, but doesn’t exist in Python 3. The 2to3 tool doesn’t handle it and the -3 warning flag is unaware of the problem. This is the reason it is so important to have good test coverage.

Step 9: Fix Problems

The fix in this case is very simple; just replace the file function with the open function:

f = open('values.txt', 'w')

This time, when you run the tests, you’ll see that the fix worked, but now the assert fails:

b'[1, 2, 3]
'Traceback (most recent call last):  File "", line 35, in     test()  File "", line 32, in test    assert s == '[1, 2, 3]

Step 10: Repeat Steps 8 and 9 Until All Tests Pass

What’s the problem now? In Python 3, when you open a file in binary mode and read from it you get back a bytes object, not a string. You can fix the problem by opening the file for reading as a text file (use 'r' instead of 'rb'):

def test():  main()  s = open('values.txt', 'r').read()  print(s)  assert s == '[1, 2, 3]

That last change solved all the problems. Now, all the tests pass and the migration is complete.

More about 2to3

The 2to3 tool is an excellent example of a modern Python application. It’s well worth your time to look at Guido Van Rossum’s code. The architecture is interesting too. The main component is a general-purpose Python refactoring engine. This engine is coupled with a simple plug-in infrastructure, where each plug-in can perform a particular refactoring. It is designed to be extensible, and you may find it useful to extend the application for your own purposes. The only drawback is that 2to3 is largely undocumented.

But here are the basic workings. You’ll find the code in the lib2to3 package under the lib subdirectory of the location where you installed Python 2.6 (on a Mac by default it’s in /Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/lib2to3). The package contains the following files:

You’ll also find three sub-directories: pgen2, fixes, and tests, each of which is described below.


The pgen2 subdirectory contains,,, and some other files that are responsible for the parsing portion of the application, based on grammar files.


This directory contains the concrete “fixers” that implement the actual 2to3 functionality. A fixer is a plug-in that applies a specific refactoring technique. You’ll get a closer look at one of the fixers later on.


This subdirectory contains a comprehensive suite of tests that test the framework itself as well as every fixer.

The other files—in the root directory—are:

The file is empty.

The file has a main() function that gets invoked by the 2to3 script itself. This is the real entry point. It defines a class called StdoutRefactoringTool that subclasses the generic refactor.RefactoringTool and prints its output to standard output. The main() function parses the command line options using the optparse.OptionParser class, initializes the refactoring tool, filters the fixers list (from the fixers subdirectory), launches the refactoring tool, and reports the results. This is a fine example of a main program for an entire class of command line programs. It doesn’t do too much, and it delegates the heavy lifting to the refactoring tool.

This file contains the code for a nice general-purpose refactoring engine. It manages the fixer plug-ins (which are just Python modules located in a package—a sub-directory—with a naming convention). The main class is RefactoringTool, which exposes several refactoring methods such as refactor_file, refactor_dir, refactor_string, and so forth. The RefactoringTool class traverses an AST tree (both pre- and post-order) and applies its fixers to appropriate nodes via the match() and transform() methods.

The actual parsing and conversion of Python source code to an AST tree is the most complicated part of the project. I will not go into too much detail because it is pretty hairy code.

This module contains the pattern compiler. 2to3 looks for patters to transform your code from Python 2 to Python 3. The grammar is fairly simple, and it’s defined in PatternGrammer.txt:

# Copyright 2006 Google, Inc. All Rights Reserved.# Licensed to PSF under a Contributor Agreement.# A grammar to describe tree matching patterns.# Not shown here:# - 'TOKEN' stands for any token (leaf node)# - 'any' stands for any node (leaf or interior)# With 'any' we can still specify the sub-structure.# The start symbol is 'Matcher'.Matcher: Alternatives ENDMARKERAlternatives: Alternative ('|' Alternative)*Alternative: (Unit | NegatedUnit)+Unit: [NAME '='] ( STRING [Repeater]                 | NAME [Details] [Repeater]                 | '(' Alternatives ')' [Repeater]                 | '[' Alternatives ']'                 )NegatedUnit: 'not' (STRING | NAME [Details] | '(' Alternatives ')')Repeater: '*' | '+' | '{' NUMBER [',' NUMBER] '}'Details: '<' Alternatives '>'

This module contains the implementation of the parse tree. It has Node and Leaf classes as well as various pattern classes (LeafPattern, NodePattern, WildcardPattern and NegatedPattern).

This module just defines a Symbol class and loads the Python grammar from a file.

Many utility functions for creating nodes, testing nodes and a bunch that defy classification. Here is an example for a function that creates a function call node:

def Call(func_name, args=None, prefix=None):    """A function call"""    node = Node(syms.power, [func_name, ArgList(args)])    if prefix is not None:        node.set_prefix(prefix)    return node

This module contains the BaseFix and ConditionalFix classes. I’ll concentrate on BaseFix here. This class can be used as a subclass to a fixer; studying it can help you understand what fixers do, and how they do it. Here’s a list of the BaseFix methods with implementation and comments elided:

class BaseFix(object):    def __init__(self, options, log):      ...    def compile_pattern(self):      ...    def set_filename(self, filename):      ...    def match(self, node):      ...      def transform(self, node, results):      ...    def new_name(self, template="xxx_todo_changeme"):      ...    def log_message(self, message):      ...    def cannot_convert(self, node, reason=None):      ...    def warning(self, node, reason):      ...    def start_tree(self, tree, filename):      ...    def finish_tree(self, tree, filename):      ...

Each fix generally has a pattern used to match specific nodes. When a match is found the fix calls the transform() method, which transforms the tree and returns the results. The set_filename() and log_message() methods are used for reporting. The cannot_convert() and warning() methods detect potential problems. The start_tree() and finish_tree() methods get called at the beginning and end of an entire tree fix for fixers that need full tree context.

Here’s a closer look at one of the simpler fixers: fix_getcwdu. The getcwdu() function in Python 2.x returns the current working directory as a Unicode string (as opposed to the getcwd() function that returns the current working directory as an ASCII string). In Python 3 all strings are Unicode, so getcwdu() can simply become getcwd(). The fixer is called fix_getcwdu. It contains a class called FixGetcwdu that subclasses BaseFix:

"""Fixer that changes os.getcwdu() to os.getcwd()."""# Author: Victor Stinner# Local importsfrom .. import fixer_basefrom ..fixer_util import Nameclass FixGetcwdu(fixer_base.BaseFix):    PATTERN = """              power< 'os' trailer< dot='.' name='getcwdu' > any* >              """    def transform(self, node, results):        name = results["name"]        name.replace(Name("getcwd", prefix=name.get_prefix()))

The implementation is concise because it takes advantage of much of the 2to3 infrastructure. It defines only a PATTERN, and overrides the transform() method of the BaseFix. The PATTERN is simple, but still I’m not clear about the syntax all the parts. It seems to represent os.getcwdu (where the os. part is probably optional).

Implement Custom Fixers

Armed with all the knowledge about 2to3 and this simple fixer, here’s a new 2to3 fixer implementation and instructions to integrate it into 2to3. The fix in this case is for the file() --> open() issue, which 2to3 stumbled on earlier in this article.

The first step is to implement a module that contains a FixFile class. The only part that needs explaining is the PATTERN. The effect of this pattern is to cause 2to3 to look for any invocation of a function named file with an arbitrary number of arguments.

"""Fixer that changes file() to open()."""# Author: Gigi Sayfan# Local importsfrom .. import fixer_basefrom ..fixer_util import Nameclass FixFile(fixer_base.BaseFix):    PATTERN = """              power<                 (name='file') trailer< '(' args=any ')' >              rest=any* >              """    def transform(self, node, results):        name = results["name"]        name.replace(Name("open", prefix=name.get_prefix()))

Save the file in the fixes subdirectory. 2to3 should automatically discover the new fixer. To check, see if the new fixer shows up in the fixer list:

> 2to3 -l | grep fileOutput:execfilefile

As you can see, it did. The next step is to see if the file fixer actually works. Here’s a small test program that uses the file() function:

file('test.txt', 'w').write('Yeah, it works!')s = file('test.txt', 'r').read()print(s)assert s == 'Yeah, it works!'file = 8print(file)print('file()')

The program also uses “file” as a variable (this is OK in Python, but not recommended because it hides the original file() function). Finally it prints the string “file()” for good measure. The test program runs successfully under Python 2, but fails with the following error under Python 3.

> py3 Traceback (most recent call last):  File "", line 1, in     file('test.txt', 'w').write('Yeah, it works!')NameError: name 'file' is not defined

The original version of 2to3 silently ignores the file function as demonstrated earlier. But if you run the version of 2to3 with the new file fixer in place, you get:

> 2to3 RefactoringTool: Skipping implicit fixer: bufferRefactoringTool: Skipping implicit fixer: idiomsRefactoringTool: Skipping implicit fixer: set_literalRefactoringTool: Skipping implicit fixer: ws_comma--- (original)+++ (refactored)@@ -1,4 +1,4 @@-file('test.txt', 'w').write('Yeah, it works!')-s = file('test.txt', 'r').read()+open('test.txt', 'w').write('Yeah, it works!')+s = open('test.txt', 'r').read() print(s) assert s == 'Yeah, it works!'RefactoringTool: Files that need to be modified:RefactoringTool:

Yes, it works! By adding the additional fixer, 2to3 can now detect and replace file() calls with open() calls. Note that the file variable and the “file()” string were not replaced because 2to3 is not doing simple string substitution. This is exactly the reason behind actually parsing the Python grammar and building a Python AST tree.

Preparing for Python 3 in a Python 2 World

Many people may find that they can’t migrate right away, either because of Python 3’s current state, or for backward compatibility reasons—but they may still want to be prepared. Migration occurs in two steps: First, migrate to Python 2.6 (should be a snap) and then start refactoring toward Python 3. Python 2.6 comes with a module called future_builtins. This module lets you use some Python 3 built-ins in your Python 2 code. The 2to3 tool recognizes the built-ins, and will leave them alone (because they already follow the Python 3 behavior). The defined built-ins are: ascii, filter, hex, map, oct, and zip.

Python 2.6 also contains the __future__ module, which lets you import even more Python 3 syntax and semantics: absolute_import, division, generators, nested scopes, Unicode literals, and the with statement.

The following short code snippet shows division behavior with and without the __future__ division under Python 2.6:

>>> 5 / 22>>> 5 // 22>>> from __future__ import division>>> 5 / 22.5>>> 5 // 22

Writing Code that Runs Under Python 2.x and Python 3

Before you try, I’ll warn you: It is possible, but not worth the effort in most cases. You will have to resort to strange idioms (especially for exception handling) and limit yourself to a subset of features and third-party packages that are available to both versions. You will also need to test constantly under both Python 2 and Python 3 to make sure you didn’t use a “forbidden” feature or dependency.

One Google “summer of code” project called 3to2 has the goal of allowing you to write Python 3 code and then transform it to Python 2.5 (or 2.6) code. If it takes off you may have yet another option.

In this article, you saw an overview of Python 2.6 and the process of migrating from Python 2.x to Python 3 using various scenarios, taking the current state of Python 3 third-party packages and various migration paths into account. Finally, you’ve seen a best-practice migration process, including a full example that migrated a sample program through all the phases—and even included an example of how to build a custom fixer for the 2to3 tool.

This article also completes the Developer’s Guide to Python 3.0 series. In the earlier articles (see the Related Resources in the left column of this article) you can find a thorough introduction to Python 3, both the language and the standard library, an explanation of the rationale and the process that arrived at Python 3, and a deep exploration of every major PEP (and many minor ones), with working code.

Python 3 is the future of Python, so I hope you begin exploring and using it sooner rather than later.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist