Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 8

First-Class Functions

Recommended resources

Welcome to the second module of our course. Having built a solid foundation in Python's low-level object and memory model, we now shift our focus up the abstraction ladder. In the previous module, we delved into the "what" and "how" of Python objects—their identity, type, value, and lifecycle. We concluded by learning how to measure memory usage with tools like tracemalloc, a crucial skill for building robust systems.

This module, "Advanced Functions, Closures & Decorators," explores some of Python's most elegant and powerful features. We begin today by examining a concept that underpins all of them: functions as first-class objects. Your experience with Python's object-oriented features and even callback patterns in JavaScript has already exposed you to this idea. Our goal today is to formalize this understanding and explore its implications, setting the stage for the more complex topics of closures and decorators.

By the end of this lesson, you will be able to explain precisely what it means for functions to be "first-class" in Python and demonstrate this by using them as variable assignments, function arguments, and return values.

Everything is an Object, Including Functions

In Module 1, we established that everything in Python is an object. Integers are objects, strings are objects, lists are objects, and, crucially, functions are also objects. When you define a function using a def statement, you are doing two things:

  1. Creating a function object in memory.
  2. Binding a name (the function name) to that object.

This is fundamentally no different from x = 10, which creates an integer object and binds the name x to it.

Just as `x` is a name that references an integer object and `word` references a string object, a function name like `my_function` is simply a name that references a function object in memory.

Because functions are objects, they have the same rights and privileges as any other object. In programming language theory, this is often described by saying functions are first-class citizens (or first-class objects).


What Makes a Function "First-Class"?

A language is said to have first-class functions if it treats them like any other data type. The video below by Corey Schafer provides a crisp definition.

Programming Terms: First-Class Functions

This video introduces the concept of first-class functions by breaking down the formal definition into practical terms.

Watch the initial segment that defines the term, from the definition to the summary of what it means in practice.

As the video explains, being a first-class citizen means a function supports three fundamental operations:

  1. It can be assigned to a variable.
  2. It can be passed as an argument to another function.
  3. It can be returned as a value from another function.

Let's explore each of these properties in detail.

1. Assigning Functions to Variables

Since a function name is just a label for a function object, you can easily attach another label to it. This is the simplest demonstration of functions being objects.

def greet(name):
    return f"Hello, {name}"




# Assign the function object `greet` to a new variable `say_hello`
say_hello = greet




# Now, `say_hello` and `greet` both point to the same function object
print(id(greet))
print(id(say_hello))




# You can call the function using the new name
print(say_hello("World"))

A common point of confusion here is the use of parentheses. Remember, greet refers to the function object itself, while greet() executes the function and evaluates to its return value.

The following segment of the Corey Schafer video provides a clear, hands-on demonstration of this.

Programming Terms: First-Class Functions

This part of the video walks through the mechanics of assigning a function to a variable, highlighting the critical difference between referring to the function and calling it.

Watch the section from assigning to a variable. Notice how he uses both Python and JavaScript, which should resonate with your full-stack experience.

This ability to treat functions as data is surprisingly powerful. You can store them in data structures like lists or dictionaries, just as you would with any other object.

In this console session, the function objects `say_hello` and `be_awesome` are stored inside a list named `my_list`, demonstrating that they can be handled like any other data type.

2. Passing Functions as Arguments (Higher-Order Functions)

This is where the concept starts to unlock more expressive programming patterns. A function that accepts another function as an argument, or returns a function, is called a higher-order function.

You've already used these extensively, perhaps without thinking about it in these terms. For example, Python's built-in sorted() function can take a key argument, which is a function used to determine the sorting order.




# A list of dictionaries
users = [
    {"username": "samuel", "posts": 12},
    {"username": "aida", "posts": 25},
    {"username": "chi", "posts": 5},
]




# Sort by username (default string sorting)
print(sorted(users, key=lambda user: user["username"]))




# Sort by number of posts, descending
print(sorted(users, key=lambda user: user["posts"], reverse=True))

Here, sorted is a higher-order function, and we are passing a lambda (an anonymous function) as an argument to customize its behavior.

For a more structured example, let's explore how to build our own higher-order function.

First-Class Functions, Higher-Order Functions, and ...

This article provides a clear, code-driven explanation of passing functions as arguments.

Read the subsection Passing Functions as Arguments. The map_function example is a classic illustration of this principle, abstracting the iteration logic away from the operation being performed.

This pattern is not just an academic curiosity; it has profound implications for software design. For instance, it provides a more concise and often more flexible alternative to the classic Strategy design pattern. The following video by ArjanCodes provides an excellent demonstration of refactoring a class-based strategy into a more functional approach. This is exactly the kind of practical trade-off analysis that distinguishes senior-level thinking.

You Can Do Really Cool Things With Functions In Python

In this video, a trading bot's logic is refactored from using classes for different strategies to simply passing strategy functions directly.

Watch the segment from the refactoring. Pay close attention to how defining a TradingStrategy function type and passing functions directly to the TradingBot simplifies the code and increases its flexibility.

3. Returning Functions from Other Functions

The final property of a first-class function is its ability to be returned from another function. This allows you to create "function factories"—functions that generate and configure other functions. This is the direct mechanism that enables both closures and decorators, which we will study in upcoming lessons.

A common use case is creating specialized versions of a function.

First-Class Functions, Higher-Order Functions, and ...

This reading continues with a practical example of a function that returns another function.

Read the short section Returning Functions from Other Functions. The create_multiplier example is a perfect demonstration of a function factory.

When you call create_multiplier(2), it doesn't return a value like 10 or 20. It returns a completely new function object, multiplier, which is "pre-configured" with factor=2. This returned function can then be stored in a variable and called later.

Corey Schafer's video provides another excellent, practical example of this pattern, which also serves as a gentle introduction to the concept of closures.

Programming Terms: First-Class Functions

This final segment of the video demonstrates how to return a function from another function using a simple logger and a more practical HTML tag generator.

Watch from returning a function. The html_tag example is particularly insightful, showing how this pattern can be used to create reusable, configurable utility functions. Notice his comment that the inner function "remembers" the tag—this is the essence of a closure, which we will dissect in our next lesson.


Conclusion

In this lesson, we have formally established the concept of functions as first-class objects in Python. This isn't just a piece of trivia; it's a core design principle of the language that enables a more expressive, flexible, and functional style of programming. As a developer with a background in Django and modern frontend frameworks, you can draw parallels to passing view functions to URL routers or passing callback functions to handle events.

Here are the key takeaways:

  • Functions are Objects: In Python, functions are objects of type function and can be treated like any other piece of data.
  • First-Class Properties: This means they can be assigned to variables, passed as arguments to other functions, and returned as values from other functions.
  • Higher-Order Functions: Functions that operate on other functions (by taking them as arguments or returning them) are called higher-order functions. They are a powerful tool for abstraction and code reuse.
  • Foundation for Advanced Concepts: A solid grasp of first-class functions is essential for mastering closures, decorators, and functional programming patterns that are prevalent in modern, high-level Python code.

In our next lesson, we will build directly on this foundation. We'll explore closures, examining how an inner function can "remember" and access the variables from the enclosing scope where it was created, even after that scope has completed its execution.