Concepts / Passing Behavior to Templates

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.

  • Programming
Interactive lab

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

  1. Dog(...) makes a new, empty instance and calls Dog.__init__(new_object, ...); self.name = ... stores into that object's __dict__.
  2. Reading obj.attr looks in obj.__dict__ first, then in the class.
  3. Assigning obj.attr = value always writes obj.__dict__, so it shadows a class attribute for that one object only.
  4. Mutating a class-level list (self.tricks.append) changes the one list every instance shares.
  5. 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.

Educational simulation

Loading the simulation…

Interactive lab

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

  1. 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.
  2. Writing f without parentheses passes the function object itself; inside apply or twice the parameter func refers to the same object.
  3. A call binds the argument to the parameter x, substitutes it into the expression, and evaluates by operator precedence (* and % before + and -, parentheses first).
  4. The value of the expression is the call's return value; there is no other body.
  5. 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.

Educational simulation

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?

  • Nothing, because the routine already knows how to order everything

  • A rule that decides which of two elements comes first

  • A copy of every element in the collection

  • A new version of the sort routine for each element type

Reveal answer
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

Definition

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.

Key point

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.

Take next elementfrom the collectionCall the rulerule applied to elementRule returns true?Increase countMore elements?Return countelement passed inresult returnedyesnoyesno
When the algorithm runs, control passes from its loop to the function object and back again on every element. The loop keeps going until the collection is exhausted.

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.

python
Output (expected)
2
Notes on the code
  • count_matching is 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
Worked example

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.

  1. 1
    Element 3

    The rule computes 3 > 5, which is false. The count stays at 0.

  2. 2
    Element 8

    The rule computes 8 > 5, which is true. The count rises to 1.

  3. 3
    Element 5

    The rule computes 5 > 5, which is false. The count stays at 1.

  4. 4
    Element 9

    The rule computes 9 > 5, which is true. The count rises to 2.

  5. 5
    End of list

    The loop stops because no elements remain, and the routine returns the count.

Result

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.

python
Output (expected)
2
Notes on the code
  • 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
lambdastarts an anonymousfunctionvparameter the algorithmsupplies:separates parameters frombodyv > 5single expression givingthe result
Each token in the lambda plays a role. The parameter names the input the algorithm passes in, and the body is the single expression whose value is the result of the call.

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.

Defined onceelsewhereWritten at the callSeveral lines of codeOne short expressionReused in many placesNeeded in one place
Both forms supply a callable behavior to the algorithm. They differ in where the behavior is defined, how much code it takes, and how many places can reuse it.

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 like

    The same lambda v > 5 is copied into five different calls across a program.

    Why it fails

    The 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 instead

    Define 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 like

    A full class is written for a single comparison used in one call.

    Why it fails

    The source's deciding question is how many places need the behavior. For one place, the extra structure adds code without adding reuse.

    Do this instead

    Use 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 like

    count_matching([3, 8, 5, 9], GreaterThan(5)(3)) passes a boolean where a callable is expected.

    Why it fails

    The algorithm needs something it can call for each element. A single computed value gives it nothing to call.

    Do this instead

    Pass 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

MEDIUM

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
EASY

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.

Related concepts

More concepts starting with P