progarmmer svg

Tarun'sDev Diary

First-Class Functions and Closure In Python

Wed Jul 26 2023 Tarun Chawla

In this insightful article, we will demystify two powerful Python concepts: first-class functions and closures.

What are First-Class Functions?

first-class functions: A programming language is said to have first-class functions if it treats functions as first-class citizens. This means that functions are treated the same way as any other entity in the programming language. For example, functions can be passed to other functions, returned by other functions and assigned to a variable just like other objects(int, string etc.). This is an important concept that helps in understanding other concepts such as closures, higher-order functions, decorators and many more. Many languages such as Python, JavaScript, PHP, and Lua support first-class functions.

Assigning function to a variable

Code:

def square(x):
  return x * x

# Assigning a function a variable
square_copy  = square

print(square_copy)
print(square)

Output:

<function square at 0x7fc12daafe20>
<function square at 0x7fc12daafe20>

Now, we can see that the variable square_copy has the same reference as the square function, which means square_copy is going to act the same way as the square function is working. As you can see:

Code:

print(square(3))
print(square_copy(3))

Output:

9
9

Note: While assigning a function to a variable, do not use parentheses, this will execute that function instead of assigning its reference and will store the returned value in the variable instead of storing the function reference. For example, square_copy = square(2) will execute the square function and return the square of the argument.

Passing function as an argument

Now, take an Example of Python’s higher-order filter function.

Code:

def is_even(num):
  """
    if num is divided by 2, the '%' will return 0 as remainder is 0.
    not(0) = True (number is even).
    not(any number except 0) = False (not even).
  """
  return not num % 2

even_numbers = list(filter(is_even, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
# Print all the even_numbers
print(even_numbers)

Output:

[2, 4, 6, 8, 10]

As we can see filter function takes the is_even function as an argument. We can create a custom filter function.

Code:

def is_even(num):
  """
    if num is divided by 2 the '%' will return 0 as remainder is 0.
    not(0) = True (number is even).
    not(any number except 0) is False (not even).
  """
  return not num % 2

# this filter function is not exactly the same as Python's built-in filter function, but it works in a similar way.
def my_filter(function, iterable):
  result = []
  for val in iteratable:
    if function(val):
      result.append(val)
  return result


even_numbers = my_filter(is_even, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(even_numbers)

Output:

[2, 4, 6, 8, 10]

The my_filter function mimics the functionality of Python’s higher-order filter function.

Returning a function from another function

code:

def create_greet():
  def greet():
    print("Hello, how are you?")
  return greet


# this function will return reference to the inner function greet.
new_func = create_greet()

print(new_func)
new_func()

Output:

<function create_greet.<locals>.greet at 0x000002C847468E00>
Hello, how are you?

As you can see, the create_greet function returns the greet function that was defined within it. We then assign this returned function to the new_func variable. Hence, the new_func is now acting as the greet function.

Note: While returning a function, do not use parentheses (), as it will call that function and will return what that function is returning. In this example, return greet() will call the greet function and will print Hello, how are you? , as the greet function is returning nothing. So, return greet() will return None.

Code:

def create_greet():
  def greet():
    print("Hello, how are you?")
  return greet() # going to call greet()


# This function will return nothing as in the return statement we are calling greet not returning it's reference.
new_func = create_greet() # as greet return None, new_func = None

print(new_func)
new_func() # None is not a function, so going to give an error.

Output:

Hello, how are you?
None
Traceback (most recent call last):
  File "e:\tarun\Chill\test.py", line 11, in <module>
    new_func()
TypeError: 'NoneType' object is not callable

What is Closure?

Closure: consider closures as an imaginary container (environment) that stores the function along with the record of surrounding variables. This allows the function to “remember” the values of those variables, even if they no longer exist in memory.

Code:

def create_multiplier(x):
    num = x
    def multiplier(n):
        return num * n
    return multiplier

# create a "doubler" function
doubler = create_multiplier(2)

print(doubler(5))  # This will output 10

# create a "tripler" function
tripler = create_multiplier(3)

print(tripler(5))  # This will output 15

Output:

10
15

Function’s Working: Whenever we call a function, a memory block is created in the call stack for that function which stores all the variables associated with that function. Once the function is executed all the memory occupied by the function and its variable is cleared.

From the function’s working, we can say that when we called create_multiplier(2) a memory block got created in the call stack for the create_multiplier function to store its variables and when the function returned multiplier the memory occupied by the function got cleared. This means that the variable num would also be removed from memory. However, when we call doubler, the function still knows the value of num. This is because the closure stores the value of num alongside the multiplier function.

Some Of The Applications