Sebastian Raschka
last updated: 05/24/2014
This is a subsection of "A collection of not-so-obvious Python stuff you should know!"
There are some good articles already that are summarizing the differences between Python 2 and 3, e.g.,
- https://wiki.python.org/moin/Python2orPython3
- https://docs.python.org/3.0/whatsnew/3.0.html
- http://python3porting.com/differences.html
- https://docs.python.org/3/howto/pyporting.html
etc.
But it might be still worthwhile, especially for Python newcomers, to take a look at some of those! (Note: the the code was executed in Python 3.4.0 and Python 2.7.5 and copied from interactive shell sessions.)
[back to Python 2.x vs 3.x overview]
We have ASCII str()
types, separate unicode()
, but no byte
type
Now, we finally have Unicode (utf-8) str
ings, and 2 byte classes: byte
and bytearray
s
############# # Python 2 ############# >>> type(unicode('is like a python3 str()')) <type 'unicode'> >>> type(b'byte type does not exist') <type 'str'> >>> 'they are really' + b' the same' 'they are really the same' >>> type(bytearray(b'bytearray oddly does exist though')) <type 'bytearray'> ############# # Python 3 ############# >>> print('strings are now utf-8 \u03BCnico\u0394é!') strings are now utf-8 μnicoΔé! >>> type(b' and we have byte types for storing data') <class 'bytes'> >>> type(bytearray(b'but also bytearrays for those who prefer them over strings')) <class 'bytearray'> >>> 'string' + b'bytes for data' Traceback (most recent call last):s File "<stdin>", line 1, in <module> TypeError: Can't convert 'bytes' object to str implicitly
[back to Python 2.x vs 3.x overview]
Very trivial, but this change makes sense, Python 3 now only accepts print
s with proper parentheses - just like the other function calls ...
# Python 2 >>> print 'Hello, World!' Hello, World! >>> print('Hello, World!') Hello, World! # Python 3 >>> print('Hello, World!') Hello, World! >>> print 'Hello, World!' File "<stdin>", line 1 print 'Hello, World!' ^ SyntaxError: invalid syntax
And if we want to print the output of 2 consecutive print functions on the same line, you would use a comma in Python 2, and a end=""
in Python 3:
# Python 2 >>> print "line 1", ; print 'same line' line 1 same line # Python 3 >>> print("line 1", end="") ; print (" same line") line 1 same line
[back to Python 2.x vs 3.x overview]
This is a pretty dangerous thing if you are porting code, or executing Python 3 code in Python 2 since the change in integer-division behavior can often go unnoticed.
So, I still tend to use a float(3)/2
or 3/2.0
instead of a 3/2
in my Python 3 scripts to save the Python 2 guys some trouble ... (PS: and vice versa, you can from __future__ import division
in your Python 2 scripts).
# Python 2 >>> 3 / 2 1 >>> 3 // 2 1 >>> 3 / 2.0 1.5 >>> 3 // 2.0 1.0 # Python 3 >>> 3 / 2 1.5 >>> 3 // 2 1 >>> 3 / 2.0 1.5 >>> 3 // 2.0 1.0
xrange()
[back to Python 2.x vs 3.x overview]
xrange()
was pretty popular in Python 2.x if you wanted to create an iterable object. The behavior was quite similar to a generator ('lazy evaluation'), but you could iterate over it infinitely. The advantage was that it was generally faster than range()
(e.g., in a for-loop) - not if you had to iterate over the list multiple times, since the generation happens every time from scratch!
In Python 3, the range()
was implemented like the xrange()
function so that a dedicated xrange()
function does not exist anymore.
# Python 2 > python -m timeit 'for i in range(1000000):' ' pass' 10 loops, best of 3: 66 msec per loop > python -m timeit 'for i in xrange(1000000):' ' pass' 10 loops, best of 3: 27.8 msec per loop # Python 3 > python3 -m timeit 'for i in range(1000000):' ' pass' 10 loops, best of 3: 51.1 msec per loop > python3 -m timeit 'for i in xrange(1000000):' ' pass' Traceback (most recent call last): File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/timeit.py", line 292, in main x = t.timeit(number) File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/timeit.py", line 178, in timeit timing = self.inner(it, self.timer) File "<timeit-src>", line 6, in inner for i in xrange(1000000): NameError: name 'xrange' is not defined
[back to Python 2.x vs 3.x overview]
Where Python 2 accepts both notations, the 'old' and the 'new' way, Python 3 chokes (and raises a SyntaxError
in turn) if we don't enclose the exception argument in parentheses:
# Python 2 >>> raise IOError, "file error" Traceback (most recent call last): File "<stdin>", line 1, in <module> IOError: file error >>> raise IOError("file error") Traceback (most recent call last): File "<stdin>", line 1, in <module> IOError: file error # Python 3 >>> raise IOError, "file error" File "<stdin>", line 1 raise IOError, "file error" ^ SyntaxError: invalid syntax >>> raise IOError("file error") Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: file error
[back to Python 2.x vs 3.x overview]
Also the handling of exceptions has slightly changed in Python 3. Now, we have to use the as
keyword!
# Python 2 >>> try: ... blabla ... except NameError, err: ... print err, '--> our error msg' ... name 'blabla' is not defined --> our error msg # Python 3 >>> try: ... blabla ... except NameError as err: ... print(err, '--> our error msg') ... name 'blabla' is not defined --> our error msg
next()
function and .next()
method[back to Python 2.x vs 3.x overview]
Where you can use both function and method in Python 2.7.5, the next()
function is all that remain in Python 3!
# Python 2 >>> my_generator = (letter for letter in 'abcdefg') >>> my_generator.next() 'a' >>> next(my_generator) 'b' # Python 3 >>> my_generator = (letter for letter in 'abcdefg') >>> next(my_generator) 'a' >>> my_generator.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'generator' object has no attribute 'next'
[back to Python 2.x vs 3.x overview]
This goes back to a change that was made in Python 3.x and is described in What’s New In Python 3.0 as follows:
"List comprehensions no longer support the syntactic form [... for var in item1, item2, ...]
. Use [... for var in (item1, item2, ...)]
instead. Also note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a list()
constructor, and in particular the loop control variables are no longer leaked into the surrounding scope."
[In:]
from platform import python_version print('This code cell was executed in Python', python_version()) i = 1 print([i for i in range(5)]) print(i, '-> i in global')
[Out:]
This code cell was executed in Python 3.3.5 [0, 1, 2, 3, 4] 1 -> i in global
[In:]
from platform import python_version print 'This code cell was executed in Python', python_version() i = 1 print [i for i in range(5)] print i, '-> i in global'
[Out:]
This code cell was executed in Python 2.7.6 [0, 1, 2, 3, 4] 4 -> i in global
[back to Python 2.x vs 3.x overview]
[In:]
from platform import python_version print 'This code cell was executed in Python', python_version() print [1, 2] > 'foo' print (1, 2) > 'foo' print [1, 2] > (1, 2)
[Out:]
This code cell was executed in Python 2.7.6 False True False
[In:]
from platform import python_version print('This code cell was executed in Python', python_version()) print([1, 2] > 'foo') print((1, 2) > 'foo') print([1, 2] > (1, 2))
[Out:]
This code cell was executed in Python 3.3.5 --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-3-1d774c677f73> in <module>() 2 print('This code cell was executed in Python', python_version()) 3 ----> 4 [1, 2] > 'foo' 5 (1, 2) > 'foo' 6 [1, 2] > (1, 2) TypeError: unorderable types: list() > str()