# Functions

A function is a block of code consisting of one or more Python statements which are invoked together as a sequence of instructions when the function is called.

If a function is defined with parameters then you have to invoke such a function with necessary arguments.

We have already called several functions in this book. E.g.;

print("hello")

Both these functions are taking one argument each; 'hello' and 'please type in anything' respectively in these examples.

These functions were already available in Python and we used them. Now we will create our own function a.k.a. user defined functions.

User Defined Function

Here is an example of a user defined function that generates a list of even numbers up to some given number as input. It is named, generate_even_numbers and the number upto which it has to generate the even numbers is defined as a parameter with the name 'limit'. Here is the full implementation of the function definition:

``````
# definition of the function with one parameter - limit
def generate_even_numbers(limit):
even_number_list = []
for i in range(2, limit, 2):
even_number_list.append(i)
return even_number_list
``````

You define a function by using the def keyword and the name of the function and a colon, followed by the full implementation of the function. By now you are familiar with the rest of the statements after the colon. The only other keyword that you have not seen so far is the return

Once this function is defined, this function can be called as shown in the code below:

`generate_even_numbers(10)`

The value '10' that is passed is here is the 'argument' for the parameter 'limit' that is defined in the function.

return keyword: You use the return keyword to pass something from the function back to the calling statement. You will see it in action below.

The generate_even_numbers function, when invoked with argument 10, creates a list of even numbers below 10 and returns that list as there is a return statement. The return value of this function is shown below:

`[0, 2, 4, 6, 8]`

The full code for invoking this function is shown below:

``````
even_numbers = generate_even_numbers(10)
print(even_numbers)
``````

Since a list of even numbers is returned, 'even_numbers' variable will have a reference to this list object that is returned from the function. Note however, that you named that list variable 'even_number_list' inside the function. However that variable is not accessible from outside of the function code block and the only way the values can be accessed is by returning that object from the function. And you have to have another variable, in our case, 'even_numbers', which receives the reference to this returned object.

When the program of execution hits a return statement, the program exits the function call and any other statements after the return is not executed.

You can send different values for the argument in this function while invoking and accordingly your results will be different. Once a function is defined, you can invoke that function any number of times with different variables and each time it returns a list based on the input number.

This function is defined to receive one argument. If you do not send the required argument within the parenthesis, then you get a TypeError

TypeError: generate_even_numbers() missing 1 required positional argument: 'limit'

Function with no return

If a function does not return anything, then the variable that tries to receive the returned value will have a None value. Here is an example of such an invocation:

``````def display_greetings(name):
print("Greetings!", name + ",",  "hope you are having a good day")

a = display_greetings("Foo")
type(a)
``````

Output:

Greetings! Foo Hope you are having a good day
NoneType

While the above was a user defined function, you have been using such functions which return nothing. Here is an example of built-in function 'print' which return nothing:

``````a = print('Z')
print(a)
``````

Output:

Z
None

Documentation

You can add documentation to a function so that others can understand how to use the function, by adding comment lines with three single or double quotes. Here is the same modified function with documentation

``````
def generate_even_numbers(limit):
'''
This method generates even numbers from 0 to the value
passed in the 'limit' parameter, excluding the 'limit' value
'''
even_number_list = []
for i in range(2, limit, 2):
even_number_list.append(i)
return even_number_list
``````

You notice that this function has documentations described between three single quotes. This comment line will be displayed when the user invokes 'help', another built-in function, function by sending in the name of the function as an argument.

Here is how it is invoked:

`help(generate_even_numbers)`

Output:

Help on function generateevennumbers in module __main\:
generate_even_numbers(limit)
This method generates even numbers from 0 to the value   passed in the 'limit' parameter, excluding the 'limit' value

Difference between argument and parameter

Parameters are variable names that are defined in a function. Arguments are the values that are sent to the variable defined in the function when the function is invoked.

### Function Definitions With Default Values

Sometimes you may have to define a function with one or more default argument values. In such cases all the default arguments can be defined after all the non-default arguments are defined. Here is an example;

``````def do_something(state, county, country="US"):
print(country, state, county)

do_something('MI', 'Wayne')
do_something('MI', 'Wayne', "United States of America")
``````

output:

US MI Wayne United States of America MI Wayne

With the above function definition, you can invoke the function with or without a value for 'country'. If you pass country value then the passed in value would be considered. If it is missing then the default value defined in the function definition will be used.

### Function Definitions with Variable Arguments and Keyword Arguments

Occasionally you may require a function which can accept variable arguments. In such cases you can use the `*` (asterisk) and/or `**` (double asterisk) against the parameter names to receive many comma separated arguments and/or keyword arguments respectively. Here is an example;

``````def do_something(*args, **kwargs):
print(kwargs)  # receives a dictionary of keyword arguments
print(args)   # receives a tuple of all arguments.

do_something(1, 2, 3, country='US', county='Wayne')
``````

output:

{'country': 'US', 'county': 'Wayne'} (1, 2, 3)

### Referencing variable from outer scope inside a function

Supposing you want to reference a variable outside of the function, you can do so only by using global keyword. Here is an example

``````x = 10
def do_something():
x += 100
print(x)

do_something()
``````

Output:

`UnboundLocalError: local variable 'x' referenced before assignment`

The above reference of the outer variable throws an error. You can fix this by assigning the global keyword for the variable x. Here is the code:

``````
x = 10
def do_something():
global x
x = x + 100
print(x)

do_something()
``````

Output:
110

Note: All variables that are declared inside a function are considered local by default unless 'global' keyword is used.

However you can reference an outer variable inside the function. Here is the example to illustrate that:

``````
x = 10
def do_something():
print(x)

do_something()
``````

Output:

10

### Understanding variable scope in Python

By default all variables declared in a python file (the .py file which becomes a module if used by other programs) is visible to conditional code blocks like if, while, functions etc.. inside that file.

However the variables declared in a module (file) are not automatically global and hence cannot be referenced by its name alone, inside other modules (files), unlike in languages such as JavaScript. Variables which are in one module can be accessed in other modules by way of importing that module and prepending the variable name with module name/namespace.

### `*` for Unpacking Collections

Asterisk (`*`) performs argument unpacking. This is used with an enclosing function. Here is an example;

``````
def multiply(a, b):
return a * b

print(multiply(*[2, 3]))
``````

output:

6

Note however that, you cannot use asterisk by itself without an enclosing function. Although the example shows a list, you can replace the list with any collection object like tuple, set etc..

### `**` for Unpacking Dictionary

You can use double asterisk (`**`) to unpack a dictionary into name, value pairs. Here too it should be used as an argument to a function. Here is an example;

``````def do_something(country,state,county):
print(country, state, county)

my_place = {'state':'Michigan',
'county':'Wayne', 'country':'United States',}
do_something(**my_place)
``````

output:

United States Michigan Wayne

#### Highlights

• def keyword is used to define a function
• function should be defined first before it is invoked.
• A function is a block of code which can be called by the same or different program
• A function can take 0 or more arguments as long as the respective parameters are defined in the function definition.
• You can set default values to certain arguments by defining the default argument values after all the non-default arguments are defined.
• You can also send variable length of arguments to a function by using an asterisk before the argument name.
• You can also send a variable length of keyword arguments by using double asterisk before the keyword arguments name.
• Multiple arguments are separated by a comma in both the definition and invocation. The order of arguments is important when you call the function- you should maintain the same order as the definition, while passing the values to the arguments unless when the function is defined for arbitrary arguments and/or keyword arguments list.
• A return statement, if present will return any object back from the function to the calling program.
• Function definition should come before the function is called otherwise you will get NameError

#### Naming convention

• It is a recommended practice to name all variables with a noun and all function names should be a verb. The reasoning behind this is, a variable holds a value and inherently does not do anything else with it so it should be a noun. A function on the other had is doing something on the arguments passed. It is taking some action on it and hence should be named with a verb.

### Anonymous functions a.k.a. Lambda functions

In python you can create functions without the `def` keyword and a name. These are called Lambda functions. Lambda functions can be created when the function has only one expression in its body. Let us take an example:

``````
return x + y
``````

In the above function definition, there is only one expression `x+y` which is returned. So this is a good candidate for defining it as a Lambda function instead. Here is how it is defined as a Lambda:

``````
(lambda x, y : x+y)(5, 10)
``````

Output:
15

In this the Lambda function is taking x and y as arguments and the expression `x+y` after colon, is the body of the lambda function. As you can see lambda function has no name and it gets called when you pass arguments (5,10) to it through a pair of parenthesis.

### Yield keyword in functions

When a function executes a return statement, it terminates the function call and returns the program of execution to the calling statement and the state of the variables inside the function is released for garbage collection. Garbage collection is a program which evicts all the de-referenced variables from memory thereby making space in memory for other relevant variables that the program generates.

If instead a function executes a yield statement then the state of the variables inside the function is saved and the function returns an object of type generator with the saved state. Here is an example:

``````
def my_func(num):
for i in range(10):
num += 1
yield num

type(my_func(10))
``````

This generator object is iterable in any loop and the variable that is used in the yield statement is given one at a time in the loop iteration.

``````
def my_func(num):
for i in range(10):
num += 1
yield num

for i in my_func(5):
print(i)
``````

This prints out the value 6 through 15 which is the value of the num variable computed inside the function and returned through the yield statement

### Generator expressions

Generator Expressions looks similar to list comprehensions, but does not construct a list object but constructs a generator.

Instead of creating a list and keeping the whole sequence in the memory, the generator object generates the next sequence in an iteration one at a time.

``````
[i for i in range(5)]  # a list object with values from 1 to 5 is returned
(i for i in range(5))  # a generator object is returned that provides values from 1 to 5 during iteration
``````

You can use the generator in any situation where a collection is required. For e.g., if you want to use the built-in sum function to find the sum of all numbers in a list, you could use either the list object or pass in the generator object. For e.g,:

``````
sum([1, 2, 3])
sum([i for i in range(4)])
sum(i for i in range(4))
``````

All the above get you the same result except in the first and second case, you have the entire list object in memory but in the third case, you have only the generator object in memory which produces the next sequence needed in the iteration, on demand.

### pass statement

Sometimes you do have the complete implementation for a compound statement in which case you can use a pass statement which is just a placeholder so that your code compiles but does nothing in that complex statement.

For e.g.;

``````
if True:
pass
``````

Remember however, the program of execution continues after the pass statement is executed if there are more statements after it. This is not like return statement. pass just results in no operation (NOP). Here the below statement does print 'hi'

``````
if True:
pass
print('hi)
``````

We typically use pass when the implementation of a block of code whether in a function, conditionals, loops etc., is deferred to a later date.