3 Python 3.2 features that are still relevant today

Explore some of the underutilized but still useful Python features.
47 readers like this.

This the third article in a series about features that first appeared in a version of Python 3.x. Some of those Python versions have been out for a while. For example, Python 3.2 was first released in 2011, yet some of the cool and useful features introduced in it are still underused. Here are three of them.

argparse subcommands

The argparse module first appeared in Python 3.2. There are many third-party modules for command-line parsing. But the built-in argparse module is more powerful than many give it credit for.

Documenting all the ins and outs of argparse would take its own article series. For a small taste, here is an example of how you can do subcommands with argparse.

Imagine a command with two subcommands: negate, which takes one argument, and multiply which takes two:

$ computebot negate 5
-5
$ computebot multiply 2 3
6
import argparse

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

The add_subparsers() methods creates an object that you can add subcommands to. The only trick to remember is that you need to add what subcommand was called through a set_defaults():

negate  = subparsers.add_parser("negate")
negate.set_defaults(subcommand="negate")
negate.add_argument("number", type=float)
multiply  = subparsers.add_parser("multiply")
multiply.set_defaults(subcommand="multiply")
multiply.add_argument("number1", type=float)
multiply.add_argument("number2", type=float)

One of my favorite argparse features is that, because it separates parsing from running, testing the parsing logic is particularly pleasant.

parser.parse_args(["negate", "5"])
    Namespace(number=5.0, subcommand='negate')
parser.parse_args(["multiply", "2", "3"])
    Namespace(number1=2.0, number2=3.0, subcommand='multiply')

contextlib.contextmanager

Contexts are a powerful tool in Python. While many use them, writing a new context often seems like a dark art. With the contextmanager decorator, all you need is a one-shot generator.

Writing a context that prints out the time it took to do something is as simple as:

import contextlib, timeit

@contextlib.contextmanager
def timer():
    before = timeit.default_timer()
    try:
        yield
    finally:
        after = timeit.default_timer()
        print("took", after - before)

And you can use it with just:

import time

with timer():
    time.sleep(10.5)
    took 10.511025413870811

functools.lru_cache

Sometimes the caching results from a function in memory make sense. For example, imagine the classical problem: "How many ways can you make change for a dollar with quarters, dimes, nickels, and cents?"

The code for this can be deceptively simple:

def change_for_a_dollar():
    def change_for(amount, coins):
        if amount == 0:
            return 1
        if amount < 0 or len(coins) == 0:
            return 0
        some_coin = next(iter(coins))
        return (
            change_for(amount, coins - set([some_coin]))
            +
            change_for(amount - some_coin, coins)
        )
    return change_for(100, frozenset([25, 10, 5, 1]))

On my computer, this takes around 13ms:

with timer():
    change_for_a_dollar()
    took 0.013737603090703487

It turns out that when you calculate how many ways you can do something like making change from 50 cents, you use the same coins repeatedly. You can use lru_cache to avoid recalculating this over and over.

import functools

def change_for_a_dollar():
    @functools.lru_cache
    def change_for(amount, coins):
        if amount == 0:
            return 1
        if amount < 0 or len(coins) == 0:
            return 0
        some_coin = next(iter(coins))
        return (
            change_for(amount, coins - set([some_coin]))
            +
            change_for(amount - some_coin, coins)
        )
    return change_for(100, frozenset([25, 10, 5, 1]))
with timer():
    change_for_a_dollar()
    took 0.004180959425866604

A three-fold improvement for the cost of one line. Not bad.

Welcome to 2011

Although Python 3.2 was released 10 years ago, many of its features are still cool—and underused. Add them to your toolkit if you haven't already.

What to read next
Tags
Moshe sitting down, head slightly to the side. His t-shirt has Guardians of the Galaxy silhoutes against a background of sound visualization bars.
Moshe has been involved in the Linux community since 1998, helping in Linux "installation parties". He has been programming Python since 1999, and has contributed to the core Python interpreter. Moshe has been a DevOps/SRE since before those terms existed, caring deeply about software reliability, build reproducibility and other such things.

Comments are closed.

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.