One part of Python I especially appreciate is
lambda functions. While I’ve never pursued functional programming, I do like many things about it, particularly the notion of functions as native data objects. Programming with functional objects opens new vistas. Most languages handle it one way or another, but languages make it natural.
Python’s lambda is such a facility, and I use it often. This week I finally got around to writing a
lambda function I’ve been meaning to for a long time, and it’s my new favorite. I thought I’d share it along with some of my other “one-liners”…
The reason for this “macro” is that, more and more, I find myself using the following framework for complex functions (especially including object initializers):
This provides maximum flexibility in terms of parameters (but [*]). I like writing functions with lots of optional settings, things that can usually be ignored because the default setting values work in most cases. But sometimes you want to tweak something, and it’s nice to have that option.
This does require good documentation discipline to record all those options, but I think it’s more than worth the effort involved. What’s a pain is writing that expression. It isn’t just the tedium factor, it’s that two names have double occurrences (
kwargs and the key string). Besides being extra tedious, it’s a synchronization problem, especially for the key string. Note the similarity of some parameter names; it’s easy to make a typo.
[*] In this form, one of its features can be a limitation, but correcting the limitation removes the flexibility. The feature is allowing unknown or unused parameters in
kwargs, but this limits error-checking. A misspelled parameter name can slip through, which only causes that parameter value to be ignored. Depending on default settings, that might be subtle bug.
I’ve always known I should make a macro (a lambda expression). Python very likely has something already built in (or in its library). but it’s just as simple to write my own. An obvious first pass is:
This eliminates the double occurrence of names and makes it easier to spot bugs. I’ve found I often need to cast the input argument (typically a string from the command line or config file) to a numeric type. This version is less clean than it could be, because the default value is invariably already the right type.
There is also that the visual left-right offset due to the type name and parentheses make it harder to visually check correctness (which depends, in part, on alignment; see the example in this post).
Thanks to Python treating types as first-class objects, here’s a better way:
Things align much nicer now. A vast improvement!
As an aside, using types as objects is a handy feature. Suppose we want something like the
range object, but want it to provide
string values as well as
int values? We can make a simple range factory:
If we run this, it prints:
[0, 1, 2, 3] [0, 1, 2, 3, 4, 5, 6, 7] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] [0.0, 1.0, 2.0, 3.0] [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] ['0', '1', '2', '3'] ['0', '1', '2', '3', '4', '5', '6', '7'] ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']
Back to the
lambda, here are some from my
They should be more or less self-explanatory. The plural function is useful in cases like this:
The two forms of joining a list into a string are so common I made functions for both. The
lst2str for all those times of just concatenating list items, and
lst2str2 for when I want some kind of delimiter between items. (Implementing this as a single function would allow making the delimiter default to the empty string. Two birds!)
zstr function is just a pretty print for complex numbers, and the
typename function extracts a type’s actual name. (Which is nicer.)
comp function replaces Python’s now obsolete (and gone)
cmp function. It returns +1 if the first item is larger, -1 if it’s smaller, and 0 if they equal. (Alternate view: +1 means first item is larger; -1 means second item is larger.) Weird thing is, I missed
cmp at first, but since I made
comp, I’ve hardly used it.
I’ve had a similar experience with
reduce, which is a formerly beloved function now obsolete (and gone). I quickly wrote a replacement, but found I hardly use it. Having to
import a function, rather than having it ever-present, apparently makes me think twice about using it.
frac function is a safe way to divide two numbers. It just returns 0.0 if the divisor is zero. It prevents errors in, for example output routines that calculate averages and can sometimes try to average a list of zero things, which leads to:
And if the count is zero, then the math blows up, but in most output cases it’s okay to just output 0.0 when the divisor is zero. The
frac function is helpful in such cases. (The real answer is neither zero nor infinity, but undefined. There are sound mathematical reasons the first two don’t work.)
dot_product functions are more to show it’s possible; I don’t think I’ve ever actually used them. When I do work with vectors, I use classes that have these as methods, and I rarely (if ever) work with vectors as just lists. (But if I ever do, I’ve got these handy.)
Lastly, here are some just for fun:
It’s easy to implement the factorial function (although the version in the
math module is much better; use that).
There are two
Collatz functions: one, given a number, returns the next number in the sequence, the other returns the entire sequence given a starting number. Note the latter is recursive (
lambda functions can call themselves) and it also calls the first Collatz function. Just a sampling of what’s possible with one-line
They’re also very handy for setting up conversions, say between kilometers and miles. My code is peppered with them (a quick scan turns up 454 occurrences among my Python files). I do love the