Python 3 Cheatsheet for Djangonauts

If you are already convinced to use Python 3 then you can directly jump to the section “Python 2 vs Python 3” for the cheatsheet

There is an intense debate whenever Python 3 is mentioned. Some think it is an unnecessary shift and Python 2 series should be continued. However, I chose Python 3 for my book because I strongly believe that it is the future of the language. Besides, the divide might not be as large you think.

Why Python 3?

Think of your favourite programming language and what you love about it. Now think of things that you don’t like about it or wish was improved. Sometimes these improvements might break older codebases but you badly want them.

Now imagine if the creator of the language takes upon himself/herself to revamp the language and rid of its warts. This is what actually led to Python 3 - Guido led the efforts to clean-up some fundamental design flaws with the 2.x series based on over fifteen years of experience developing a successful language.

While the development of Python 3 started in 2006, its first release, Python 3.0, was on December 3, 2008. The main reasons for a backwards incompatible version were – switching to Unicode for all strings, increased use of iterators, cleanup of deprecated features like old-style classes and some new syntactic additions like the nonlocal statement.

Python 2.7 development was supposed to end in 2015 but was extended for five more years through 2020. There will not be a Python 2.8. Soon all major Linux distributions would have completely switched to using Python 3 as a default. Many PaaS providers like Heroku already support Python 3.4.

Python 2 and Python 3 have been in parallel development by the core development team for a number of years. Eventually, Python 3 is expected to be the future of the language.

Porting Django

The reaction to Python 3 in the Django community was rather mixed. Even though the language changes between version 2 and 3 were small (and over time, reduced), porting the entire Django codebase was a significant migration effort.

Django has been supporting Python 3 since version 1.5. In fact, the strategy was to rewrite the code into Python 3 and deal with Python 2 as a backward compatibility requirement.

Most of the packages listed in the Python 3 Wall of Superpowers have turned green (indicating they have support for Python 3). Almost all the red ones have an actively developed Python 3 port. Also worth noting is the Django Wall of Superpowers with over 67% of the top 200 Django packages having a Python 3 port.

Python 3 Advantage

Many newcomers to Django wonder whether to start using Python 2 or 3. I recommend starting with Python 3 for the following reasons:

  1. Better syntax: It fixes a lot of ugly syntax like izip, xrange and __unicode__ with the cleaner and more straightforward zip, range and __str__.

  2. Sufficient third-party support: Of the top 200 third party libraries, more than 80% have Python 3 support.

  3. No legacy code: Since they are creating a new project rather than dealing with legacy code which needs to support an older version.

  4. Default in Modern Platforms: It is already the default Python interpreter in Arch Linux. Ubuntu and Fedora plan to complete the switch in a future release.

  5. It is easy: From a Django development point of view, there are so few changes that it can be learnt in a few minutes.

The last point is important. Even if you know Python 2, you can pick up Python 3 in a short time.

Python 2 vs Python 3

This section covers the most important changes in Python 3 from a Django developer’s perspective. To understand the full list of changes, please refer to the recommended reading section in the end.

The examples are given in both Python 2 and Python 3. Depending on your installation, all Python 3 commands might need to be changed from python to python3 or python3.4 to invoke the interpreter.

Change all __unicode__ methods into __str__

In Python 3, the __str__() method is called for string representation of your models rather than the awkward sounding __unicode__() method. This is one of the most evident ways to identify Python 3 ported code.

Python 2 Python 3
class Person(models.Model):
    name = models.TextField()

    def __unicode__(self):
        return self.name
      
class Person(models.Model):
    name = models.TextField()

    def __str__(self):
        return self.name
      

This reflects the difference in the way Python 3 treats strings. In Python 2, the human readable representation of a class could be returned by __str__() (bytes) or __unicode__() (text). However, in Python 3 the readable representation is simply returned by __str__() (text).

All Classes Inherit from object

Python 2 has two kinds of classes – old-style (classic) and new-style. New-style classes are classes which directly or indirectly inherit from object. Only new style classes can use Python’s advanced features like slots, descriptors and properties. Many of these are used by Django. However, classes were still old-style by default for compatibility reasons.

In Python 3, the old-style classes don’t exist anymore. As seen below, even if you don’t explicitly mention any parent classes the object class will be present as a base. So, all classes are new-style.

Python 2 Python 3
>>> class CoolMixin:
...     pass
>>> CoolMixin.__bases__
()
      
>>> class CoolMixin:
...     pass
>>> CoolMixin.__bases__
(<class 'object'>,)
      

Calling super() is Easier

The simpler call to super(), without any arguments, will save you some typing in Python 3.

Python 2 Python 3
class CoolMixin(object):

    def do_it(self):
        return super(CoolMixin,
            self).do_it()
class CoolMixin:

    def do_it(self):
        return super().do_it()
      

Specifying the class name and instance is optional, thereby making your code DRY and less prone to errors while refactoring.

Relative Imports Must be Explicit

Imagine the following directory structure for a package named app1:

/app1
  /__init__.py
  /models.py
  /tests.py

Now, in Python 3, let us run the following in the parent directory of app1:

$ echo "import models" > app1/tests.py
$ python -m app1.tests
Traceback (most recent call last):
   ... omitted ...
ImportError: No module named 'models'
$ echo "from . import models" > app1/tests.py
$ python -m app1.tests

Successfully import-ed

Within a package, you should use explicit relative imports while referring to a sibling module. You can omit __init__.py in Python 3, though it is commonly used to identify a package.

In Python 2, you could use import models to successfully import models.py module. But it was ambiguous and could accidentally import any other models.py in your Python path. Hence this is forbidden in Python 3 and discouraged in Python 2 as well. Use from . import models instead.

HttpRequest and HttpResponse have str and bytes Types

In Python 3, according to PEP 3333 (amendments to the WSGI standard), we are careful not to mix data coming from or leaving via HTTP, which will be in bytes; as opposed to text within the framework, which will be native (Unicode) strings.

Essentially, for HttpRequest and HttpResponse objects:

  • Headers will be always str objects
  • Input and output streams will be always byte objects

Unlike Python 2, the string and bytes are not implicitly converted while performing comparisons or concatenations with each other. Strings mean Unicode strings only.

Exception Syntax Changes and Improvements

Exception handling syntax and functionality has been significantly improved in Python 3.

In Python 3, you cannot use the comma separated syntax for the except clause. Use the as keyword instead:

Python 2 Python 3
try:
    pass
except BaseException, e:
    pass
      
try:
    pass
except BaseException as e:
    pass
      

The new syntax is recommended for Python 2 as well.

In Python 3, all exceptions must be derived (directly or indirectly) from BaseException. In practice, you would create your custom exceptions by deriving from the Exception class.

As a major improvement in error reporting, if an exception occurs while handling an exception then the entire chain of exceptions are reported:

Python 2 Python 3
>>> try:
...   print(undefined)
... except Exception:
...   print(oops)
...
Traceback (most recent call last):
  File "", line 4, in <module>
  NameError: name 'oops' is not defined
>>> try:
...   print(undefined)
... except Exception:
...   print(oops)
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  NameError: name 'undefined' is not defined

During the handling of the preceding
exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  NameError: name 'oops' is not defined
      

Once you get used to this feature, you will definitely miss it in Python 2.

Standard Library Reorganized

The core developers have cleaned-up and better organized the Python standard library. For instance SimpleHTTPServer now lives in the http.server module:

Python 2 Python 3
$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
      
$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 ...
      

New Goodies

Python 3 is not just about language fixes. It is also where bleeding-edge Python development happens. This means improvements to the language in terms of syntax, performance and built-in functionality.

Some of the notable new modules added to Python 3 are:

  • asyncio : Asynchronous I/O, event loop, coroutines and tasks
  • unittest.mock : Mock object library for testing
  • pathlib : Object-oriented filesystem paths
  • statistics : Mathematical statistics functions

Even if some of these modules might have backports to Python 2, it is more appealing to migrate to Python 3 and leverage them as built-in modules.

Pyvenv and Pip are Built-in

Most serious Python developers prefer to use virtual environments. virtualenv is quite popular to isolate your project setup from the system-wide Python installation. Thankfully, Python 3.3 is integrated with a similar functionality using the venv module.

Since Python 3.4, a fresh virtual environment will be pre-installed with pip, a popular installer:

$ python -m venv djenv
[djenv] $ source djenv/bin/activate
[djenv] $ pip install django

Notice that the command prompt changes to indicate that your virtual environment has been activated.

Other Changes

We cannot possibly fit all the Python 3 changes and improvements in this post. But the other commonly cited changes are:

  1. print is now a function : Previously it was a statement i.e. arguments were not in parenthesis.
  2. Integers don’t overflow : sys.maxint is outdated, integers will have unlimited precision.
  3. Inequality operator <> is removed : Use != instead.
  4. True Integer Division : In Python 2, 3 / 2 would evaluate to 1. It will be correctly evaluated to 1.5 in Python 3.
  5. Use range instead of xrange : range() will now return iterators as xrange() used to work before.
  6. Dictionary keys are views : dict and dict-like classes (like QueryDict) will return iterators instead of lists for keys(), items() and values() method calls.

This article is an excerpt from the book “Django Design Patterns and Best Practices” by Arun Ravindran

Further Information

Hi! Welcome to ArunRocks, an odd collection of writeups on programming, travel, gadgets and practically anything under the sun. This state of affairs could be blamed on the ecelectic interests of your host, Arun Ravindran. He loves programming in several languages especially Python. He is currently a developer member of the Django Software Foundation. Read more...

Comments