Command Palette

Search for a command to run...

Hectal
PHASE 5Beginner ~26 min· topic 6 of 10

Topic 5.6

Functions Are Values: Passing Them Around

In one line

In Python a function is an object like any other: you can store it in a variable, put it in a list or dict, pass it to another function, and return it. That idea powers sorted(key=...), map, filter, small lambda functions, and the tool tables that let AI models call your code.

Think of it like this

A recipe card. The card is not the food; it is the instructions for making it. You can keep cards in a box (a list), file them under labels (a dictionary), hand one to a cook who decides when to follow it (passing it to another function), or let a helper pick the right card for you (returning it). A Python function is a card like that: you can hold it and hand it around without cooking, which means without running it.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

First-class function
A function that can be treated like any other value: stored, passed and returned.
Higher-order function
A function that takes a function as an argument, or returns one.
Lambda
A short unnamed function made from one expression, written lambda x: x + 1.
Callable
Anything that can be called with round brackets.
map
A built-in that applies a function to every item and gives the results lazily.
filter
A built-in that keeps only the items for which a function returns something true.
Dispatch table
A dictionary that maps names to functions so you can call the right one by name.
key function
A function given to sorted, min or max that says what to compare.

Step by step

01A name with no brackets is the function itself

shout is the function object. shout("hi") calls it. You can copy the object to another name, print its __name__, and ask its type. Nothing runs until you add brackets.

main.pywhole filepython
def shout(text):
    return text.upper() + "!"


loud = shout               # no brackets: a second name, not a call
print(loud("hello"))       # HELLO!
print(type(shout).__name__, shout.__name__)   # function shout

02Passing a function as an argument

apply takes any function and a value and calls the function on the value. The caller decides the behaviour, the helper does the plumbing.

main.pywhole filepython
def shout(text):
    return text.upper() + "!"


def whisper(text):
    return text.lower() + "..."


def apply(func, text):
    return func(text)


print(apply(shout, "Hello"))      # HELLO!
print(apply(whisper, "Hello"))    # hello...

03Functions in a dict, functions returned

Because functions are values, they can be dictionary values. Looking up a key then gives you a function you can call. A function can also decide which function to hand back.

main.pywhole filepython
styles = {"loud": shout, "quiet": whisper}
print(styles["loud"]("Good Morning"))     # GOOD MORNING!


def pick(style):
    return shout if style == "loud" else whisper


chosen = pick("quiet")        # chosen is the function whisper
print(chosen("Wait"))         # wait...
Functions in a dict, functions returneddiagram
Rendering diagram…

04key= for sorting and searching

sorted, min and max accept key=function. Python calls the function once per item, and compares the results. The items themselves are returned unchanged. Pass len, not len().

main.pywhole filepython
words = ["banana", "fig", "cherry", "apple"]
print(sorted(words, key=len))                   # shortest first
print(sorted(words, key=lambda w: w[-1]))       # by last letter

05lambda, map, filter and comprehensions

A lambda is just a function without a name. map transforms and filter selects. Comprehensions do both at once and are usually more readable. Use a def as soon as a lambda needs a second thought.

main.pywhole filepython
numbers = [1, 2, 3, 4, 5, 6]
print(list(map(lambda n: n * n, numbers)))        # [1, 4, 9, 16, 25, 36]
print(list(filter(lambda n: n % 2 == 0, numbers)))  # [2, 4, 6]
print([n * n for n in numbers if n % 2 == 0])       # [4, 16, 36]

06Callables and the tool pattern

callable(x) is true for anything you can call. In AI code, tools are plain functions registered in a dictionary. The model returns text such as {"name": "add", "arguments": {...}}, your program looks the name up in the dictionary (and refuses names that aren't there), and calls func(**arguments). Never let a model name run arbitrary code: only the functions you put in the table can be called.

Try it yourself

  1. 1

    Sort by your own rule

    In the second example, sort words by the last letter in reverse (reverse=True). Then sort by lambda w: w.count("a"). Predict first.

  2. 2

    Add a tool

    In the third example, write a function square(n) and add it to TOOLS. Add a request {"name": "square", "arguments": {"n": 9}}. Run it.

  3. 3

    Call it by mistake

    Change key=len to key=len() and read the error. Calling len() with no argument happens before sorted even starts.

Code & diagrams

Passing, storing and returning functions New tab

shout and whisper are passed to apply, stored in a dict, and returned from pick.

Sign in to run this example in your browser.

Expected output

HELLO!
hello...
loud -> GOOD MORNING!
quiet -> good morning...
wait...
type: function
name: shout
True True False
key=, lambda, map and filter New tab

The key function says what to compare. map is lazy, so the last line shows its type.

Sign in to run this example in your browser.

Expected output

['fig', 'apple', 'banana', 'cherry']
['banana', 'apple', 'fig', 'cherry']
['cherry', 'banana', 'apple', 'fig']
[1, 4, 9, 16, 25, 36]
[2, 4, 6]
[4, 16, 36]
('Ravi', 95)
map
A dispatch table of tools New tab

This is the shape of every tool-calling loop: look up the function by name, refuse unknown names, call it with the arguments dict.

Sign in to run this example in your browser.

Expected output

add -> 5
shout -> HELLO
unknown tool: delete_everything

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Call the function instead of passing it

Write len() with brackets in the key argument.

main.pywhole filepython
words = ["banana", "fig", "cherry", "apple"]
print(sorted(words, key=len()))
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 2, in <module>
print(sorted(words, key=len()))
~~~^^
TypeError: len() takes exactly one argument (0 given)

Myth vs fact

Myth

lambda is faster or more advanced than def.

Fact

A lambda is a def with a tiny body and no name. It has the same speed. Its only advantage is brevity at the point of use.

Myth

map and filter are the Pythonic way to transform lists.

Fact

Comprehensions are the usual choice and read better. map is good when you pass an existing named function, such as map(int, strings).

Myth

print(map(...)) shows the results.

Fact

map is lazy: printing it shows a map object. Wrap it in list(...) to see the values.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Function objects have attributes: __name__, __doc__, __defaults__, __code__, __closure__ and more (Topic 5.7). They can even carry your own attributes. This is what decorators (Topic 9.6) build on.

  • ▸

    functools.partial(func, *args, **kwargs) makes a new function with some arguments already filled in. operator.itemgetter(1) and operator.attrgetter("name") are faster, clearer replacements for the lambdas lambda s: s[1] and lambda o: o.name as sort keys.

  • ▸

    sorted(key=...) calls the key function exactly once per item (not once per comparison), then sorts by the results (the decorate-sort-undecorate idea). The sort is stable (Topic 4.2).

  • ▸

    map and filter return one-shot iterators: loop over one twice and the second pass is empty. map(f, a, b) takes several iterables and stops at the shortest one without warning; Python 3.14 adds strict=True (as in zip) to raise an error instead. filter(None, items) keeps the truthy items. A lambda has no statements, no annotations and its __name__ is <lambda>, which makes tracebacks and logs harder to read.

  • ▸

    A tool table is also a security boundary. The model chooses a name; only names you registered can run. Validate the arguments (types, ranges) before calling, and never evaluate model text as code. Phase 16 returns to this.

Remember this

  1. 1

    def greet(name): ... creates a function object and binds the name greet to it. The name greet (no brackets) is the object; greet("Asha") (with brackets) is a call to it. So other = greet gives the same function a second name, and [greet, shout] is a list of two functions. Languages where this works are said to have first-class functions.

  2. 2

    A function that takes another function as an argument is called a higher-order function. apply(func, text) calls func(text) and does not care which function it is. This lets you separate what to do to each item from how to go through the items, which is exactly what sorted(words, key=len) does: sorted handles the ordering, key says what to compare.

  3. 3

    A lambda is a tiny unnamed function written in one expression: lambda w: w[-1] means "given w, return its last character". It can have several parameters but only one expression, with no statements and no return keyword. (An expression is code that produces a value, like w[-1]. A statement is a whole instruction, like x = 5. The value of the expression is what the lambda returns.) Use a lambda for short throw-away functions passed straight into something like sorted. For anything longer, or that you reuse, write a def with a name.

  4. 4

    map(func, items) applies func to every item and filter(func, items) keeps the items for which func is true. Both return lazy iterators (Topic 9.1: objects that make each value only when asked), so wrap them in list(...) to see the values. Python programmers usually prefer a comprehension (Topic 4.7) for the same job: [n * n for n in numbers if n % 2 == 0] is clearer than list(map(lambda n: n * n, filter(lambda n: n % 2 == 0, numbers))). Reach for map when you already have a named function to apply, such as map(str, numbers).

  5. 5

    A function can also be returned from another function, which is how you pick behaviour at run time (pick("loud") returns shout). A dictionary of functions is a dispatch table: TOOLS[name](**arguments) looks up the function by its name and calls it. This is how AI agents (programs that let a model decide which steps to take) run tools: the model sends back a name and a dict of arguments, and your code looks up the matching function.

  6. 6

    Any object you can call with brackets is callable. Functions, lambdas, built-ins such as len, and (later) classes and objects with a __call__ method are all callable. The built-in callable(x) tells you if you can call x. A common slip is writing key=len() (calling it now, with no argument) when you meant key=len (passing the function itself).

Explain it without notes

01

What does it mean that functions are first-class values in Python?

02

What is a lambda, and when should you prefer def?

03

What is the difference between key=len and key=len()?

04

When would you choose a comprehension over map and filter?

05

How does a dispatch table work, and why is it used for tool calling?

Practice

01

Write apply_twice(func, value) that returns func(func(value)). Test it with a function that adds 3, and with str.upper.

02

Sort the list ["pear", "fig", "banana", "kiwi"] by length, and break ties alphabetically, using one sorted call.

03

Make a dispatch table OPS for "add", "sub" and "mul" (use lambdas) and a function calculate(op, a, b) that looks the operation up and calls it. Unknown operations should return None.

Trade-offs

  • ↔

    Lambda versus named function. A lambda keeps a tiny rule next to its use but has no name or docstring and shows as <lambda> in tracebacks. A named function is better as soon as the rule has meaning of its own.

  • ↔

    Dispatch table versus if/elif chain. A dictionary of functions is shorter and easy to extend, and lets you list the available actions. An if/elif chain is clearer for two or three branches with different logic.

Done when you can

  • Done when you can pass, store and return a function, and explain brackets versus no brackets.

  • Done when you can sort with key= using len and a lambda.

  • Done when you can convert a map/filter into a comprehension.

  • Done when you can write a dispatch table and call a function by name from a dict.

  • Done when you can explain why key=len() fails.