Functions as First-Class Objects
Source: Fluent Python, 2nd Edition — Chapter 7 by Luciano Ramalho
A first-class object is a program entity that can be created at runtime, assigned to a variable or stored in a data structure, passed as an argument to a function, and returned as the result of a function. Integers, strings, and dicts already work this way in Python — nothing special. The point of this chapter is that functions work this way too.
That single fact — functions are just objects — is what makes higher-order functions, decorators, and a good chunk of Python's standard library (sorted, functools, operator) possible.
Treating a function like an object
A def statement creates a function object and binds it to a name, exactly the way any other assignment binds a name to an object. The function has attributes (__doc__ among them) and a type, just like any other value.
🧪 A function is an instance of the function class
fact = factorial doesn't copy the function — it's a second label on the same object, exactly like the aliasing from the last chapter. And because factorial is just an object, it can be handed straight to map(function, iterable), which calls the function once per item and returns an iterable of results.
Mini quiz — functions as objects
First-class functions
1 / 2fact = factorial then fact(5)
Pick an answer to see if you are right.
Challenge: factorials via map
Build the factorials from 0! to n! using map(factorial, range(n + 1)), not a loop.
Higher-order functions
A function that takes a function as an argument, or returns one, is a higher-order function. sorted(iterable, key=...) is the one you'll reach for constantly: key is called once per item, and its return value — not the item itself — decides the order.
🧪 Sorting by length, then by reversed spelling
Note what key=reverse does not do: it never changes the words themselves, only what's compared while sorting — which is why words that rhyme (raspberry, strawberry, cherry) cluster together without any word being altered.
Mini quiz — higher-order functions
key= functions
1 / 2sorted(words, key=len) sorts by
Pick an answer to see if you are right.
Challenge: sort by reversed spelling
Write reverse(word) and use it as a key= function to sort words by their reversed spelling.
Modern replacements for map, filter, and reduce
map and filter still exist, but a list comprehension (or generator expression) usually reads better and does the same job without a lambda. reduce moved from a built-in to functools in Python 3 — its most common use, summation, is better served by sum. all and any cover two other common reductions.
🧪 map/filter/reduce next to their comprehension equivalents
all(iterable) is True if every element is truthy (all([]) is True); any(iterable) is True if at least one element is truthy (any([]) is False).
Mini quiz — map/filter/reduce vs comprehensions
Reducing built-ins
1 / 2The most common use of reduce — summation — is better written as
Pick an answer to see if you are right.
Challenge: sum without reduce
Return the sum of the squares of the even numbers in nums, using sum() with a generator expression — no functools.reduce.
Anonymous functions
lambda builds a function object inline. The catch: the body must be a single expression — no while, no try, no = assignment. Outside the narrow case of a short key= argument, a lambda usually hurts readability more than it helps.
🧪 The same sort, without naming reverse
If a lambda is hard to read, Fredrik Lundh's refactoring recipe is worth memorizing: write a comment explaining what it does, think of a name that captures that comment, convert the lambda to a def with that name, then delete the comment — the name alone should now carry the meaning.
Mini quiz — lambda
Anonymous functions
1 / 2A lambda body can contain
Pick an answer to see if you are right.
Challenge: one-line reverse sort
Sort words by reversed spelling using a lambda directly as key= — no separate def.
The nine flavors of callable objects
() isn't reserved for def-made functions. As of Python 3.9, the data model lists nine callable types: user-defined functions, built-in functions (len), built-in methods (dict.get), methods, classes (calling one runs __new__ then __init__), class instances with __call__, generator functions, native coroutine functions (async def), and asynchronous generator functions (async def with yield). The safest way to check any of them is the callable() built-in.
🧪 callable() sees through all of them
str is a class, and classes are callable — calling one is how you get an instance. gen is a generator function: calling gen() doesn't run its body yet, it returns a generator object that only does work when you iterate it.
Mini quiz — callable types
Nine flavors
1 / 2Calling a class, like str() or MyClass()
Pick an answer to see if you are right.
User-defined callable types
Give a class a __call__ method and its instances become callable — a function-like object that can also carry state between calls. That combination is exactly what decorators (Chapter 9) need.
🧪 BingoCage: state plus call
bingo() is shorthand for bingo.pick() — that's all __call__ does. The state (self._items) survives between calls, which is the whole point: a plain function can't remember anything from one call to the next without extra machinery like a closure.
Mini quiz — call
Callable instances
1 / 2Defining __call__ on a class means
Pick an answer to see if you are right.
Challenge: callable counter
Build Counter with a __call__ that increments self.count and returns it, so each call yields the next count.
From positional to keyword-only parameters
A parameter name after a bare *content (or a lone *) can only be passed by keyword — it will never soak up an unnamed positional argument. Since Python 3.8, a / in the signature marks everything before it positional-only — it can never be passed by keyword, matching how built-ins like divmod(a, b) have always worked.
🧪 Keyword-only b, and a small HTML tag builder
f(1, 2) would raise TypeError — b demands a keyword. class_ (trailing underscore because class is reserved) works the same way in tag: it can only ever be passed as class_=..., never as a stray positional argument swallowed by *content.
Mini quiz — parameter flavors
Keyword-only and positional-only
1 / 2def f(a, *, b): f(1, 2)
Pick an answer to see if you are right.
Challenge: keyword-only greeting
Make loud keyword-only by putting * before it in the signature.
The operator module
Sorting or extracting by a field is common enough that writing lambda fields: fields[1] every time gets old. operator.itemgetter(i) builds that function for you (it works on anything with __getitem__, not just sequences); operator.attrgetter('a.b') does the same for attributes, including dotted, nested lookups; operator.methodcaller('name', *args) builds a function that calls .name(*args) on whatever it's given.
🧪 itemgetter and methodcaller
operator also has plain function equivalents for arithmetic — add, mul, and_, and friends — so reduce(mul, range(1, n + 1)) doesn't need a throwaway lambda a, b: a * b just to compute a factorial.
Mini quiz — operator module
itemgetter, attrgetter, methodcaller
1 / 2itemgetter(1) builds a function that
Pick an answer to see if you are right.
Challenge: sort cities by country code
Sort (name, cc, pop) tuples with operator.itemgetter(1) and print just the names, in order.
Freezing arguments with functools.partial
functools.partial(fn, *args, **kwargs) returns a new callable with some of fn's arguments already filled in. It's how you adapt a function that needs N arguments to a spot that only ever hands it N - k.
🧪 Freezing the first argument of mul
mul takes two arguments, so it can't be passed straight to map(fn, iterable) where fn must take exactly one. partial(mul, 3) fixes the first argument at 3, producing a genuine one-argument function — triple — that map can call directly. The resulting functools.partial object exposes .func, .args, and .keywords if you ever need to inspect what got frozen.
Mini quiz — functools.partial
partial application
1 / 2partial(mul, 3) returns
Pick an answer to see if you are right.
Challenge: freeze an argument
Use functools.partial with operator.add to build a function that adds a fixed n to whatever it's given, then map it over a list.
Chapter summary
- Functions are first-class objects — assignable, storable, passable, returnable — exactly like ints or dicts.
- Higher-order functions (
sorted(key=...),map,functools.partial) take or return functions.sorted/min/maxwith akeyare the ones you'll actually use daily. - List/generator comprehensions usually replace
map/filtermore readably;sum,all,anycover the commonreducecases. lambdais a single-expression function literal — great for a shortkey=, awkward for anything more.- There are nine callable flavors in Python;
callable()recognizes all of them. - A class with
__call__makes its instances invocable like functions, while still holding state between calls. *argscapture extra positionals,**kwargscapture extra keywords, a bare*(or a named*param) forces everything after it to be keyword-only, and/forces everything before it to be positional-only.operator.itemgetter/attrgetter/methodcallerreplace one-offlambdas for extracting items, attributes, and calling methods.functools.partialfreezes some of a callable's arguments, adapting it to fit an API that expects fewer.
Exercises
Return the same result as list(map(str.upper, filter(lambda w: len(w) > 3, words))), but as a single list comprehension.