Passing Behavior to Templates
Function objects can be used as arguments to algorithms, so the algorithm supplies the general process and the function object supplies the specific behavior.
Try it: Classes, Instances and Attributes
How a class creates instances with __init__ and self, why an attribute read checks the instance's __dict__ before the class, why assigning through an instance never changes the class, how a class-level list is shared by every instance, and why obj.method(x) is Class.method(obj, x).
How it works
- Dog(...) makes a new, empty instance and calls Dog.__init__(new_object, ...); self.name = ... stores into that object's __dict__.
- Reading obj.attr looks in obj.__dict__ first, then in the class.
- Assigning obj.attr = value always writes obj.__dict__, so it shadows a class attribute for that one object only.
- Mutating a class-level list (self.tricks.append) changes the one list every instance shares.
- obj.method is found on the class and bound to obj, so obj.method(x) runs Class.method(obj, x).
Default run (30 steps): Ready: class Dog and the statements you picked. Watch where each attribute is found and where each assignment writes. … Finished. Printed: ['sit'] / Wolf / Canis familiaris / Fido is 5.
Simplified: The lab's own small Python interpreter runs a fixed, hand-written script (you choose the variant and the values, never the code); nothing is executed as real Python. Every script was run in CPython 3.12 with the same inputs and the lab reproduces its output and error messages. Object numbers are illustrative, not real id() values.
Loading the simulation…
Try it: Functions as Values: Lambda
That a lambda (like def) evaluates to a function object that a name can refer to and that can be passed to other functions — apply(f, x), twice(f, x), map(f, xs), sorted(xs, key=f) — and that each call is traced by binding the argument, substituting it into the single expression and evaluating in operator order.
How it works
- lambda x: expression evaluates to a function object (named <lambda>); nothing inside it is computed yet. f = ... gives it a name, exactly as def f(x): return expression would.
- Writing f without parentheses passes the function object itself; inside apply or twice the parameter func refers to the same object.
- A call binds the argument to the parameter x, substitutes it into the expression, and evaluates by operator precedence (* and % before + and -, parentheses first).
- The value of the expression is the call's return value; there is no other body.
- map(f, xs) calls f once per item and keeps the results in order; sorted(xs, key=f) calls f once per item to get keys, orders by the keys (ties keep their order) and returns the items.
Default run (9 steps): The program is ready: a lambda written inline, then f(x). … print shows 7.
Simplified: Four one-parameter lambdas with editable integer constants, small int arguments (at most 6 items). Each substituted expression the lab shows was evaluated by CPython 3.12 and has the same value as the real call; printed results and ZeroDivisionError messages match CPython.
Loading the simulation…
Why Algorithms Need Outside Behavior
Many general-purpose algorithms do not know in advance exactly what to do with each element they process. A sorting routine may need to decide which of two elements comes first, and a search routine may need to test each element against some condition. When the algorithm is written once for many element types, the part of its behavior that varies is usually handed to it from outside. Function objects and lambdas are two ways of supplying that small piece of behavior at the point where the algorithm is called.
What do you think happens?
A sort routine is written once and works for many kinds of elements. What must the caller still provide to it?
Reveal answer
A rule that decides which of two elements comes first
The source describes a sorting routine that may need to decide which of two elements comes first. The algorithm supplies the general process, and the caller supplies the specific comparison rule.
The rest of this article follows one question. When you call an algorithm that needs a behavior argument, should that behavior be a named object defined somewhere else, or should it be written directly inside the call? The source answers this clearly, and you will be able to justify the answer by the end of the article.
What a Function Object Is
A function object is a value that can be called like a function and that is passed to an algorithm as an argument. Function objects can be used as arguments to algorithms, so the algorithm supplies the general process and the function object supplies the specific behavior.
Because the algorithm only needs something it can call, the same algorithm can be reused with many different behaviors. The template (the generic algorithm) supplies the reusable process, and each function object supplies one particular rule for that process to follow. Both forms of supplying behavior, a named function object and a lambda, give the algorithm the same kind of thing: a callable behavior. What differs is where that behavior lives in the source code and how many places refer to it.
The algorithm owns the loop and the bookkeeping. The function object owns only the rule that is applied to each element. Keeping these responsibilities separate is what lets one algorithm serve many different purposes.
Tracing the Algorithm's Loop
To see how control moves between an algorithm and a function object, consider a generic counting routine. It walks through a collection and asks the supplied rule whether each element should be counted. The routine is written once, and the rule is provided by the caller. The diagram below shows what happens on each iteration.
Notice the division of labor. The algorithm handles the loop, the increment, and the decision to stop. The function object is called once per element and does nothing else. The algorithm never needs to know what the rule means, only that it can call it and read a true or false answer.
Writing a Function Object by Hand
The following example is an original illustration of the idea. The generic routine accepts any callable as its second argument. The function object is a small class that stores a limit and implements the call behavior, so that an instance can be used like a function.
2count_matchingis the general process and does not know what counts as a match- GreaterThan stores its own state, the limit, inside the object
__call__is the method that lets the algorithm call the object like a function- The call passes an instance as the rule, so the same routine can be reused with a different limit
Tracing the call on a short list
Trace count_matching on the list [3, 8, 5, 9] with the rule GreaterThan(5). Track each call to the rule and the running count.
- 1Element 3
The rule computes 3 > 5, which is false. The count stays at 0.
- 2Element 8
The rule computes 8 > 5, which is true. The count rises to 1.
- 3Element 5
The rule computes 5 > 5, which is false. The count stays at 1.
- 4Element 9
The rule computes 9 > 5, which is true. The count rises to 2.
- 5End of list
The loop stops because no elements remain, and the routine returns the count.
The routine returns 2. The rule was called exactly four times, once per element.
Replacing the Object with a Lambda
A lambda is a compact way to write a simple function object directly inside the call. The same counting routine can be used without defining a separate class, because the rule is a single expression needed in one place.
2- The algorithm is unchanged; only the argument differs
- The lambda produces the same callable behavior as the GreaterThan instance
- The rule is written at the exact place where the algorithm is called
Named Object or Lambda: The Deciding Question
The source gives a short rule for this choice. A lambda is the appropriate choice for a simple function object needed in one place only. A behavior needed in several places is better defined once as a named function object and passed wherever it is needed. The deciding question is therefore how many places in the program need the behavior.
Read the comparison column by column. The named function object costs more code up front, but that code is written once and can be passed to every call that needs the same rule. The lambda costs almost nothing to write, but it lives only at the call where it appears. If a second call needs the same rule, copying the lambda creates duplicates that must be kept in step, which is the situation the source's rule is meant to prevent.
Mistakes When Passing Behavior
Common mistake Using a lambda for a rule needed in many places
What it looks likeThe same lambda v > 5 is copied into five different calls across a program.
Why it failsThe source says a behavior needed in several places is better defined once as a named function object. Copies can drift apart when one is edited and the others are not.
Do this insteadDefine a named function object once and pass that object to every call that needs the rule.
Common mistake Creating a named class for a one-off rule
What it looks likeA full class is written for a single comparison used in one call.
Why it failsThe source's deciding question is how many places need the behavior. For one place, the extra structure adds code without adding reuse.
Do this insteadUse a lambda when the rule is simple and needed in only one place.
Common mistake Passing the result of the rule instead of the rule itself
What it looks likecount_matching([3, 8, 5, 9], GreaterThan(5)(3)) passes a boolean where a callable is expected.
Why it failsThe algorithm needs something it can call for each element. A single computed value gives it nothing to call.
Do this insteadPass the callable object or lambda itself, without calling it at the point of the argument.
Common mistake Forgetting that the algorithm, not the caller, controls how many times the rule runs
Choose and Justify
A program sorts a list of product names by length in two places: once for the on-screen catalog and once for the export report. Should the length rule be a named function object or a lambda? Explain your choice using the deciding question from this article.
Hints
- Ask how many places in the program need the behavior
- The rule is needed in two places, which is more than one
- The source says a behavior needed in several places is better defined once as a named function object
Write a short justification of one sentence for each of these two cases: a rule that filters out empty strings in a single place, and a rule that checks whether a number is even and is used in three different algorithm calls.
Hints
- The single-place rule is a candidate for a lambda
- The three-call rule is a candidate for a named function object defined once
Key Takeaways
- An algorithm can accept a function object as an argument, so the algorithm supplies the general process and the function object supplies the specific behavior.
- On each iteration, the algorithm passes an element to the function object, reads its result, and then continues its own loop.
- A lambda is the appropriate choice for a simple function object needed in one place only.
- The deciding question is how many places in the program need the behavior; a behavior needed in several places is better defined once as a named function object.