Functions are values
A function is an ordinary object: you can store it, pass it and return it. A dict of functions is Python’s switch statement, and it is usually clearer than the if/elif chain it replaces.
def shout(text):
return text.upper()
f = shout # no parentheses: the function itself
f("hi") # 'HI'
handlers = {"click": on_click, "key": on_key} # a dict of functions
handlers["click"](event)
sorted(words, key=len) # a function passed as an argument
sorted(words, key=str.casefold) # an unbound method works too
The folds that replace a loop
any and all short-circuit, so passing a generator to them means the work stops at the first answer. all([]) is True and any([]) is False — the mathematically correct answers, and both surprise people once.
sum(prices) # and sum(prices, start=10)
min(people, key=lambda p: p.age)
max(counts.values())
any(w.isupper() for w in words) # stops at the first True
all(x > 0 for x in values) # stops at the first False
sorted(rows, key=lambda r: (-r["score"], r["name"]))
sum(1 for x in items if x.ok) # count matching, without building a list
lambda, and when not to
A lambda is a single expression with no name and no statements. It earns its place as an argument to sorted, min, max or groupby; assigning one to a variable just gives you a worse def, with no name in the traceback.
And map(lambda x: f(x), xs) is always [f(x) for x in xs] spelled worse — a comprehension is the Python way to map and filter.
sorted(people, key=lambda p: p.age) # good: one expression, used once
f = lambda x: x * 2 # bad: just write def, you get a name
sorted(words, key=lambda w: w.lower()) # fine, but str.lower is shorter
sorted(nums, key=lambda n: abs(n)) # use key=abs
functools
@cache memoises on the arguments, which must be hashable. It is one line that turns an exponential recursion into a linear one — and a memory leak if you put it on something with unbounded inputs, which is why lru_cache(maxsize=...) exists.
from functools import reduce, partial, lru_cache, cache
reduce(operator.mul, nums, 1) # fold — rare in Python, and that is fine
open_utf8 = partial(open, encoding="utf-8") # a function with an argument fixed
@cache # unbounded; @lru_cache(maxsize=1000) to bound it
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
fib(200) # instant — without the cache this never finishes
fib.cache_info() # CacheInfo(hits=..., misses=..., ...)
operator, and method chaining
itemgetter and attrgetter are the two worth remembering: they say exactly what a lambda would, in less space, and they take several keys at once.
from operator import itemgetter, attrgetter, methodcaller
sorted(rows, key=itemgetter("score")) # faster and clearer than a lambda
sorted(people, key=attrgetter("last", "first"))
max(counts.items(), key=itemgetter(1)) # the most common