Welcome to our website.

Python 2.7 and Python 3: The Compatibility Changes That Matter

Choosing between Python 2 and Python 3 is one of the first questions many beginners encounter. For someone following a tutorial, the practical answer is usually simple: use the version used by that tutorial. Once the fundamentals are familiar, the differences are much easier to understand.

For a new project, the choice has historically been less restrictive because many Python libraries supported both Python 2.7.x and Python 3.x. Even so, understanding the differences is important. It helps prevent subtle bugs, makes version-specific behavior easier to recognize, and is especially useful when porting an existing project.

Using __future__ to adopt newer behavior

Python 3 introduced several language changes that are incompatible with Python 2. In many cases, Python 2 can enable the newer behavior through the __future__ module. This is useful when writing Python 2 code that should be easier to move to Python 3 later.

For example, Python 2 can use Python 3-style division with:

from __future__ import division

The main features provided through __future__ include:

<table> <thead> <tr> <th>Feature</th> <th>Optional starting version</th> <th>Mandatory starting version</th> <th>Effect</th> </tr> </thead> <tbody> <tr> <td>nested_scopes</td> <td>2.1.0b1</td> <td>2.2</td> <td>PEP 227: Statically Nested Scopes</td> </tr> <tr> <td>generators</td> <td>2.2.0a1</td> <td>2.3</td> <td>PEP 255: Simple Generators</td> </tr> <tr> <td>division</td> <td>2.2.0a2</td> <td>3.0</td> <td>PEP 238: Changing the Division Operator</td> </tr> <tr> <td>absolute_import</td> <td>2.5.0a1</td> <td>3.0</td> <td>PEP 328: Imports: Multi-Line and Absolute/Relative</td> </tr> <tr> <td>with_statement</td> <td>2.5.0a1</td> <td>2.6</td> <td>PEP 343: The “with” Statement</td> </tr> <tr> <td>print_function</td> <td>2.6.0a2</td> <td>3.0</td> <td>PEP 3105: Make print a function</td> </tr> <tr> <td>unicode_literals</td> <td>2.6.0a2</td> <td>3.0</td> <td>PEP 3112: Bytes literals in Python 3000</td> </tr> </tbody> </table>

print: a statement in Python 2, a function in Python 3

One of the most visible syntax changes is the replacement of Python 2's print statement with the print() function in Python 3. Parentheses are therefore required in Python 3.

Python 2 also accepts parentheses, but they do not necessarily mean that print is being called as a function. In some cases, the expression inside the parentheses becomes a tuple. The reverse form—calling print without parentheses—raises a SyntaxError in Python 3.

Python 2

print 'Python', python_version()
print 'Hello, World!'
print('Hello, World!')
print "text", ; print 'print more text on the same line'

Output:

Python 2.7.6
Hello, World!
Hello, World!
text print more text on the same line

Python 3

print('Python', python_version())
print('Hello, World!')
print("some text,", end="")
print(' print more text on the same line')

Output:

Python 3.4.1
Hello, World!
some text, print more text on the same line

This Python 2 example demonstrates the tuple issue:

print 'Python', python_version()
print('a', 'b')
print 'a', 'b'
Python 2.7.7
('a', 'b')
a b

The second line prints a tuple, while the third line uses the Python 2 print statement to print two separate objects.

Division of integers

Division is one of the easiest differences to overlook because using the wrong operator does not produce a syntax error.

In Python 2, dividing two integers with / performs integer division. In Python 3, / produces a floating-point result, while // remains floor division.

Python 2

print 'Python', python_version()
print '3 / 2 =', 3 / 2
print '3 // 2 =', 3 // 2
print '3 / 2.0 =', 3 / 2.0
print '3 // 2.0 =', 3 // 2.0

Output:

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

Python 3

print('Python', python_version())
print('3 / 2 =', 3 / 2)
print('3 // 2 =', 3 // 2)
print('3 / 2.0 =', 3 / 2.0)
print('3 // 2.0 =', 3 // 2.0)

Output:

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

When code needs to run across both versions, it is safer to be explicit. A Python 3 script can use float(3) / 2 or 3 / 2.0, while Python 2 code can import the Python 3 behavior with:

from __future__ import division

Text, Unicode, and binary data

Python 2 has an ASCII-oriented str type and a separate unicode type. The unicode() function can be used to create Unicode text, but Python 2 does not have a distinct bytes type—its str type is commonly used for byte data.

Python 3 separates text from binary data more clearly. Ordinary strings are Unicode text, while bytes and bytearray represent binary data.

Python 2

print 'Python', python_version()
print type(unicode('this is like a python3 str type'))
print type(b'byte type does not exist')
print 'they are really' + b' the same'
print type(bytearray(b'bytearray oddly does exist though'))

Output:

Python 2.7.6
<type 'unicode'>
<type 'str'>
they are really the same
<type 'bytearray'>

Python 3

print('Python', python_version())
print('strings are now utf-8 u03BCnicou0394é!')
Python 3.4.1
strings are now utf-8 μnicoΔé!
print('Python', python_version(), end="")
print(' has', type(b' bytes for storing data'))
Python 3.4.1 has <class 'bytes'>
print('and Python', python_version(), end="")
print(' also has', type(bytearray(b'bytearrays')))
and Python 3.4.1 also has <class 'bytearray'>
'note that we cannot add a string' + b'bytes for data'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-13-d3e8942ccf81> in <module>()
----> 1 'note that we cannot add a string' + b'bytes for data'

TypeError: Can't convert 'bytes' object to str implicitly

The important practical rule is that text and binary values cannot be freely combined in Python 3. A string must not be concatenated directly with a bytes object; the data needs to be decoded or encoded deliberately.

xrange() becomes range()

Python 2 commonly uses xrange() when a loop needs a lazy, iterable sequence of numbers. It behaves much like a generator: values are produced as needed instead of requiring a complete list to be created in memory first.

In Python 3, range() takes over this role. There is no separate xrange() function, so calling xrange() in Python 3 raises a NameError.

The intended comparison can be illustrated with these functions:

import timeit

n = 10000
def test_range(n):
    return for i in range(n):
    pass

def test_xrange(n):
    for i in xrange(n):
    pass

In Python 2, the corresponding benchmark showed xrange() using less time than range() in that test:

Python 2.7.6

timing range()
1000 loops, best of 3: 433 µs per loop

timing xrange()
1000 loops, best of 3: 350 µs per loop

In Python 3, range() is the lazy sequence type:

print('Python', python_version())

print('\ntiming range()')
%timeit test_range(n)
Python 3.4.1

print(xrange(10))
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
in ()
----> 1 print(xrange(10))

NameError: name 'xrange' is not defined

The difference in the benchmark is not evidence that Python 3's range() and Python 2's xrange() use fundamentally different strategies. Their sequence behavior is essentially the same; the observed timing difference in the example is attributed to the overall speed difference between the two Python versions.

Membership checks on range

Python 3's range type also provides a __contains__ method. For integer membership checks, this allows Python to determine whether a value belongs to the range without scanning every value.

x = 10000000
def val_in_range(x, val):
    return val in range(x)

print('Python', python_version())
assert(val_in_range(x, x/2) == True)
assert(val_in_range(x, x//2) == True)
%timeit val_in_range(x, x/2)
%timeit val_in_range(x, x//2)

The example produced results like these in Python 3:

Python 3.4.1
1 loops, best of 3: 742 ms per loop
1000000 loops, best of 3: 1.19 µs per loop

The integer lookup was roughly 60,000 times faster than the floating-point lookup in that measurement. The distinction matters because the integer path can use the arithmetic structure of the range, while a floating-point value does not receive the same optimization.

Python 2's range and xrange did not provide __contains__ in the same way, so the difference between integer and floating-point membership tests was much smaller in the example:

print 'Python', python_version()

assert(val_in_xrange(x, x/2.0) == True)
assert(val_in_xrange(x, x/2) == True)
assert(val_in_range(x, x/2) == True)
assert(val_in_range(x, x//2) == True)
%timeit val_in_xrange(x, x/2.0)
%timeit val_in_xrange(x, x/2)
%timeit val_in_range(x, x/2.0)
%timeit val_in_range(x, x/2)
Python 2.7.7
1 loops, best of 3: 285 ms per loop
1 loops, best of 3: 179 ms per loop
1 loops, best of 3: 658 ms per loop
1 loops, best of 3: 556 ms per loop

The presence of __contains__ can be inspected directly in Python 3:

print('Python', python_version())
range.__contains__
Python 3.4.1
<slot wrapper '__contains__' of 'range' objects

In Python 2, the same lookup fails:

print('Python', python_version())
range.__contains__
Python 2.7.7
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-7-05327350dafb> in <module>()
1 print 'Python', python_version()
----> 2 range.__contains__

AttributeError: 'builtin_function_or_method' object has no attribute '__contains__'
print('Python', python_version())
xrange.__contains__
Python 2.7.7

---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
in ()
1 print('Python', python_version())
----> 2 xrange.__contains__

AttributeError: type object 'xrange' has no attribute '__contains__'

Raising exceptions

Python 2 accepts both the old comma-based exception syntax and the newer callable form. Python 3 accepts only the form that constructs an exception object.

Python 2

print 'Python', python_version()
Python 2.7.6
raise IOError, "file error"
---------------------------------------------------------------------------
IOError Traceback (most recent call last)
<ipython-input-8-25f049caebb0> in <module>()
----> 1 raise IOError, "file error"

IOError: file error
raise IOError("file error")
---------------------------------------------------------------------------
IOError Traceback (most recent call last)
<ipython-input-9-6f1c43f525b2> in <module>()
----> 1 raise IOError("file error")

IOError: file error

Python 3

The comma form is invalid in Python 3:

print('Python', python_version())
Python 3.4.1
raise IOError, "file error"
File "<ipython-input-10-25f049caebb0>", line 1
raise IOError, "file error"
^
SyntaxError: invalid syntax

The compatible form is:

print('Python', python_version())
raise IOError("file error")

In the Python 3 example, IOError appears as OSError in the traceback:

Python 3.4.1
OSError: file error

Catching exceptions with as

Exception binding also changed. Python 2 allows a comma between the exception type and the variable receiving the exception. Python 3 requires the as keyword.

Python 2

print 'Python', python_version()
try:
    let_us_cause_a_NameError
except NameError, err:
    print err, '--> our error message'
Python 2.7.6
name 'let_us_cause_a_NameError' is not defined --> our error message

Python 3

print('Python', python_version())
try:
    let_us_cause_a_NameError
except NameError as err:
    print(err, '--> our error message')
Python 3.4.1
name 'let_us_cause_a_NameError' is not defined --> our error message

When porting code, except SomeError, err: must therefore be changed to except SomeError as err:.

next() and the iterator method

Python 2.7 supports both the built-in next() function and the iterator's .next() method. Python 3 standardizes on the built-in function; generator objects no longer expose .next().

Python 2

print 'Python', python_version()
my_generator = (letter for letter in 'abcdefg')
next(my_generator)
my_generator.next()
Python 2.7.6
'b'

Python 3

print('Python', python_version())
my_generator = (letter for letter in 'abcdefg')
next(my_generator)
Python 3.4.1
'a'
my_generator.next()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-14-125f388bb61b> in <module>()
----> 1 my_generator.next()

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

Use next(my_generator) when code is intended for Python 3.

Comprehension variables no longer leak

Python 2 allows the loop variable used in a list comprehension to remain in the surrounding namespace. Python 3 isolates that variable, so it does not overwrite a name outside the comprehension.

Python 2

print 'Python', python_version()

i = 1
print 'before: i =', i

print 'comprehension: ', [i for i in range(5)]

print 'after: i =', i
Python 2.7.6
before: i = 1
comprehension: [0, 1, 2, 3, 4]
after: i = 4

Python 3

print('Python', python_version())

i = 1
print('before: i =', i)

print('comprehension:', [i for i in range(5)])

print('after: i =', i)
Python 3.4.1
before: i = 1
comprehension: [0, 1, 2, 3, 4]
after: i = 1

This is particularly useful when a comprehension uses a variable name that is also needed later in the same scope.

Comparing unrelated types

Python 2 permits ordering comparisons between values of unrelated types, even when the result has no useful semantic meaning. For example, lists and strings can be compared, and Python 2 returns an ordering result.

print 'Python', python_version()
print "[1, 2] > 'foo' = ", [1, 2] > 'foo'
print "(1, 2) > 'foo' = ", (1, 2) > 'foo'
print "[1, 2] > (1, 2) = ", [1, 2] > (1, 2)
Python 2.7.6
[1, 2] > 'foo' = False
(1, 2) > 'foo' = True
[1, 2] > (1, 2) = False

Python 3 rejects these comparisons with TypeError instead of applying an arbitrary ordering:

print('Python', python_version())
print("[1, 2] > 'foo' = ", [1, 2] > 'foo')
print("(1, 2) > 'foo' = ", (1, 2) > 'foo')
print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2))
Python 3.4.1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-16-a9031729f4a0> in <module>()
1 print('Python', python_version())
----> 2 print("[1, 2] > 'foo' = ", [1, 2] > 'foo')
3 print("(1, 2) > 'foo' = ", (1, 2) > 'foo')
4 print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2))
TypeError: unorderable types: list() > str()

Although this may expose errors during migration, the exception is generally preferable to silently producing an arbitrary result.

Reading user input

Python 2 has two input functions with significantly different behavior. input() evaluates the entered text, which means entering 123 produces an integer and can also create unsafe behavior when the input is treated as Python code. raw_input() returns the input as a string.

Python 2.7.6
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

>>> my_input = input('enter a number: ')

enter a number: 123

>>> type(my_input)
<type 'int'>

>>> my_input = raw_input('enter a number: ')

enter a number: 123

>>> type(my_input)
<type 'str'>

Python 3 simplifies this distinction: input() always returns a string.

Python 3.4.1
[GCC 4.2.1 (Apple Inc. build 5577)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

>>> my_input = input('enter a number: ')
enter a number: 123
>>> type(my_input)
<class 'str'>

If numeric input is required in Python 3, it must be converted explicitly, for example with int() or float().

More functions return iterables instead of lists

Python 3 changes the return type of several operations that produced lists in Python 2. The result is often a lazy, iterable object. This generally saves memory when the values will only be traversed once, although repeatedly traversing a generator-like object may not be appropriate for every use case.

If a real list is needed, the iterable can be materialized with list().

Python 2

print 'Python', python_version()

print range(3)
print type(range(3))
Python 2.7.6
[0, 1, 2]
<type 'list'>

Python 3

print('Python', python_version())
print(range(3))
print(type(range(3)))
print(list(range(3)))
Python 3.4.1
range(0, 3)
<class 'range'>
[0, 1, 2]

Besides range(), commonly encountered changes include:

  1. zip()
  2. map()
  3. filter()
  4. Dictionary keys()
  5. Dictionary values()
  6. Dictionary items()

Code that expects indexing, repeated traversal, or list-specific operations may need an explicit conversion such as list(zip(...)) or list(dictionary.keys()).

These differences are not the entire Python 2-to-Python 3 migration story, but they cover many of the language behaviors most likely to cause confusion. Paying attention to syntax, division, text handling, lazy iterables, exception syntax, and changed built-in behavior can prevent a large class of porting errors.

Related Posts