Summary of important differences between the 2.7.x and 3.x versions of Python

  • 2020-04-02 14:28:12
  • OfStack

Many Python beginners ask: which version of Python should I learn? My usual answer to this question is "choose the Python tutorial that works best for you, and use whichever version of Python is used in the tutorial. When you're done, study the differences between the versions.

But if you want to develop a new project in Python, how do you choose the Python version? I can safely say that most Python libraries support versions 2.7.x and 3.x of Python, so whichever version you choose is fine. But in order to avoid some of the pitfalls common in some versions of Python when using Python, or to port a Python project, it's still important to understand the major differences between the two common versions of Python.

directory

(link: #) (link: #) (link: #) (link: #) (link: #) (link: #) (link: #) (link: #) (link: #) (link: #) (link: #) (link: #) (link: #)

__future__ module

"(link: #)"

Python 3.x introduces some keywords and features that are not compatible with Python 2. In Python 2, you can import the new content with the built-in module of s/s. If the code you wish to write in Python 2 can also be run in Python 3.x, then it is recommended to use the s/s module. For example, if you want to have the integer division behavior of Python 3.x in Python 2, you can import the corresponding module with the following statement.


from __future__ import division

The following table lists the other imported features in successive packages:

features Optional version Mandatory version The effect
nested_scopes 2.1.0b1 2.2 PEP 227:
Statically Nested Scopes
generators 2.2.0a1 2.3 PEP 255:
Simple Generators
division 2.2.0a2 3.0 PEP 238:
Changing the Division Operator
absolute_import 2.5.0a1 3.0 PEP 328:
Imports: Multi-Line and Absolute/Relative
with_statement 2.5.0a1 2.6 PEP 343:
The " with " Statement
print_function 2.6.0a2 3.0 PEP 3105:
Make print a function
unicode_literals 2.6.0a2 3.0 PEP 3112:
Bytes literals in Python 3000

(source: (link: https://docs.python.org/2/library/__future__.html#module-__future__)

Example:


from platform import python_version

The print function

"(link: #)"

While the print syntax is a minor change in Python 3 and should be well known, it's worth mentioning that the print statement in Python 2 was replaced by the print() function in Python 3, which means that in Python 3 you must enclose the object you want to output in parentheses.

It is also possible to use additional parentheses in Python 2. Conversely, SyntaxError is triggered in Python 3 when you want to call the print function in Python2 without parentheses.

Python 2


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

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')

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

print 'Hello, World!'

File "<ipython-input-3-139a7c5835bd>", line 1
print 'Hello, World!'
^
SyntaxError: invalid syntax

Note:

In Python, it is normal to output "Hello World" with or without parentheses. But if you output multiple objects in parentheses at the same time, you create a tuple, because in Python 2, print is a statement, not a function call.


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

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

Integer division

"(link: #)"

Because the change in integer division in Python 3 is often overlooked (an Error does not trigger Syntax Error), it is important to pay particular attention to this change when porting code or executing Python 3 code in Python 2.

Therefore, I will still try to replace 3/2 with float(3)/2 or 3/2.0 in Python 3 scripts to avoid errors that my code might cause in Python 2 environments (or, conversely, use the Python 3 division from successive future__ import division in Python 2 scripts).

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

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)

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

Unicode

"(link: #)"

Python 2 has an ASCII based STR () type, which can be converted to a unicode type by a separate unicode() function, but not a byte type.

In Python 3, you finally have Unicode (utf-8) strings, and two byte classes: bytes and bytearrays.

Python 2


print 'Python', python_version()

Python 2.7.6

print type(unicode('this is like a python3 str type'))

<type 'unicode'>

print type(b'byte type does not exist')

<type 'str'>

print 'they are really' + b' the same'

they are really the same

print type(bytearray(b'bytearray oddly does exist though'))

<type 'bytearray'>

Python 3


print('Python', python_version())
print('strings are now utf-8 u03BCnicou0394 e !')

Python 3.4.1
strings are now utf-8  mu nico Δ e !

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

xrange

"(link: #)"

In Python 2.x, it is common to create an iterable object with xrange(), usually in a "for loop" or "list/collection/dictionary derivation."

This behavior is very similar to a generator (such as "lazy evaluation"), but the xrange-iterable infinite here means that it is possible to iterate indefinitely over this xrange.

Because of xrange's "lazy curiosity" nature, range() is usually faster than xrange() if you only need to iterate once (as in the for loop). However, it is not recommended to use range() in multiple iterations, because range() regenerates a list in memory each time.

In Python 3, range() is implemented the same way as the xrange() function, so there is no dedicated xrange() (using xrange() in Python 3 triggers a NameError).


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

Python 2


print 'Python', python_version()
 
print 'ntiming range()'
%timeit test_range(n)
 
print 'nntiming xrange()'
%timeit test_xrange(n)

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

Python 3


print('Python', python_version())
 
print('ntiming range()')
%timeit test_range(n)

Python 3.4.1
 
timing range()
1000 loops, best of 3: 520 µs per loop

print(xrange(10))

---------------------------------------------------------------------------
NameError Traceback (most recent call last)
in ()
----> 1 print(xrange(10))
 
NameError: name 'xrange' is not defined

Method with a range object in Python 3

Also worth mentioning is that in Python 3.x, range has a new method with contains__. This method can effectively speed up the "find" of integers and bores in Python 3.x.


x = 10000000
def val_in_range(x, val):
 return val in range(x)
 
def val_in_xrange(x, val):
 return val in xrange(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)

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

According to the timeit result above, finding integers is about 60,000 times faster than finding floating point Numbers. But since range or xrange in Python 2.x has no method with contains__, there is not much difference in the speed of finding integers and floating points in Python 2.


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 following code proves that there is no method in Python 2.x:


print('Python', python_version())
range.__contains__

Python 3.4.1
<slot wrapper '__contains__' of 'range' objects

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__'

A note on the speed difference between xrange() in Python 2 and range() in Python 3:

One reader pointed out the difference in execution speed between range() in Python 3 and xrange() in Python 2. Since both are implemented the same way, the execution speed should theoretically be the same. The speed difference here is simply because the overall speed of Python 3 is slower than that of Python 2.


def test_while():
 i = 0
 while i < 20000:
  i += 1
 return

print('Python', python_version())
%timeit test_while()

Python 3.4.1
%timeit test_while()
100 loops, best of 3: 2.68 ms per loop

print 'Python', python_version()
%timeit test_while()

Python 2.7.6
1000 loops, best of 3: 1.72 ms per loop

An exception

"(link: #)"

Python 2 supports both the old and the new exception firing syntax, while Python 3 accepts only bracketed syntax (otherwise SyntaxError will be triggered) :

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


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 proper way to raise an exception in Python 3:

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

Python 3.4.1
 
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
<ipython-input-11-c350544d15da> in <module>()
1 print('Python', python_version())
----> 2 raise IOError("file error")
 
OSError: file error

Exception handling

"(link: #)"

Exception handling in Python 3 has also changed a bit. You must use the "as" keyword in Python 3.

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

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

"(link: #)"

Since the next() (.next()) function (method) is used a lot, there is one other syntax change (implementation change) : in Python 2.7.5, both the function form and the method form are available, while in Python 3, only the next() function (an attempt to call the.next() method triggers an AttributeError) is used.

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'

For loop variables and global namespace leaks

"(link: #)"

The good news is that in Python 3.x, variables in the for loop no longer leak into the global namespace!

This is a change made to Python 3.x, described In "What's New In Python 3.0" as follows:

"List derivation no longer supports [... for var in item1, item2...] For this syntax, use [... for var in (item1, item2,...)] Instead. Note also that list derivation has different semantics: list derivation is now closer to the syntactic sugar of generator expressions in the list() constructor, and in particular, loop control variables are no longer leaking into the space around the loop.

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

Comparative unordered type

"(link: #)"

Another nice change in Python 3 is that if we try to compare unordered types, a TypeError is triggered.

Python 2


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


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()

The user's input is parsed through input()

"(link: #)"

Fortunately, Python 3 improved the input() function so that it always stores the user's input as a STR object. In Python 2, you have to use raw_input() instead of input() to avoid some of the dangerous behavior that can occur when reading non-string types.

Python 2


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


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'>

Returns an iterable object instead of a list

"(link: #)"

As you can see in the xrange section, some functions and methods return iterable objects in Python, not lists as in Python 2.

Because these objects are typically traversed only once, this saves a lot of memory. However, it is not efficient to iterate over these objects multiple times through a generator.

At this point we really need a list object, and we can simply turn the iterable object into a list using the list() function.

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]

Here is a list of other common Python 3 functions and methods that no longer return lists:

Zip () The map () The filter () The dictionary's.key() method The dictionary's.value() method The dictionary's.item() method

More on Python 2 and Python 3

"(link: #)"

Here are some other great articles to learn more about Python 2 and Python 3,

// migrate to Python 3

(link: https://wiki.python.org/moin/Python2orPython3) (link: https://docs.python.org/3.0/whatsnew/3.0.html) (link: http://python3porting.com/differences.html) (link: https://docs.python.org/3/howto/pyporting.html) (link: http://nothingbutsnark.svbtle.com/my-view-on-the-current-state-of-python-3)

// praise and criticism of Python 3

(link: http://asmeurer.github.io/python3-presentation/slides.html#1) (link: #) (link: #) (link: #) (link: http://sealedabstract.com/rants/python-3-is-fine/)

Related articles: