Luxurious Life Of First Class Python Functions

Luxurious Life Of First Class Python Functions

In Python, everything is a Object. Integer, Lists, Dictionary and even Functions. In this article we will talk about Functions as First Class Objects

Intro

In Python, everything is a Object. Integer, Lists, Dictionary and even Functions. In this article we will talk about Functions as First Class Objects in Python and some best use cases.



Table of Contents

  1. What Are First Class Objects?
  2. First Class Functions
  3. Functions Are Objects
  4. Objects As Functions
  5. Functions Stored In Data Structures
  6. Passing Functions As Arguments
  7. Nested Functions
  8. Conclusion



1. What Are First Class Objects?

According to programming language design a First Class Object (also Entity, Citizen, Type or Value) is an entity that can be dynamically created, destroyed, passed to a function, returned as a value, modified, assigned, and have all the rights as other variables in the programming language have.

Everything, really everything in Python is an Object. Classes that we define are also objects known as First Class Classes. Functions in Python are also objects known as First Class Functions. The concept of First Class Objects comes from SmallTalk (A Programming Language). This helps Python fairly adopt Functional Programming.


2. First Class Functions

In Python functions are First Class Objects. They have the same rights and as other objects have.

This means that functions :

  1. are objects
  2. can be used in an expression
  3. can be assigned
  4. have types
  5. can be stored in data structures
  6. can be passed as an parameter
  7. can be returned from a function

image.png

As you can see the function a_simple_function is a object of class function. Also the function has many properties and methods just like a object. This is not the case with languages like C or Java. First Class Functions are also found in Swift, Scala, JavaScript, Kotlin and PHP.


3. Functions Are Objects

The functions are objects in python, thus they can be assigned to another variable. As you can see a_simple_function is assigned to new_variable. Now new_variable is callable and will execute a_simple_function when called.

# defining a function
def a_simple_fucnion(text):
    print(f'Hello {text}')

# assigning the fucntion like an object    
new_variable = a_simple_function

# calling the fucntion through new object 
new_variable("Hashnode")

# Output :
Hello Hashnode

Python assigns the ID when a function or any other immutable object is assigned to a new variable. The new_variable is pointing to the original function and even after deleting a_simple_fucntion.

print(new_variable.__name__)
print(id(new_variable))
print(id(a_simple_function))

# removing a_simple_fucntion from scope
del a_simple_function
new_variable("Aagam Sheth")

# Output :
'a_simple_function'
2160378153568
2160378153568
Hello Aagam Sheth


4. Objects As Functions

Objects are not functions in Python or in any OOP language in any case. But Python provides a dunder method __call__ that allows the object to behave like a function. When a calling an object instance like a function, the __call__ method is executed.

class PrintWelcome:
    def __init__(self, name):
         self.name = "Aagam Sheth"

    def __call__(self, to_place):
        return f'Welcome to {to_place} {name}.'


welcome_aagam = PrintWelcome("Aagam Sheth")
print(welcome_aagam("hashnode"))


# Output :
Welcome to Hashnode Aagam Sheth.


5. Functions Stored In Data Structures

First Class Objects can be stored in data structures and so can be functions in Python. They work the same way as other object do and can be called when accessed from the dat structure.

def a_simple_function():
    return "        Hello Mate!!        "


func_dict = {
    'hello_func': a_simple_function,
    'trim_func': str.strip,
}

print(func_dict)


# Output :
{'hello_func': <function a_simple_function at 0x000001D8B69E8A60>, 'trim_func': <method 'strip' of 'str' objects>}

The pointes to the function are stored in the dictionary. This is similar to the concept discussed on Functions Are Objects.

func_dict['trim_func'](func_dict['hello_func']())

# Output :
Hello Mate!!

First the a_simple_function is called from the func_dict and then the str.strip is called. Functions can be stored in any data structure in any format. This can be really helpful to store similar function that are meant to be performed later dynamically based on the input provided.


6. Passing Functions As Arguments

The way functions are stored in the data structure, they can also be passed as arguments to other functions.

def a_simple_function(arg1):
    print(f'Hello {arg1}')

help(a_simple_function)

a_simple_function(a_simple_function)

# Output :
Help on function a_simple_function in module __main__:
a_simple_function()

Hello <function a_simple_function at 0x000001D8B69E8A60>

Python has a functions that take functions a argument. For example, filter function expects the first argument to be a function.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

def is_odd(n):
    if n % 2 == 1:
        return n

list(filter(is_odd, numbers))

# Output :
[1, 3, 5, 7, 9]

There are many Python functions like map that expect a function in its arguments. Lambdas are also the same concept. Passing function as an argument have many other use cases and very important concept based on this is a Decorator.


7. Nested Functions

A Function definition block can also contain another function definition resulting in to nested functions. Just like Conditional and Loop statements. Nested functions are able to access variables of the enclosing scope. Inner functions are used so that they can be protected from everything happening outside the function. This process is also known as Encapsulation.

There is one catch here. This is due to how python sees scoping which is totally messed up in my opinion. You can read more at

As you can see there is error with scoping here.

def a_simple_function():
    a = "Aagam Sheth"

    def nested_func():
        print(a)  # Error is here as a is local variale of nested_func, a_simple_functions.a is not in scope
        a = "Hashnode"

    print(a)
    nested_func()
    print(a)

a_simple_function()

# Output :
Aagam Sheth
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in a_simple_function
  File "<stdin>", line 5, in nested_func
UnboundLocalError: local variable 'a' referenced before assignment

So to solve this

def a_simple_function():
    a = "Aagam Sheth"

    def nested_func():
        nonlocal a
        print(a)  # Aagam Sheth
        a = "Hashnode"

    print(a)  # Aagam Sheth
    nested_func()
    print(a)  # Hashnode

a_simple_function()

# Output :
Aagam Sheth
Aagam Sheth
Hashnode

One of the use case for nested functions is Decorators or Wrappers. It is a software design pattern. A decorator is just a function that runs something before and after the function it decorates is called. This allow us extend and change the behavior of the function in certain circumstances. This was proposed in PEP 318

def decorator_func(func):
    def wrapper_func():
        print("Before Function")
        func()
        print("After Function")
    return warpper_func

@decorator_func  # Pythonic way to define a decorator on fucntion
def a_simple_function():
    print("Function is running")

a_simple_func()

# Output : 
Before Function
Function is running
After Function


# another way to define. 
a_simple_function = decorator_func(a_simple_function)

# Usually to dynamically assign a decorator on a function above method is used like below
a_simple_function_variable = decorator_func(a_simple_function)
a_simple_function_variable()

Decorators are really powerful and very useful. They definitely deserve a separate blog.


Conclusion

Everything in Python including functions is an object, you can assign them to variables, store them in data structures, and pass or return them to and from other functions as function are First Class Citizens on the Language. First Class Functions allow is to enhance the abstraction and encapsulation abilities of the language moving one step forward to more purer Functional Programming Design and enhance Object Oriented patterns and simulations.

The example used in the article are just for explaining the flow and syntax of the language. But this does not limit the function to be a super power.


I have used carbon and hashnode's code block. I am not sure which is more readable and easy on eyes. Please also like my comment "Carbon vs Hashnode Code Block" if you like carbon code and it is readable.

Thank you for reading!

I would love to connect with you on Twitter.

Check out my other blogs on aagamsheth.com