7 Functions
Functions are presented as a practical cornerstone of programming because they reduce repetition, improve maintainability, and let developers think at higher levels of abstraction. The chapter frames functions as the “verbs” of programs, enabling you to package behavior behind clear names. Python adds a powerful twist: functions are first-class objects that can be stored, passed, and returned, which unlocks flexible designs. While Python doesn’t support traditional function overloading, it achieves comparable flexibility via default parameters, variable positional arguments (*args), and keyword arguments (**kwargs). The chapter’s goal is to equip you with techniques that make code more reusable, expressive, and modular.
You learn how function objects track their own calling conventions and how defaults make arguments optional, with important rules such as placing defaults after required parameters and never using mutable defaults. The text emphasizes scoping through the LEGB rule (Local, Enclosing, Global, Built-ins), warning about shadowing built-ins and showing when global and nonlocal are appropriate. Inner functions and closures are used to capture state across calls, with nonlocal enabling updates to enclosing variables—a pattern that’s both powerful and common in Pythonic designs.
These ideas are cemented through hands-on patterns. An XML string builder demonstrates mixing required, optional, and keyword-only style inputs to produce flexible output. A prefix-notation calculator uses a dispatch table to map operator symbols to callables, illustrating how to treat functions as data and how the operator module avoids reimplementing basic operations. A password generator factory shows closures capturing configuration (the allowed character set) and producing specialized generators driven by random choice. The chapter closes by encouraging higher-order thinking—storing functions in collections, passing and returning them—and suggests practice tasks like map-like transformers, line processors, and function combinators to deepen mastery.
Inner vs. outer x
Python Tutor’s depiction of two password-generating functions
Summary
Writing simple Python functions isn’t hard. But where Python’s functions really shine is in their flexibility—especially when it comes to parameter interpretation—and in the fact that functions are data too. In this chapter, we explored all of these ideas, which should give you some thoughts about how to take advantage of functions in your own programs.
If you ever find yourself writing similar code multiple times, you should seriously consider generalizing it into a function that you can call from those locations. Moreover, if you find yourself implementing something that you might want to use in the future, implement it as a function. Besides, it’s often easier to understand, maintain, and test code that has been broken into functions, so even if you aren’t worried about reuse or higher levels of abstraction, it might still be beneficial to write your code as functions.
FAQ
Why are functions so important in Python?
They reduce repetition (the DRY principle), improve readability and maintainability, let you think at a higher level of abstraction (treating operations as named “verbs”), and make code easier to test and reuse.Does Python support function overloading like some other languages?
No. Redefining a function name replaces the previous definition. To handle varying inputs, use flexible parameters: optional defaults, variable positional arguments (*args), and keyword arguments (**kwargs).How do default parameter values work, and where must they appear?
Defaults make parameters optional; they are evaluated once at function definition time and reused on every call. Parameters with defaults must come after all non-default parameters in the signature.Why should I avoid mutable default arguments, and what is the safe pattern?
Mutable defaults (like list or dict) persist across calls, so changes in one call affect later calls. Use a sentinel (usually None) and create the mutable value inside the function:def add_item(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucket
When should I use *args and **kwargs?
Use *args for an unknown number of positional arguments (typically to iterate over them) and **kwargs for arbitrary named options collected into a dict. Prefer looping over *args rather than indexing, and keep required parameters explicit before *args/**kwargs.What is Python’s LEGB scoping rule, and when do global and nonlocal apply?
LEGB means name lookup occurs in Local, Enclosing (for nested functions), Global, then Built-ins. Use global inside a function to assign to a module-level name; use nonlocal inside an inner function to assign to a variable in the nearest enclosing function’s scope.How can I treat functions as data, and why is that useful?
Functions are first-class objects: store them in data structures and pass/return them. A common pattern is a dispatch table mapping strings to functions:import operator
ops = {'+': operator.add, '-': operator.sub}
result = ops['+'](2, 3) # 5
This removes long if/elif chains and makes code extensible.How do I build a simple XML string function that supports attributes?
Accept a tag, optional content, and **kwargs for attributes; format attributes from kwargs:def myxml(tag, content='', **attrs):
attrs_str = ''.join(f' {k}="{v}"' for k, v in attrs.items())
return f'<{tag}{attrs_str}>{content}</{tag}>'
How can I implement a prefix-notation calculator without if/elif?
Use a dispatch table with the operator module:import operator
OPS = {'+': operator.add, '-': operator.sub, '*': operator.mul,
'/': operator.truediv, '%': operator.mod, '**': operator.pow}
def calc(expr):
op, a, b = expr.split()
return OPS[op](int(a), int(b))
What is a closure, and how does it help with a password generator?
A closure is an inner function that remembers variables from its enclosing scope. For passwords, create a factory that captures the allowed characters and returns a generator function:import random
def create_password_generator(chars):
def make_pw(length):
return ''.join(random.choice(chars) for _ in range(length))
return make_pw
Each call to create_password_generator returns an independent generator bound to its own chars.
Python Workout, Second Edition ebook for free