Python Functions: A Beginner’s Guide — Part 2
Tags: beginners, programming, python, codenewbie
Published: 2023-01-17 03:48:39

## **Introduction**

In the previous article in the series [Python Functions: A beginner’s Guide — Part 1](https://dev.to/ashutoshvaidya/python-functions-a-beginners-guide-part-1-4off), we learned about the fundamentals of functions, how to define them, and what terminology is used. This article will explore the local and global scope of variables, default and keyword paraments, and different types of functions.

## Local and Global Scope

When a variable is defined within a function, it is considered a ***local*** variable and is accessible only within that function. For example, in the following code, the `name` variable is a local variable that can only be accessed within the greet function:

```python
def greet(name):
    greeting = 'Hello, ' + name
    return greeting

print(greet('Jon'))  # Output: 'Hello, Jon'
print(name)  # This will cause an error, as the name variable is not defined outside of the greet function
```

A ***global*** variable, on the other hand, is defined outside of any function and can be accessed and modified from anywhere in the code. An example of a global variable is as follows:

```python
name = 'Jon'  # This is a global variable
def greet():
    greeting = 'Hello, ' + name  #The name variable is accessed within the function
    return greeting

print(greet())  # Output: 'Hello, Jon'
print(name)  # Output: 'Jon'
```

>  **Note**: Global variables should be avoided wherever possible because they can make your code more difficult to understand and debug.

If you need to use a variable in multiple functions, it is often better to pass it as an argument to the functions that need it. This can make your code more modular and easier to maintain. We will explore different types of parameters in the following section.

## Function Parameters

Function parameters in Python are variables used in the function definition to specify the input that the function can accept when called.

Take a look at the following function definition:

```python
def greet(name, greeting='Hello'):
    return f'{greeting}, {name}'
```

The `name` and `greeting` variables in this example are function parameters. When calling the function, you can supply values for these parameters to specify the function’s arguments. As an example:

```python
print(greet('Jon'))  # Output: 'Hello, Jon'
print(greet('Jon', 'Hi'))  # Output: 'Hi, Jon'
```

In Python, you can use a variety of function parameters, including:

 1. ***Positional parameters:*** These are the most common kind of function parameters, and their position in the function declaration determines their value. In the preceding example, `name` is a positional parameter.

 2. ***Default parameters:*** You can define default values for function parameters in the function declaration by including an equal sign and a value. In the example from earlier, `greeting` has a default value of *‘Hello,’* so if you don’t specify one when using the function, it will use the default value.

 3. ***Keyword parameters:*** Function arguments can be specified using keyword notation, which enables you to specify the values of the arguments in any order as long as the names of the arguments are included. For example:

```python
print(greet(greeting='Hi', name='Jon'))  # Output: 'Hi, Jon'
```

`*args` and `**kwargs`

The `*` and `**` operators in Python can be used to pass a variable number of arguments to a function.

The `*args` operator allows you to pass a variable number of positional (i.e., non-keyword) arguments to a function. The arguments are passed as a tuple. For example:

```python
def sum_numbers(*args):
    total = 0
    for number in args:
        total += number
    return total
    
print(sum_numbers(1, 2, 3))  # Output: 6
print(sum_numbers(1, 2, 3, 4, 5))  # Output: 15
```

The `**kwargs` operator allows you to pass a variable number of keyword arguments to a function. The arguments are passed as a dictionary. For example:

```python
def print_keyword_arguments(**kwargs):
    for key, value in kwargs.items():
        print(f'{key} = {value}')
        
print_keyword_arguments(name='Jon', age=30)
    # Output:
    # name = Jon
    # age = 30
```

>  **Note**: Using `*args` and `**kwargs` can be useful when you want to write a flexible function that can handle a variable number of arguments.

## Different types of Functions

In Python, there are several types of functions that you can use. These include:

* ***Normal functions:*** These are the most common type of functions and are defined using the `def` keyword. They can take arguments and return a value.

* ***Lambda functions:*** These are single-line, anonymous functions that are defined using the `lambda` keyword. They are often used when you need to define a simple function for a short period of time. For example:

```python
add = lambda x, y: x + y
print(add(2, 3))  # Output: 5
```

* ***Recursive functions:*** These are functions that call themselves in order to solve a problem. They can be used to solve problems that can be divided into smaller subproblems, such as calculating the factorial of a number. Here is an example of a recursive function that calculates the factorial of a number:

```python
def factorial_recursive(num):
    if num == 0:
        return 1
    else:
        return num * factorial_recursive(num - 1)

fact = factorial_recursive(5)
print(f"Factorial of 5 is {fact}") # Output: Factorial of 5 is 120
```

* ***Nested functions:*** In Python, you can define functions within other functions. These are called nested functions. The inner function has access to the variables of the outer function, but not vice versa. For example:

```python
def outer(x):
    y = x + 1  # y is a local variable of the outer function
    def inner(z):
        return z + y  # The y variable is accessible within the inner function
    return inner

my_function = outer(2)
print(my_function(3))  # Output: 6
print(y)  # This will cause an error, as the y variable is not defined outside of the outer function
```

>  **Note**: Nested functions can be useful for organizing and structuring code, but it is important to be aware of the variable scopes and how they affect the accessibility of variables within the functions.

* ***Generators:*** A generator is a special type of function that returns an iterator that produces a sequence of values on demand, instead of returning a single value. In Python, you can define a generator function using the `yield` keyword, like below:

```python
def my_range(n):
    i = 0
    while i < n:
        yield i
        i += 1
```

When you call a generator function, it does not execute the code within the function. Instead, it returns a generator object that you can iterate over to produce the values. For example, if you want to iterate over to generator 'my_range` we just created you can write something like below:

```
for i in my_range(3):
    print(i)  #Output: 0, 1, 2
```

> **Note**: Generators are useful for producing sequences of values that are too large to fit in memory all at once. They allow you to process the values one at a time, which can be more efficient and use less memory than storing all of the values in a list.

If you find generators interesting, you may like my article [Behind the scenes of the For Loop](https://dev.to/ashutoshvaidya/behind-the-scenes-of-for-loop-3h6h) where I discussed iterators, generators, and how the For Loop works internally.

## Advantages of Functions

 1. **Code Reuse:** Functions allow you to reuse code, which saves time and improves the efficiency of your code. Instead of repeating the same code, you can construct a function once and call it anytime the task requires it.

 2. **Modularization:** Functions can assist you in breaking down your code into smaller, more manageable chunks. This can help you minimize repetition and make your code easier to read and understand.

 3. **Abstraction:** Functions enable you to mask the specifics of how a task is carried out from the rest of your code. Because you don’t have to worry about the details of how the function works, your code will be easier to read and understand.

 4. **Testing and Debugging:** Functions can let you test and debug your code more easily. Because functions are self-contained chunks of code, you can test and debug them independently of the rest of your code.

 5. **Code Maintenance:** Functions can help you maintain your code more easily over time. If you need to update the code for a function, you only need to edit it once rather than searching through your entire program to identify all of the places where the code is utilized.

## Final Summary

In a summary, 
* Functions are an essential aspect of Python programming and are used to organize and structure code. They let you reuse code, saving you time and making your code more efficient.

* Functions have their own variable scope, and several sorts of functions, including conventional functions, lambda functions, and recursive functions to name a few.

* Function parameters can also be used to establish default values and pass a variable number of arguments to a function.

* Functions provide numerous advantages, including code reuse, modularization, automation, testing and debugging, and code maintenance.

I hope you find this article helpful, don’t forget to give a like, provide feedback and subscribe. Happy Reading!