Let’s explore the advantages Python 3 has to offer over Python 2!
Table of Contents
Print is no longer a statement but a built-in function
In Python 3, print became a function call instead of a statement. Some of the advantages of this change in Python 3:
- There’s really no reason for print to be a statement. It’s more consistent if print is a function.
- Since print is a function, it can be passed as an argument to functions that expect a function. For example, take a function that requires another function to further process your data as an argument. For simple mocking/debugging, you can now also pass the print() function.
- You can use print like this now because it is a function:
[print(x) for x in range(10)]
- You can override the print function by assigning to
builtins.print
, whereas you can’t do that with a statement.
Unicode in Python 3
Another big Python 3 advantage is that every string, by default, is a Unicode string. In Python 2, a string defaults to an ASCII string, limiting the range of characters it can handle. If you wanted a Unicode string, you had to create one like this explicitly:
# Python 2 unicode_string = u'Ümlaut? Nō prōblem!' # Python 3 unicode_string = 'Ümlaut? Nō prōblem!'
This is a must-have for many countries.
Data classes
Since version 3.7, which is fairly recent, Python offers data classes. There are several advantages over regular classes or other alternatives, like returning multiple values or dictionaries:
- A data class requires a minimal amount of code.
- You can compare data classes because
__eq__
is implemented for you. - You can easily print a data class for debugging because
__repr__
is implemented as well. - Data classes require type hints, reducing the chances of bugs.
Here’s an example of a data class at work:
from dataclasses import dataclass @dataclass class Card: rank: str suit: str card = Card("Q", "hearts") print(card == card) # True print(card.rank) # 'Q' print(card) Card(rank='Q', suit='hearts')
Merging dictionaries (Python 3.5+)
Since Python 3.5, it became easier to merge dictionaries:
dict1 = { 'a': 1, 'b': 2 } dict2 = { 'b': 3, 'c': 4 } merged = { **dict1, **dict2 } print (merged) # {'a': 1, 'b': 3, 'c': 4}
If there are overlapping keys, the keys from the first dictionary will be overwritten.
Divisions became more predictable
In Python 2, the division operator /
defaults to an integer division unless one of the operands is a floating-point number. So you have this behavior:
# Python 2 5 / 2 = 2 5 / 2.0 = 2.5
In Python 3, the division operator defaults to a floating-point division and the // operator became an integer division. So we get:
# Python 3 5 / 2 = 2.5 5 // 2 = 2
For the complete motivation behind this change, you should read PEP-0238.
Meaningful comparisons
In Python 2, you could compare anything to everything. The following example would all return True
:
"a string" > 2 None < 5
It makes no sense and can hide bugs. In Python 3, these comparisons will throw a TypeError
exception.
No more range vs. xrange
Python 2 had two range functions: range
and xrange
. The latter was faster since it was based on iterators. In Python 3, range
has become xrange
and the xrange
name was dropped. It’s one of the examples where Python became less confusing for newcomers.