7 min read

10 benefits of using Python 3 over 2

10 important reasons to stop working with Python 2 and switch to Python 3.
10 benefits of using Python 3 over 2
It's time to use Python 3

This article should not exist in 2021. But it does!

Regardless of the fact that Python 2 has become obsolete since the beginning of 2020, the migration process from Python 2 to Python 3 has been relatively slow.

A quick analysis of the PyPI package downloads shows that there are more than 5.5 billion package installations for Python 2.7 in the last 6 months.

Package downloads from PyPI categorized by Python version

Just a quick look at Stack Overflow, and you will realize that more than 4000 questions have been asked under the Python 2.7 tag.

If you are an active python programmer (no matter which level), you probably know that Python 2 is obsolete. Probably a lot of your friends, colleagues, and people in the Python community have already suggested you move on to Python 3.

If you haven’t done it yet, too bad. You should really be listening more keenly to your well-wishers.

You have been missing out on a lot of stuff that Python 3 brings to the table. Python 3 has a lot of key attributes that set it aside from Python 2.

In this article, I am going to demonstrate the 10 most important differences (In my humble opinion) between Python 2 and 3 and the advantages associated with them.


Table of Content

  1. Print is a function, not a statement
  2. More relatable to Mathematics
  3. Meaningful comparisons
  4. No more xrange()
  5. Extended Iterable Unpacking
  6. Unicode support
  7. Fixed syntax for inequality operator
  8. raw_input() is gone! Just input()
  9. Iterables instead of list
  10. Raising and handling exceptions

In Python 3, print is a function and not a statement. It comes with some keyword arguments as additional features. Syntax wise, print() in Python 3 requires a parenthesis.

print "My lucky number is", 7  # Python 2
print("My lucky number is", 7) # Python 3

In order to suppress a newline while printing, you had to use a trailing comma in Python 2.

Code

# Python 2 
print 2,
print 4

The above snippet produces the following results.

Output

2 4

In order to reproduce the same results in Python 3, you have to use the end keyword argument.

Code

print(2, end =" ")
print(4)

My ultimate favorite addition is the file keyword argument to Python 3. The syntax for printing lines directly to files in Python 3 is much more readable than it was in Python 2.

Code

# Python 2
print >>open('test.txt','w'), "Printing to file"

# Python 3
print("Printing to file", file = open('test.txt','w')) # Python 3

More relatable to Mathematics

1 divided by 2 is not 0 in Python 3! But that is what happened in Python 2. In order to get the right value, you had to do one of the following.

Code

# Python 2

float(1)/2
1.0/2
1/2.0

In Python 3, 1 divided by 2 is 0.5 (which is what it should be). If you need the truncating behavior, use // instead of /.


Meaningful comparisons

Unlike Python 2, there is no more comparison of anything to everything. Try running the following in a Python 2 interpreter.

Code

# Python 2

print "one" > 2
print None < 2
print None < len

Does it make sense? Amazingly, all of them return True.

In Python 3, you cannot perform such operations anymore. When the operands don’t have a meaningful natural ordering, the interpreter returns a TypeError.

Code

print("one" > 2)

The above snippet returns the following error.

Output

Traceback (most recent call last):
File <input_name>, line 1, in <module>
  print("one" > 2)
TypeError: '>' not supported between instances of 'str' and 'int'

This is a great enhancement as it will drastically improve the robustness of your code.


No more xrange()

In Python 2, xrange usually had the advantage of being faster than range when you are iterating over an iterable once. For example, in a for loop. However, in Python 3, range has been implemented as xrange. Hence, the xrange doesn’t exist anymore in Python 3. It returns a NameError if you use it.

So no need to switch between range and xrange. Just use range.


Extended Iterable Unpacking

With Python 3, you can perform advanced levels of iterable unpacking.

For e.g.

Code

x, *y, z = range(5)
   
print(x)
print(y)
print(z)

When you run the above code in a Python 3 interpreter, you would get results like this.

Output

0
[1, 2, 3]
4

This operation is not possible in Python 2. If you try to execute this with Python 2, you would receive a SyntaxError

# Output in Python 2

File <input_name>, line 1
 x, *y, z = range(5)
    ^
SyntaxError: invalid syntax

Unicode support

In Python 2, you had 2 text types - str and Unicode. The str type was mostly limited to ASCII characters. It also had one byte type called bytearray.

In Python 3, you only have one text type, which is equivalent to Unicode type in Python 2. You also have two byte types - bytes and bytearray.

In Python 2, you had to prefix a string with u in order to make it Unicode. For example,

Code

# Python 2

print type('Pylenin')
print type(u'Pylenin')

will produce the following results.

Output

<type 'str'>
<type 'unicode'>

However in Python 3, since str is the only text type, it handles Unicode by default.

In Python 2, you can easily concatenate a str type and byte type. However, it is not possible in Python 3.

Code

   
x = "Python 2"+b" will remain forever"
   
# Python 2 
print x
print type(x)

# Python 3
print(x) 
print(type(x))

The snippet for Python 2 will work and the overall type will be cast to str. However, for Python 3, it will return a TypeError.

Output

# Python 2
Python 2 will remain forever 
<type 'str'>

# Python 3
Traceback (most recent call last):
File <input_name>, line 1, in <module>
  x = "Python 2"+b" will remain forever"
TypeError: must be str, not bytes

Fixed syntax for inequality operator

In Python 2, both != and <> used to work perfectly fine as inequality operators.

Code

# Python 2

print 2!=3
print 2<>3

However, in python 3, the alternative of <> has been completely removed. Basically now there is only one way of doing it. By using != , or the inequality operator.


raw_input() is gone! Just input()

In Python 2, you had both input() and raw_input(). The difference was that, input() was able to read and store any data type and store it as the same type. In order to store every input as a string, you had to use raw_input(). Let’s look at some examples.

Code/Output

# Python 2

>>> my_input = input('Enter something: ')

Enter something: 123
>>> type(my_input)
<type 'int'>
   
>>> my_input = input('Enter something: ')

Enter something: "Pylenin"
>>> type(my_input)
<type 'str'>
   
>>> my_input = input('Enter something: ')

Enter something: Pylenin # Without quotation
Traceback (most recent call last):
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
NameError: name 'Pylenin' is not defined

Watch how it throws an error when you pass Pylenin without any quotations. This is really dangerous as you could expect malicious behavior in a lot of situations. In Python 3, the raw_input() has been removed. There is only the input() method in Python 3 and it parses the user input as a string for every data type.

Code/Output

# Python 3
>>> my_input = input('Enter something: ')

Enter something: 123
>>> type(my_input)
<class 'str'>
   
>>> my_input = input('Enter something: ')

Enter something: "Pylenin"
>>> type(my_input)
<class 'str'>
   
>>> my_input = input('Enter something: ')

Enter something: Pylenin # Without quotation
>>> type(my_input)
<class 'str'>

Iterables instead of list

In Python 2, built-in functions like range, zip, map and filter used to return a list. However, in Python 3, they return iterables. You can convert them to a list by using the list() method.

Now does it make sense? In some way, Yes! Usually, you iterate only once over them. Hence, it is helpful in saving memory.

Also, dictionary methods like keys(), values() and items() do not return lists anymore. They return views.

Code

# Python 3

x = {"a":1,"b":2}
   
print(type(x.keys()))
print(type(x.values()))
print(type(x.items()))

The above snippet will return the following result.

Output

<class 'dict_keys'>
<class 'dict_values'>
<class 'dict_items'>

Raising and handling exceptions

One of my favorite improvements in Python 3 is the way it raises and handles exceptions. Let’s compare the results of executing a small code in Python 2 with Python 3.

Code

# Python 2
    
class FirstClass():
  def exc(self):
    raise Exception("Error in first class")
    
class SecondClass():
  def exc(self):
    foo = FirstClass()
    try:
      foo.exc()
    except:
      raise Exception("Error in second class")
x = second_class()
print x.exc() 

Now, this is certainly not the best implementation of Classes in Python. However, for this purpose, it will do the job.

When you run the above code, you will only see the exception raised in the second class.

Output

# Python 2

Traceback (most recent call last):
  File <input>, line 13, in <module>
    print x.exc()
  File <input>, line 11, in exc
    raise Exception("Error in second class")
Exception: Error in second class

This is very bad for debugging. If you go through the code, you will see that there is a try and except block in the SecondClass. You first try to call the exc() method lying within the FirstClass. If that block fails, you raise an exception.

Now ideally, you would want both the exceptions to be shown to us. Because the truth is, the except block is being executed only after the try block fails. Python 2 fails to deliver on this traceback. However, Python 3 provides a better overview of the exception.

Output

# Python 3

Traceback (most recent call last):
  File <input>, line 9, in exc
    foo.exc()
  File <input>, line 3, in exc
    raise Exception("Error in first class")
Exception: Error in first class
    
During handling of the above exception, another exception occurred:
    
Traceback (most recent call last):
  File <input>, line 13, in <module>
    print(x.exc())
  File <input>, line 11, in exc
    raise Exception("Error in second class")
Exception: Error in second class

As you can see, you are warned about both the exceptions in Python 3. This arrangement is therefore much better for debugging purposes.

Like I mentioned earlier, you are missing out on a lot of things if you haven’t switched to Python 3 yet. There are more examples where Python 3 performs both syntactically and in terms of performance over Python 3. However, these 10 changes were the ones that I thought were more relevant. At least I tend to notice them very easily.

So if you haven’t switched yet, I recommend you to make the jump now. Before it is too late!