User's functions

A user's function is a block of code that you write, which only runs when it is called. You can pass data (parameters) into a function, and a function can also return data as a result of its call. Keep in mind that the code inside of the function is independent of the outside one.

All the user's functions must be declared in the following way:

  1. The keyword def marks the start of the function
  2. The name you give to the function
  3. Parentheses, and the parameters inside of them (they are optional)
  4. ":" to mark the end of the function's header
  5. The code (indented 1 level) that will be executed when the function is called
  6. An optional return statement to return data from the function

Creating and calling a function

So let's create a simple function that will greet you. So you need to define it with def greetings():, followed by the actions you want it to do print('Hello World!'). Remember that the code you want inside of the function must be 1 level indented.

To execute the function, you only need to call it with greetings().

def greetings():
    print('Hello World!')

greetings()                 # This will print "Hello World!" in the terminal

Arguments

If you want to pass some data to the function, then you need to specify them in the definition of the function and also when you call it. Be careful of the order you use for the variables, they must be in the same order.

def greetings1(name):
    print('Hello {}!'.format(name))

def greetings2(name1, name2):
    print('Hello {} and {}!'.format(name1, name2))

def presentation(people):
    print('We have {} scientists today:'.format(len(people)))
    for i,person in enumerate(people):
        print("{}. {}".format(i+1,person))

c1 = 'Sthepen'
c2 = 'Albert'
c3 = 'Nikola'
c4 = 'Isaac'
c5 = 'Marie'
scientists = [c1, c2, c3, c4, c5]

"""
We have 5 scientists today:
1. Sthepen
2. Albert
3. Nikola
4. Isaac
5. Marie
"""
presentation(scientists)

greetings1(c1)              # Hello Sthepen!
greetings2(c2, c3)          # Hello Albert and Nikola!
greetings2(c4, c5)          # Hello Isaac and Marie!

Default arguments

In case you need it, you can leave some arguments with a default value, for that you only need to define them in the function.

You can also call the function and specify some variables, in case you don't want to use the default values.

def greetings( name = 'Albert', time=''):
    if time == 'morning':
        print('Hello {}, good morning!'.format(name))
    elif time == 'evening':
        print('Hello {}, good evening!'.format(name))
    elif time == 'night':
        print('Hello {}, good night!'.format(name))
    else:
        print('Hello {}!'.format(name))

greetings()                                 # Hello Albert!
greetings('Marie')                          # Hello Marie!
greetings(time='morning')                   # Hello Albert, good morning!
greetings('Nikola', 'night')                # Hello Nikola, good night!

Returning data

Most of the time you will want the functions to return something as the result of executing them. To do that, you will use the return statement. If you don't write it, the function will interpret it as return None, as the default case.

In case you want to return a specific variable, you only need to write return var.

Or if you want to return multiple variables, you can do it by returning a list return [var1, var2, ... ], or with a tuple return (var1, var2, ... ).

def fullname(name,lastname):
    mystr = "{} {}".format(name,lastname)
    return mystr

def square(val):
    val2 = val*val
    return val2

def powers(val):
    val2 = val*val
    val3 = val*val*val
    val4 = val2*val2
    return [val2,val3,val4]

fname = fullname('Albert', 'Einstein')
fivesquare = square(5)
fivepowers = powers(3)

print("Hello {}!".format(fname))
# Hello Albert Einstein!

print("5^2 is {}".format(fivesquare))
# 5^2 is 25

print("Some powers of 3 are {}".format(fivepowers))
# Some powers of 3 are [9, 27, 81]

Nested functions

This is kinda an advanced topic because it can be used in such a way that improves the memory usage of your code, the speed, or just the readability. But basically, it's when you declared a function inside of another function.

It has some applications, but it is mostly to avoid passing a lot of data to the function, or it is a unique function that will be called just inside of that function or any other advanced reason (we will use these functions in further topics).

def sums(Arr):

    # Notice we are not passing Arr to the function
    def simple_sum():
        ans = 0
        for a in Arr:
            ans += a
        return ans

    # Notice we are not passing Arr to the function
    def square_sum():
        ans = 0
        for a in Arr:
            ans += a*a
        return ans

    # Internally, it is a tuple, like return (A,B)
    return simple_sum(), square_sum()

A = [1,2,3,4,5]
s1, s2 = sums(A)
print("The sums of {} are {} and {}".format(A, s1, s2))
# The sums of [1, 2, 3, 4, 5] are 15 and 55

Global and nonlocal variables

In some cases where you want to use a variable that was not passed as an argument, you can use the global statement, and then you will be able to use and modify those variables.

nonlocal statement is used in nested functions when you want to modify a variable that was not passed as an argument from the main function to the nested one.

A = []

def factorial(number):
    global A                    # Using a global variable

    ans = 1
    def rec_factorial():
        nonlocal ans, number    # Using a non-local variables

        if number <= 1:
            return 1
        else:
            ans *= number
            number -= 1
            rec_factorial()

    rec_factorial()
    A += [ans]
    return ans

f10 = factorial(10)    
f7 = factorial(5)
f4 = factorial(4)

print(f10,f7,f4)                # 3628800 120 24
print(A)                        # [3628800, 120, 24]

Pass statement

Functions cannot be empty, but you can write down the "pass" statement to avoid getting an error. You can use it when you want to run your code and you are not ready to write the full code of that case.

def doNothing():
    pass

def incomplete(i=0):
    if i == 1:
        print('It is 1')
    else:
        pass

# This code won't do anything
doNothing()
incomplete()

Main function

In most programming languages, there is a special function that is executed automatically every time the program is run, which is the main function, or main() as it normally coded. It essentially serves as a starting point for the execution of a program.

In Python you can execute your programs without the main function. This is because the Python interpreter executes from the top of the file unless a specific function is defined. But it has a disadvantage, you can not call a function that has not been declared before. If you do that, the program will pop up a NameError.

def greetings1():
    print('Hello World!')

greetings1()    # This line will print "Hello World!"
greetings2()    # This will pop up an NameError, because greetings2 is not yet defined

def greetings2():
    print('Hello World!')

But to be more professional or good practices with your team members, you will need to specify the main function. Which is very easy and you need just a few more lines to do that.

This gives you an advantage, the position of the main function inside of your code does not affect the execution of your program, and you will have a clear view of where your program will start executing code.

# Declare the main function. This will run at the beginning
def main():
    f5 = factorial(5)
    print('The factorial of 5 is '+str(f5))
    f7 = factorial(7)
    print('The factorial of 7 is '+str(f7))

# Declare the factorial function
def factorial(n):
    ans = 1
    while n >= 1 :
        ans *= n
        n -= 1
    return ans

# Define which is the main function
if __name__ == "__main__":
    main()