CHAPTER 17 · BEGINNER4 min read

Flexible arguments and functional tools

Design functions that accept variable inputs, pass behavior as a value, and choose readable transformations.

Python 3.12+Free to readModule 4 of 13
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Use *args, **kwargs, and keyword-only parameters without losing a clear contract.
  • Pass functions to sorted, map, and filter, and explain when a comprehension is clearer.
  • Use any, all, and functools.reduce for small, well-understood folds.

01Collect arguments deliberately

A function signature is a public interface. *args collects extra positional arguments into a tuple, while **kwargs collects extra keyword arguments into a dictionary. Parameters after a bare * are keyword-only, which is useful for options that should be named at the call site. Python also supports positional-only parameters before a /, but use that boundary only when the parameter name is not part of the contract.

Flexibility is not the same as vagueness. Keep the important inputs explicit, give options meaningful defaults, and validate the values that remain flexible. A function that accepts **options but silently ignores unknown keys is harder to debug than one that documents and checks the supported set.

summaries.pyPython
def summarize(title, *scores, passing=60):    if not scores:        return f"{title}: no scores"    average = sum(scores) / len(scores)    status = "pass" if average >= passing else "review"    return f"{title}: {average:.1f} ({status})"print(summarize("Ada", 82, 91, 76))print(summarize("Lin", 54, 61, passing=70))
Outputstdout
Ada: 83.0 (pass)
Lin: 57.5 (review)

02A function can be passed to another function

Functions are objects. You can store one in a variable, return it from another function, or pass it as an argument. The key parameter of sorted is a function that receives one item and returns the value to sort by. A lambda is a small anonymous function expression; it is convenient for a one-line key, but a named function is easier to test and reuse when the rule has a name.

The same idea powers max, min, and many library APIs. Keep key functions free of side effects: sorting may call a key function once per item, and the caller expects a stable answer. When a sort uses several fields, return a tuple from the key. Python's sort is stable, so sorting by a secondary field first and then by the primary field also works.

ranking.pyPython
records = [("Ada", 92), ("Lin", 81), ("Sam", 57)]ranked = sorted(records, key=lambda item: item[1], reverse=True)print(ranked)print([name for name, score in ranked if score >= 60])
Outputstdout
[('Ada', 92), ('Lin', 81), ('Sam', 57)]
['Ada', 'Lin']

03Map, filter, and comprehensions

map applies a function to every item and returns a lazy iterator. filter keeps items for which a predicate is true. Both are useful when the function already has a name, such as str.strip or math.sqrt. A list comprehension is often more readable when you need a simple expression and a condition in the same place. zip pairs items from parallel iterables, and enumerate supplies a position while you iterate.

functools.reduce folds a sequence into one value by repeatedly combining an accumulator with the next item. Use it for genuinely associative operations such as a product or a sum of structured values, and pass an explicit initial value when the sequence may be empty. any and all are clearer than writing a loop that returns a boolean. Two compact forms also belong in your vocabulary: a conditional expression such as `status = "pass" if score >= 60 else "review"` chooses one of two values, and the walrus operator (`if (count := len(items)) > 0:`) assigns a name inside a larger expression. Use both in small doses; nested conditional expressions and assignments hidden inside complicated conditions make code harder to read.

cleanup.pyPython
from functools import reduceraw = [" 8 ", "", "12", "oops"]cleaned = map(str.strip, filter(None, raw))print(list(cleaned))values = [2, 3, 4]print(reduce(lambda left, right: left * right, values, 1))names = ["Ada", "Lin"]scores = [92, 81]print(list(zip(names, scores)))for position, name in enumerate(names, start=1):    print(position, name)print(any(value > 3 for value in values))print(all(value > 0 for value in values))
Outputstdout
['8', '12', 'oops']
24
[('Ada', 92), ('Lin', 81)]
1 Ada
2 Lin
True
True

04Keep the interface readable

A flexible helper should make the common call short and the unusual call explicit. Prefer a small number of named options over a large **kwargs bag. If a function accepts a callback, document when it is called, what arguments it receives, and whether it may raise. Those details are part of the contract even when the callback is supplied by the caller.

Functional style is not a requirement. A for loop that updates a result is often the clearest choice, especially when it includes logging, validation, or an early exit. Choose the smallest construct that makes the data flow obvious. The goal is a function another person can call correctly without reading its implementation.

tags.pyPython
def tag(*parts, sep=" · "):    clean = [part.strip() for part in parts if part.strip()]    return sep.join(clean) or "(untagged)"print(tag(" python ", "", "cli", sep="/"))print(tag())
Outputstdout
python/cli
(untagged)
LET IT CLICK

The ideas worth keeping.

  • Use *args and **kwargs only when the call shape genuinely varies, and keep the important parameters explicit.
  • Functions are values: sorted, map, filter, and callbacks become easier once you read them as behavior passed into another operation.
  • Choose comprehensions for small inline transformations and named functions for reusable rules.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

A reusable label

Write make_label(*parts, sep=' / ') that strips each non-empty part and joins the results. Return '(empty)' when nothing remains. Demonstrate the default and a custom separator.

your_turn_1.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
def make_label(*parts, sep=" / "):    values = [part.strip() for part in parts if part.strip()]    return sep.join(values) or "(empty)"print(make_label("  data", "", "cleaning "))print(make_label("api", "v2", sep="-"))

*parts collects the positional inputs into a tuple. Keyword-only sep keeps the separator from being mistaken for another label.

02

Sort by two fields

Given a list of dictionaries with name and score fields, return the names ordered by score descending and then by name ascending, including only scores of at least 60.

your_turn_2.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
people = [    {"name": "Ada", "score": 92},    {"name": "Lin", "score": 92},    {"name": "Sam", "score": 57},]ordered = sorted(people, key=lambda person: (-person["score"], person["name"]))print([person["name"] for person in ordered if person["score"] >= 60])

The key returns a tuple: negating the score makes the descending order explicit, and the name breaks ties in ascending order.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01Inside def f(*args, **kwargs), what type is args?
02What does sorted(words, key=len) use as the comparison value?
03When is map(str.strip, items) clearer than a comprehension?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

A reusable label

Write make_label(*parts, sep=' / ') that strips each non-empty part and joins the results. Return '(empty)' when nothing remains. Demonstrate the default and a custom separator.

EXPECTED OUTPUT
make_label("  data"  "cleaning ")
make_label("api" v2 sep="-")
challenge.pyPython 3
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this chapter, when you want a second angle or the full details.

One chapter closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock chapter completion.