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.
Comments are closed.