Python — First-Class Functions

5 min read

In this lesson, we’ll learn about Python’s first-class functions.

A language has first-class functions when it treats functions as first-class citizens. In simple terms, that means you can pass a function as an argument to another function, return a function from another function, and assign a function to a variable or store it inside a data structure.

A bit abstract? Let’s walk through it with examples.

Create a file called first_class_function.py in any directory you like and type the following code:

first_class_function.py
def square(x):
return x * x

print(square(5))

f = square

print(square)
print(f)

Save the file. From the directory it’s in, open a terminal or command prompt and run it:

$ python first_class_function.py
25
<function square at 0x000001FE1433D1F0>
<function square at 0x000001FE1433D1F0>

We defined a tiny square function and called it. Then we assigned square to the variable f and printed both square and f. Both refer to the same function object stored at memory address 0x000001FE1433D1F0. Now let’s see whether f actually behaves like a function.

Update the code as follows and run it again:

first_class_function.py
def square(x):
return x * x

f = square

print(f(5))
$ python first_class_function.py
25

Calling f(5) invokes square — exactly as we’d hope. As mentioned, when a language supports first-class functions you can not only assign functions to variables but also pass them as arguments to other functions and use them as return values. Let’s look at the next example.

Update the code as follows and run it:

first_class_function.py
def square(x):
return x * x

def my_map(func, arg_list):
result = []
for i in arg_list:
result.append(func(i)) # call square; func == square
return result

num_list = [1, 2, 3, 4, 5]

squares = my_map(square, num_list)

print(squares)
$ python first_class_function.py
[1, 4, 9, 16, 25]

We passed square as an argument to my_map, and inside the for loop my_map called square for every item. At this point you might wonder: “Couldn’t we just write a single simple_square function and skip the indirection?” Like this:

Update the code as follows and run it:

first_class_function.py
def square(x):
return x * x

num_list = [1, 2, 3, 4, 5]

def simple_square(arg_list):
result = []
for i in arg_list:
result.append(i * i)
return result

simple_squares = simple_square(num_list)

print(simple_squares)
$ python first_class_function.py
[1, 4, 9, 16, 25]

Huh — we got the same result with simpler code. True. When you only want to apply one specific operation, a regular function like simple_square is enough. But first-class functions let you reuse multiple existing functions without rewriting the wrapper. Let’s see that benefit:

Update the code as follows and run it:

first_class_function.py
def square(x):
return x * x

def cube(x):
return x * x * x

def quad(x):
return x * x * x * x

def my_map(func, arg_list):
result = []
for i in arg_list:
result.append(func(i))  # call square; func == square
return result

num_list = [1, 2, 3, 4, 5]

squares = my_map(square, num_list)
cubes = my_map(cube, num_list)
quads = my_map(quad, num_list)

print(squares)
print(cubes)
print(quads)
$ python first_class_function.py
[1, 4, 9, 16, 25]
[1, 8, 27, 64, 125]
[1, 16, 81, 256, 625]

Given several existing functions like square, cube, and quad, a single wrapper function such as my_map lets us reuse them all without modifying the originals.

Now let’s look at how to return a function from a function. We’ll build a tiny logger:

Update the code as follows and run it:

first_class_function.py
def logger(msg):
def log_message():  # 1
print('Log: ', msg)

    return log_message

log_hi = logger('Hi')
print(log_hi)  # prints the log_message object
log_hi()  # prints "Log: Hi"
$ python first_class_function.py
<function logger.<locals>.log_message at 0x0000022AB43EAA60>
Log:  Hi

The log_message function defined at #1 is returned from logger, assigned to log_hi, and then called. Notice something interesting: a function’s local variables normally disappear from memory after the function returns, so they shouldn’t be reachable afterward — yet the 'Hi' value bound to msg is still accessible after logger has returned. A function like log_message that remembers its enclosing function’s local variables even after that enclosing function has finished is called a closure. Let’s prove log_message really remembers by deleting logger from the global namespace and calling log_message again:

Update the code as follows and run it:

first_class_function.py
def logger(msg):
def log_message():  # 1
print('Log: ', msg)

    return log_message


log_hi = logger('Hi')
print(log_hi)  # prints the log_message object
log_hi()  # prints "Log: Hi"

del logger  # remove logger from the global namespace

# verify logger is gone
try:
print(logger)
except NameError:
print('NameError: logger no longer exists.')

log_hi()  # "Log: Hi" still prints even after logger is gone
$ python first_class_function.py
<function logger.<locals>.log_message at 0x0000022EC0BBAAF0>
Log:  Hi
NameError: logger no longer exists.
Log:  Hi

Even after deleting logger, calling log_hi() still works — log_message continues to remember 'Hi'.

So even after logger has been completely removed, the inner log_message still remembers its captured msg. Closures are useful in many ways; we’ll cover them in detail in a separate lesson.

Here’s a slightly more practical example:

Update the code as follows and run it:

first_class_function.py
# a plain function
def simple_html_tag(tag, msg):
print('<{0}>{1}<{0}>'.format(tag, msg))


simple_html_tag('h1', 'Simple Heading Title')

print('-' * 30)


# a function that returns a function
def html_tag(tag):
def wrap_text(msg):
print('<{0}>{1}<{0}>'.format(tag, msg))

    return wrap_text


print_h1 = html_tag('h1')  # 1
print(print_h1)  # 2
print_h1('First Heading Title')  # 3
print_h1('Second Heading Title')  # 4

print_p = html_tag('p')
print_p('This is a paragraph.')
$ python first_class_function.py
<h1>Simple Heading Title<h1>
------------------------------
<function html_tag.<locals>.wrap_text at 0x00000272C3CFAAF0>
<h1>First Heading Title<h1>
<h1>Second Heading Title<h1>
<p>This is a paragraph.<p>

At #1 (line 19), html_tag is assigned to print_h1, and at #2 (line 20) printing the variable shows it holds a wrap_text function object. At #3 (line 21) and #4 (line 22), wrap_text is then called by passing a string. At this point you may wonder why a wrapper function is needed when a plain function like simple_html_tag would do the job. Understanding higher-order functions such as html_tag is what makes the later topics — closures, decorators, and generators — much easier to grasp.

The next post covers closures.

X