Projects Blog About Contact

Functions - First class objects in Python

Feb. 10, 2016

Why are functions considered first class objects in Python?

Colloquially, Functions are considered as first class citizens in a programming language if:

  1. A function can be passed as an argument to another function

  2. A function can return another function.

  3. Common operations supported for other data types are supported for functions as well.

(Condition 3 is a superset of conditions 1 and 2. But it's useful to think of this condition separately for learning purposes.)

More formally:

In programming language design, a first-class citizen (also type, object, entity, or value) in a given programming language is an entity which supports all the operations generally available to other entities.

Condition 1: Let's say you define a function called square:

def square(n): 
    """Compute the square of a number""" 
    return n * n

And now you want to write another function that squares a sequence of numbers:

def square_a_sequence_of_numbers(sequence): 
    for n in sequence: 
        yield square(n)

Here's an usage example: (I'm using the interactive ipython interpreter)

my_tuple = (1, 2, 3, 4, 5, ) 
<generator object square_a_sequence_of_numbers at 0x10d3aac80>
# IPython stores the result of the previous expression in a variable named _
[1, 4, 9, 16, 25]

What if you have another function that computes the cube?

def cube(n): 
    return n * n * n

And now you want a function that calculates the cube of each number in a sequence. Wouldn't it be better if you could reuse the code you wrote earlier? This is where a map-like function comes into play.

def map_a_sequence_of_numbers(func, sequence): 
    for n in sequence: 
        yield func(n)

Usage Example:

map_a_sequence_of_numbers(cube, my_tuple)
<generator object map_a_sequence_of_numbers at 0x10d3aad20>
[1, 8, 27, 64, 125]

Here, map_a_sequence_of_numbers is a called a 'higher_order_function' since it accepts another function as one of its arguments. Python has a built-in map function, but Pythonistas prefer list comprehensions to map.

Condition 2 - Function returning another function:

Decorators are the definitive examples of functions that return another function.

Here, we define a decorator, cached which caches the result of a pure function that takes a single numeric argument.

def cached(func): 
    cached_result = {} 
    def wrapper(n): 
        value = cached_result.get(n) 
        if value is not None: 
            print("Cache hit: f({})".format(n)) 
            return value
        print("Cache miss: f({})".format(n)) 
        value = func(n) 
        cached_result[n] = value 
        return value 
    return wrapper
def fib(n): 
    if n in [0, 1]: 
        return 1 
        return fib(n-2) + fib(n-2)

Usage Example:

Cache miss: f(5) 
Cache miss: f(3) 
Cache miss: f(1) 
Cache hit: f(1) 
Cache hit: f(3) 
Cache hit: f(3) 

Condition 3 - Other operations on functions:

Python has a dir function which can be called on any object to list its attributes. Functions are no different. You can query the documentation, source code and other attributes of a function object.

'Compute the square of a number'

You can even see the bytecode executed by the interpreter using the module.

import dis dis.dis(square)
 17    0 LOAD_FAST 0 (n) 
       3 LOAD_FAST 0 (n) 

Capabilities such as inspecting functions are used by library and compiler authors for meta-programming

All conditions are satisfied, and we're done!

So why is this feature important?

Having first class functions makes a language more expressive, so you have to write less boilerplate code. For example, until Java8, Java did not have first class functions and handling UI events was a huge pain. But web programming in JavaScript is much easier and even enjoyable, mainly because functions are first class objects.

Also, languages with first class functions also allow functions to be modified during runtime which is really useful for testing and debugging. These are just a couple of examples. Once you've programmed in a language with first class functions, you can't go back.

PS: I've entered the entire explanation in an IPython notebook so that you can paste the code in your interpreter and play along.