You never finish a project, you just stop working on it.
Hey pips!
Python provides a super awesome built-in way of sorting sequences – just call sorted()
on any sequence, and it’ll return a list with the sorted elements.
>>> sorted([4, 1, 0, 6, 9])
[0, 1, 4, 6, 9]
Note that the items need to be the same type to be sortable – you can’t sort a list containing both str
and int
objects. By default, it’ll use the type’s comparator to sort the objects. For numbers, that’s >
/<
.
But we can also supply our own comparator, through the key
parameter. You might need this if you’re doing some extra processing or calculations when sorting, or you want to sort objects based on particular properties.
This comparator is a function that takes 2 items in, and should return True
or False
(or any truthy/falsy values) depending on which item takes priority when ordering.
>>> def compare(player1, player2):
return player1.score > player2.score
Now how do we pass this function in? Remember that functions are objects, so we just set key
to the function identifier:
>>> sorted(players, key = compare)
# ^^^^^^^ we’re not calling compare() here!
["Rick", "Violet", "Aris"]
Internally, sorted
will now call compare()
when it sorts the items.
This is a key example (excuse the pun) of where you might pass around functions in Python – as a callback that another function can use.
But it’s a little unergonomic having to first define the function, then pass it in. It’s like having to append items to a list in a loop, instead of using a list comprehension.
def callback():
print("processing chunk")
contents = []
for packet in data:
contents.append(packet.payload)
process(contents, callback)
So, Python also gives us a nice way of defining functions inline, using the lambda
keyword.
>>> lambda: "sup world!"
<function <lambda> at 0x0000015883B3A420>
[!Tip] Apparently this is a reference to lambda calculus, but I have zero expertise on that, so.
The lambda:
part transforms this into a function. It’s followed by an expression, which is what the function returns. So for instance, these are all valid lambda functions:
lambda: 2
lambda: x or y
lambda: all(items)
But remember that this is defining the function, not calling it. The whole lambda expression evaluates to a function object. We can assign that to a identifier to see it in action:
>>> woah = lambda: print("sup world!")
>>> woah()
sup world!
But more commonly, we’re just passing the function to some other expression instead of calling it.
process(
[packet.payload for packet in data],
lambda: print("processing chunk")
# ^^^^^^ this is the `callback` argument
)
Notice this means the function is never bound to an identifier – it’s effectively nameless. Hence lambda functions are often also called anonymous functions, altho I find this a little weird since there’s nothing stopping you from still giving them a name.
huzzah = lambda: "true power lies in anonymity"
So this is great and all, but we need parameters! Add them after the lambda
keyword, before the :
.
>>> squarer = lambda x: x**2
>>> squarer(13)
169
And if you need multiple, separate them with commas like you would in a regular function definition:
>>> diff = lambda x, y: math.abs(x - y)
>>> diff(-10, -7)
3
And if you need optional parameters, make ’em optional:
>>> roll = lambda target, obscure = False: target.poke() if obscure else target.link()
>>> roll("Rick")
>>> roll("Yeltsa", obscure = True)
And if you need arbitrary, guess what, those work too!
lambda first, *rest: first in rest
lambda **kwargs: set(kwargs.values)
And in fact, all the symbols you can use normally in function definitions work exactly the same here.
lambda req_pos_only, /, opt_either = True, *, opt_key_only = None: "whatup"
The one exception is type hinting, which wouldn’t mesh well with the lack of parentheses here – but since you’re usually using lambda for small, one-off functions, you don’t really need type hinting anyway.
So, to see how a function definition collapses to an inline lambda expression:
def check_all(source, target):
if target:
return all(target in each for each in source)
else:
return False
# with a little short-circuiting magic...
check_all = lambda source, target: target and all(target in each for each in source)
But don’t start using this everywhere! Lambda functions are meant specifically for those use cases where you’re defining and passing a function object around – they’re not at all meant to replace regular function definitions entirely. You can see from the examples above that they can quickly get out of hand with too many parameters.
Also notice lambda functions only have a single expression, which they automatically return. You can’t have multiple statements or complex control flow – and no, ;
does not work either.
# invalid
(lambda:
print("wouldn’t")
print("it")
return "be"
)
# valid, but guess what it does
lambda: print("nice"); print("if")
Anyway, back to using sorted()
, we can now see how we could more conveniently pass in our comparator function by defining it inline using a lambda expression:
sorted(
players,
key = lambda p1, p2: p1.score > p2.score
)
Further Reading
- Passing callback functions into other functions is a form of dependency injection.