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