Python 3.7.0: Five New Features to Learn and Explore

Given the newness of Python 3.7.0—it was released on June 27—my first attempts to run it on WSL (Windows Subsystem for Linux) Ubuntu didn’t quite go as planned. There’s no Debian or Ubuntu distribution of Python 3.7.0 at the moment, just the sources, so I used pyenv,which fetches the sources and build them.

Even after I upgraded the WSL Ubuntu to 18.04, my attempts with Python 3.7.0 still hit a permissions error. Microsoft, please take note! Fortunately, I could install pyenv on a Hyper-V virtual machine running Ubuntu, and then Python 3.7.0 installed without a problem. (These alternative instructions helped, but don’t forget it’s 3.7.0, not 3.6.4.)

I like pyenv because it supports virtualenv and allows you to install different Pythons in different folders. If you’re testing software or installing lots of modules, using virtualenv is definitely the way to go; it means there’s less chance of breaking the standard Python installation.

There’s a lot of new stuff in Python 3.7.0, much of it quite simple. For instance, str, bytes and bytearray now have an isascii() function that returns ‘true’ if they only contain ASCII characters.

There’s a full list of new stuff in the release notes, but I wanted to drill a bit into the five features listed below.

New Time Functions

The existing time.time() function returns time as a float, but it has limited precision. As a result, six new nanosecond versions of existing functions have been created by adding _ns. Try this in Python 3.7.0:

>>> print (time.time(), time.time_ns())
1530460499.8412137 1530460499841213700
>>> print (time.time(), time.time_ns())
1530460503.3369007 1530460503336900600
>>> print (time.time(), time.time_ns())
1530460505.1775672 1530460505177567200

 

You’ll see the values diverge slightly.

Forced UTF-8 Mode

On platforms other than Windows, there has always been a problem with the locale supporting UTF-8 or ASCII (7-bit) characters. The standard C locale is typically ASCII.

This affects how text files are read (for example). UTF-8 includes all 8-bit ASCII characters and 2-4 length characters, as well. This change forces the locale to be one of the supported UTF-8 locales. You can change what happens by setting a new environment variable, PYTHONCOERCELOCALE. To use ASCII requires both this setting and PYTHONUTF8 to be disabled.

Setting PYTHONUTF8 to 1 forces the Python interpreter to use UTF-8, but if this isn’t defined, it defaults to the locale setting; only if PYTHONCOERCELOCALE is disabled does it use ASCII mode. In other words, UTF-8 is not the default on Unix systems unless you explicitly disable these two settings.

Built-Breakpoint

With prior versions of Python, you could get a break point by using the pdb debugger and this code, which breaks after function1() is called and before function2():

function1()
import pdb; pdb.set_trace()
function2()

 

The author of PEP 553 found this a bit fiddly, and two statements on a line upset some Python linters. So now there’s a new breakpoint() function:

function1()
breakpoint()
function2()

 

This works in conjunction with the Python environment variable PYTHONBREAKPOINT. Set it to 0 and the breakpoint does nothing. Give it a function and module value, and it will import the module and call it. You can change this programmatically at runtime; that’s very handy for having conditional breakpoints.

Data Classes

If you’re storing data in classes, this new feature simplifies things by generating boilerplate code for you.

A data class is a normal Python class with the addition of a @dataclass decorator. It makes use of Type hints, a new feature since Python 3.5 where you annotate variables to provide a hint as to the type of variable. You have to use this for fields in a data class.

In the example below, you can see the : str after the variable name. That’s a type hint.

If you want your data class to be immutable, just add (frozen=true) to the decorator. Here’s a simple example showing an immutable data class. There’s not a lot of code in the class declaration; all the usual stuff is generated for you. The last two lines use the class and print out the instances:

from dataclasses import dataclass

@dataclass(frozen=True)
class ReadOnlyStrings(object):

   field_name : str
   field_address1 : str
test = {ReadOnlyStrings(1,'David'), ReadOnlyStrings(2,'Bolton')}
print(test)

 

Running it outputs this:

{ReadOnlyStrings(field_name=2, field_address1='Bolton'), ReadOnlyStrings(field_name=1, field_address1='David')}

Development Runtime Mode

The -X parameter of CPython (the standard implementation of Python) lets you set various implementation details, and has been extended to include the word ‘dev.’

This activates additional runtime checks such as debug hooks on memory allocators, lets the faulthandler module dump the Python traceback, and enables asyncio debug mode (which adds extra logging and warnings but slows down asyncio performance).

Your program can detect if it’s running dev mode by checking sys.flags.dev_mode. Try this in the Python interpreter:

python -X dev
Python 3.7.0 (default, Jul  2 2018, 20:50:09) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys 
>>> print (sys.flags.dev_mode)
True

 

Dev mode is really only meant for debugging, as the impact of it on performance will probably slow your scripts down.

Conclusions

Python 3.7.0 is a service release, and thus offers a number of all-around improvements. Some previously deprecated function and modules have also been removed.

Side note: One new feature that seems possibly a bit excessive is the ability to declare functions with more than 255 parameters, and pass more than 255 parameters to a function! Based on reading the bugsfor this, it seems the rationale was having a hard (i.e., 255) limit was just not “Pythonic.”

Related