Functions and Functional Programming in Python (1. Functions Input Interfaces or Signatures)

In the name of Allah, most gracious and most merciful,

Functional programming is a new way of problem-solving that is different from procedural programming. We will start to see the world as a set of actions while in Object-Oriented Programming (OOP) we see the world as objects or nouns that interact together using actions that are associated with these nouns which I will talk about in separate post(s). In functions, simply you have an interface that transforms input(s) to output(s). So it is a new problem-solving paradigm.

When I am talking about functional programming, I mean functions with clean interfaces that transform inputs to output. Using functions reduces errors, makes your code modular and gives you the freedom to compose functions within functions “Composability” to implement complex functionalities that become very complex if implemented using procedural programming only. By composing functions, we could start to see patterns that will help us in designing our solutions for different problems.

In this post I will talk about the different python input signatures.

1. Introduction

As I have previously mentioned, you can think of a function as something that simply takes input(s), make some operations on them, and finally produces or returns output(s). Let’s take this step by step.

# function example in Python
def function_name(input_arg_1, input_arg_2, ...):
# now I am inside the function where I could
# perform some operations

# finally I could return outputs(s)
return out_1, out_2, ...

# to use this function you call it (here is an example of a function call) whose outputs
# are stored in variables
out_1, out_2, ... = function_name(input_arg_1, input_arg_2, ...)

When we are thinking about inputs, we should think about how could we obtain these inputs, and here comes the idea of an interface which is your gateway to the function to take inputs. By the way, inputs are called arguments or input arguments in coding terminology.

2. Positional Arguments

Input arguments that are declared by variable names without the = sign. They must be provided or you will get an error on calling the function without them. You could also call them by keywords. The order is important unless you call them by keywords.

def subtract_fn(a, b):
return a - b

# now a and b are positional arguments

# a function call with inputs at correct position
print(subtract_fn(3, 2))
>> 1

# a function call with inputs specified by keywords
print(subtract_fn(a=3, b=2))
>> 1

# a function call with inputs specified by keywords but position changed
print(subtract_fn(b=2, a=3))
>> 1
# still we get the same value and not -1

3. Keyword Arguments

They are also called default arguments, named arguments, or optional arguments. As you might have expected, they are optional so if you didn’t input them, their default values, which are after the = sign, will be used. However, you can override these default values by just providing a value in their place either by specifying the keyword explicitly or by putting it in its correct position. Let me clarify things with the below code. Again the order is not important if you used keywords in the function call.

def multiply_fn(a=3, b=4):
return a * b

# now a and b are the keyword arguments (a and b are the keywords)

# a function call without any inputs
print(multiply_fn())
>> 12

# now we got the default values

# we can override these values positionally (i.e. without specifying keywords)
print(multiply_fn(5, 6))
>> 30

# now it worked so the a, and b values were replaced

# we can also override these values by specifying keywords explicitly
print(multiply_fn(a=2, b=1))
>> 2

# we could override the default values while changing the input order provided that we provide the
# keywords explicitly in the function call

# here is another division example to clarify this point
def divide_fn(a=3, b=4):
return a / b

# normal function call
divide_fn()
>> 0.75

# we change order here while explicitly specifying keywords
divide_fn(b=3, a=9)
>> 3.0

If the function has many details that are usually not needed for the users. In other words, you have default values that will be okay for most use cases. So by using keyword arguments, the user won’t have to worry about these inputs unless he is doing something more complex so then he could override the default values. Therefore, we have simplified the use of complex functions for users while maintaining customizability.

When To Use Keyword Arguments

It captures excess arguments that are supplied positionally into a tuple. By doing this, the user could input a variable number of arguments. Variadic Positional Arguments are captured by preceding their name in function signatures with a star * like this def func_name(*args): You could have a look at the following code for further clarifications.
# just by adding * in the function signature
def multiply_all_fn(*args):
value = 1
for arg in args:
value *= arg

return value

multiply_all_fn(2, 3, 4)
>> 24

# we could use anything instead of args, so args is not mandatory but it could be
# kind of something common in the Python community
def multiply_all_2_fn(*whatever_name):
value = 1
for anything in whatever_name:
value *= anything

return value

multiply_all_2_fn(2, 3, 4)
>> 24
# so it works

# you could also provide the inputs as any type of iterable like list, tuple,
# or sets but you should unpack the iterable while calling the function using
# the *
some_tuple = (2, 3, 4)
some_list = [2, 3, 4]
some_set = {2, 3, 4}

print(f"Unpacking Tuple Input:\n{multiply_all_fn(*some_tuple)}")
print(f"Unpacking List Input:\n{multiply_all_fn(*some_list)}")
print(f"Unpacking Set Input:\n{multiply_all_fn(*some_set)}")
>>> Unpacking Tuple Input:
24
Unpacking List Input:
24
Unpacking Set Input:
24

# unpacking is like getting each of the iterable elements and supplying it
# individually so
multiply_all_fn(*(2, 3, 4))

# is the same as
multiply_all_fn(2, 3, 4)

# and that's it :)

This is very useful if you want to provide any number of positional arguments without knowing how many are these numbers.

It captures excess keyword arguments and these arguments are captured in a dictionary. By doing this, the user could input a variable number of named parameters which is very useful when having a configurable environment that has many configurable parameters. Variadic Keyword Arguments are captured by preceding their name in function signatures with a double ** like this def func_name(**kwargs): You could have a look at the following code for further clarifications.

# just by adding ** in the function signature
def print_whatever_provided_fn(**kwargs):
for key, value in kwargs.items():
print(key, value, sep=":")

print_whatever_provided_fn(number=23, name="some name", date="dd/mm/yyyy")
>>> number:23
name:some name
date:dd/mm/yyyy

# again kwargs is not mandatory so you could use whatever name you want
def print_whatever_provided_2_fn(**some_dictionary):
for key, value in some_dictionary.items():
print(key, value, sep=":")

>>> number:23
name:some name
date:dd/mm/yyyy

# so it works

# you could also provide the inputs as a dictionary then unpack it using ** in
# before the dictionary name in the input arguments
whatever_dictionary = {'number': 25,
'name': "Some Name",
'data': "dd/mm/yyyy"}

print_whatever_provided_fn(**whatever_dictionary)
>>> number:25
name:Some Name
data:dd/mm/yyyy

print_whatever_provided_2_fn(number= 23, name= "some name", date= "dd/mm/yyyy")

# so unpacking is like providing the dictionary keys as keywords, and the
# dictionary values as values for the corresponding keywords in the input
# arguments

6. Keyword-Only Arguments

Keyword-Only Arguments must be supplied by their keywords, otherwise, you will get an error. They are arguments that are after the variadic positional arguments because if you didn’t specify a keyword for them they will be automatically part of the variadic positional arguments. The following code will clarify things more.

def multiply_all_kw_only_fn(*args, add):
value = 1
for anything in args:
value *= anything

# no keyword is supplied for add
multiply_all_kw_only_fn(2, 3, 4, 5)

>>> TypeError: multiply_all_kw_only_fn() missing 1 required keyword-only argument: 'one'

# now it worked

Finally

Thank you. I hope this post has been beneficial to you. I would appreciate any comments if anyone needed more clarifications or if anyone has seen something wrong in what I have written in order to modify it, and I would also appreciate any possible enhancements or suggestions. We are humans, and errors are expected from us, but we could also minimize those errors by learning from our mistakes and by seeking to improve what we do.

Allah bless our master Muhammad and his family.

References

https://www.udacity.com/course/intermediate-python-nanodegree–nd303

Subscribe
Notify of