-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
81: First-Class Functions and Passing Functions as Arguments
I've seen this specific bug a dozen times in code reviews. A developer wants to make their code flexible by passing a function into another function—which is a powerful pattern—but they trip over a tiny syntax detail that changes the entire meaning of the line.
def double(n):
return n * 2
def apply_operation(numbers, operation):
return [operation(x) for x in numbers]
nums = [1, 2, 3, 4]
# Oops! Here is where it goes wrong:
result = apply_operation(nums, double())
print(result)
The 'TypeError: double() missing 1 required positional argument' Trap
If you run the code above, Python is going to scream at you. You'll get a TypeError saying double() is missing an argument. Why? Because of those parentheses: double().
In Python, when you add parentheses to a function name, you aren't talking about the function; you are executing the function right then and there. In the example above, Python tries to run double() first, and only then tries to pass the result of that call into apply_operation. Since double requires an input n, it crashes before apply_operation even starts.
Even if double didn't require an argument, you'd still have a problem. If double() returned the number 10, you'd essentially be calling apply_operation(nums, 10). Then, inside the list comprehension, Python would try to do 10(x), which would throw a different error: 'int' object is not callable.
Passing the Reference, Not the Result
To fix this, we have to treat the function as a "first-class citizen." In Python, functions are just objects, like strings or lists. You can move them around and pass them into other functions without actually running them yet.
The fix is simple: remove the parentheses.
def double(n):
return n * 2
def apply_operation(numbers, operation):
# 'operation' is now a reference to the function we passed in
return [operation(x) for x in numbers]
nums = [1, 2, 3, 4]
# Pass the function itself, not the result of calling it
result = apply_operation(nums, double)
print(result) # Output: [2, 4, 6, 8]
By passing double (no parentheses), you're handing a reference to the function over to apply_operation. Now, apply_operation holds that reference in the variable operation and decides when and how to call it.
Why This Actually Matters in Real Projects
You might be thinking, "Why not just write a double_list function?" In a small script, sure. But in a real-world codebase, this is how you build plugins, flexible API wrappers, or custom sorting logic.
Think about Python's built-in sorted() function. It has a key argument. When you pass a function to key, you're telling Python: "Don't just sort these items; first, run every item through this function, and sort them based on the result." You aren't calling the key function yourself; you're giving sorted() the tool it needs to do the job.
I generally recommend this approach whenever you find yourself writing three different functions that do almost the exact same thing, with only one small calculation being different. Instead of duplicating the logic, pull that one difference out into its own function and pass it in as an argument.
📋 Practical Task
Building a Flexible Log Message Formatter
Imagine you are building a logging system. Depending on the environment (Development, Production, or Debug), the log messages need to be formatted differently. Instead of using a bunch of if/else statements inside your logger, you're going to use first-class functions to handle the formatting.
Your Task:
- Create three formatting functions:
dev_format(text): Returns the text wrapped in brackets, e.g.,"[DEV] message".prod_format(text): Returns the text in all uppercase, e.g.,"MESSAGE".debug_format(text): Returns the text with a timestamp prefix (you can just hardcode a string like"12:00 - message").
- Create a function called
log_message(message, formatter). This function should take the string message and the formatter function as arguments, and return the result of applying the formatter to the message. - Demonstrate that your
log_messagefunction works by calling it three times, passing a different formatter each time.
There are no comments for now.