Functions#

Functions are blocks of code that only run when it is called. They are the basic building blocks we can use to construct larger amounts of code to solve problems.
Formally, a function is a useful device that groups together a set of statements so they can be run more than once. They can also let us specify parameters that can serve as inputs to the functions.

On a more fundamental level, functions allow us to not have to repeatedly write the same code again and again. If you remember back to the notebooks on data types and data structures, remember that we used a function len to get the length of a string. Since checking the length of a sequence is a common task, you would want to write a function that can do this repeatedly at command.

Defining a function#

A function is defined using the def statement. A function can take zero or more arguments and return some result.
NOTE: As with code inside an if statement or a loop, the code inside a function needs to be indented properly

def say_hello():
    print('Hello from python')

In the above snippet, we just defined the function. To run the code inside a function, we need to call it with the appropriate arguments provided (if any). Now we will run the function that we have defined above.

say_hello()
Hello from python

Returning a value#

A function can be used to return a value using the return statement. This returned value can then be stored as a variable or printed as required.

const = 4

def add_two_numbers(number1, number2):
    result = number1 + number2 + const
    return result
add_two_numbers(3, 5)
12
result
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[5], line 1
----> 1 result

NameError: name 'result' is not defined
res = add_two_numbers(3, 5)
print(res)
12

Next up we look at a function to determine if a number is prime. We can simply do this by writing a function that checks if the number is not divisible by anything other than 1 and the number itself.

def is_prime(num):
    '''
    Naive method of checking for primes. 
    '''
    for n in range(2,num):
        if num % n == 0:
            print(num,'is not prime')
            break
    else: # If never mod zero, then prime
        print(num,'is prime!')
is_prime(16)
16 is not prime
is_prime(17)
17 is prime!

Here we are using the break statement to end the for loop as soon as we encounter a factor other than 1 and the number itself. A function can house any statement, including if, for, while, match, and others.
Also note, the statements inside the triple quotes are not executed and serve as documentation for a given function. It lets the reader of our code know why we wrote the function. It is generally good practice to have well-documented functions so that anyone can read and understand your code.

def is_prime(num):
    '''
    Naive method of checking for primes without break. 
    '''
    for n in range(2,num):
        if num % n == 0:
            print(n)
            print(num,'is not prime')
    else: # If never mod zero, then prime
        print(num,'is prime!')

is_prime(16)
2
16 is not prime
4
16 is not prime
8
16 is not prime
16 is prime!

Since we are inside a function, we can replace the break statement above with a return statement. As soon as a function returns something, it shuts down and nothing else inside the function is executed.

def is_prime(num):
    '''
    Naive method of checking for primes with return. 
    '''
    for n in range(2,num):
        if num % n == 0:
            return False
    else: # If never mod zero, then prime
        return True

is_prime(16)
False

Lambda Expressions, Map, and Filter#

Many commands that we have used before are functions built-in (pre-defined) to Python, such as len, range, etc.
Now we will learn about some functions that are particularly useful to us like filter and map and then use them to build powerful lambda expressions.

Map function#

The map function allows you to ‘map’ a function to an iterable object. That is to say, you can quickly call the same function to every item in an iterable, such as a list. For example:

def square(num):
    return num ** 2
my_nums = [1,2,3,4,5]
map(square, my_nums)
<map at 0x17d02f30130>
# to get the results, either iterate through map() or just cast it to a list
list(map(square, my_nums))
[1, 4, 9, 16, 25]
for item in map(square, my_nums):
    print(item)
1
4
9
16
25

The functions can also be more complex

def splicer(mystring):
    if len(mystring) % 2 == 0:
        return 'even'
    else:
        return mystring[0]
    
mynames = ['John','Cindy','Sarah','Kelly','Mike']

list(map(splicer,mynames))
['even', 'C', 'S', 'K', 'even']

Filter function#

The filter function returns an iterator yielding those items for which a given function is true. Meaning you need to filter by a function that returns a boolean. Then passing that into filter (along with the iterable) returns only the results that would return True when passed to the function. Basically we are checking all the items in an iterable with some boolean function.

def check_even(num):
    return num % 2 == 0 
nums = [0,1,2,3,4,5,6,7,8,9,10]
filter(check_even,nums)
<filter at 0x17d032e7190>
list(filter(check_even,nums))
[0, 2, 4, 6, 8, 10]

The above expression uses the filter function and the check_even function to return only items in our list (nums) that are even.

Lambda Expression#

One of Python’s most useful tools is the lambda expression. Lambda expressions allow us to create “anonymous” functions. This basically means we can quickly make ad-hoc functions without needing to properly define a function using def.
Function objects returned by running lambda expressions work exactly the same as those created and assigned by defs. There is key difference that makes lambda useful in specialized roles:

lambda’s body is a single expression, not a block of statements.

The lambda’s body is similar to what we would put in a def body’s return statement. We simply type the result as an expression instead of explicitly returning it. Because it is limited to an expression, a lambda is less general that a def. We can only squeeze design, to limit program nesting. Lambda is designed for coding simple functions, and def handles the larger tasks.

Lets slowly break down a lambda expression by deconstructing a function:

def square(num):
    result = num ** 2 
    return result

square(2)
4

We can simplify it:

def square(num):
    return num ** 2

square(2)
4

We can actually write it all in one line:

def square(num): return num ** 2

square(2)
4

A lambda expression is then written as:

# we generally don't assign names to a lambda expression, this is just a demonstration
square = lambda num: num ** 2
square(2)
4

So why would use this? Many function calls need a function passed in, such as map and filter. Often you only need to use the function you are passing in once, so instead of formally defining it, you just use the lambda expression. Let’s repeat some of the examples from above with a lambda expression

list(map(lambda num : num**2 , my_nums))
[1, 4, 9, 16, 25]
list(filter(lambda n: n % 2 == 0,nums))
[0, 2, 4, 6, 8, 10]

Here are a few more examples, keep in mind the more comples a function is, the harder it is to translate into a lambda expression, meaning sometimes its just easier (and often the only way) to create the def keyword function.

lambda s: s[0]
<function __main__.<lambda>(s)>
lambda s: s[::-1]
<function __main__.<lambda>(s)>

You can even pass in multiple arguments into a lambda expression. Again, keep in mind that not every function can be translated into a lambda expression.

lambda x,y : x + y
<function __main__.<lambda>(x, y)>

You will find yourself using lambda expressions often with certain non-built-in libraries, for example the pandas library for data analysis works very well with lambda expressions. For example,

import pandas as pd

df = pd.DataFrame(
    {"name": ["IBRAHIM", "SEGUN", "YUSUF", "DARE", "BOLA", "SOKUNBI"],
     "score": [50, 32, 45, 45, 23, 45]
    }
)

df
name score
0 IBRAHIM 50
1 SEGUN 32
2 YUSUF 45
3 DARE 45
4 BOLA 23
5 SOKUNBI 45
df["lower_name"] = df["name"].apply(lambda x: x.lower())
df
name score lower_name
0 IBRAHIM 50 ibrahim
1 SEGUN 32 segun
2 YUSUF 45 yusuf
3 DARE 45 dare
4 BOLA 23 bola
5 SOKUNBI 45 sokunbi

In the code above, we created another column for the dataframe using the built-in lower function to convert all the names to lowercase.
We will learn more about pandas and write more code like above later in the course.

Reference:#

  1. https://www.w3schools.com/python/python_functions.asp

  2. https://www.freecodecamp.org/news/python-lambda-function-explained/

  3. https://www.dataquest.io/blog/tutorial-lambda-functions-in-python/