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 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
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()
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
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(["multiply", "2", "3"])
Namespace(number1=2.0, number2=3.0, subcommand='multiply')
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)
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()
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()
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.