Python 3 Advantages

Let’s explore the advantages Python 3 has to offer over Python 2!

Print is no longer a statement but a built-in function

Some of the advantages:

  • 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.


In Python 3, every string is, by default, 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_sting = u'Ümlaut? Nō prōblem!'

# Python 3
unicode_sting = 'Ü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

class Card:
    rank: str
    suit: str

card = Card("Q", "hearts")

print(card == card)
# True

# 'Q'

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.