Functions: The Building Blocks of Reusable Code
Functions are one of the most powerful concepts in programming. They allow you to bundle a set of instructions into a single unit that you can reuse repeatedly throughout your code. This saves you from writing the same logic over and over, making your programs more organized, readable, and easier to maintain.
1. Defining and Calling Functions
Before you can use a function, you must first define it. In Python, you use the def keyword, followed by the function's name and parentheses. The code block that makes up the function's body is indented. To execute the function, you simply call it by its name, followed by parentheses.
Example Code:
# Function Body.
def greet():
print('Welcome to Python Function')
print('Function is used for code reusibility.')
# Calling the function.
greet()
Output:
Welcome to Python Function
Function is used for code reusibility.
2. Parameters, Arguments, and Default Arguments
Functions are much more useful when they can operate on different pieces of data. You can pass data into a function using parameters and arguments.
- A parameter is a variable defined in the function's definition. It acts as a placeholder for a value.
- An argument is the actual value you pass to the function when you call it.
There are three main types of arguments you can use:
Positional Arguments
These are assigned to parameters based on their order. The first argument goes to the first parameter, the second to the second, and so on. The order of the arguments when you call the function must match the order of the parameters in the function definition.
Keyword Arguments
These are assigned to parameters using the parameter's name. This allows you to pass arguments in any order and makes your code more readable, as you can see exactly which value is being assigned to which parameter.
Default Arguments
You can provide a default value for a parameter directly in the function definition. This makes the parameter optional; if an argument is not passed for it during the function call, the default value is used. Parameters with default values must always be listed after any parameters without a default.
Example Code:
# Positional Argument, Keyword Argument and Default Parameter.
# A Function with positional, and default parameters.
def describe_pet(pet_name, pet_type='dog'):
print(f'I have a lovely {pet_type}.\nIts name is {pet_name}.\n\n')
# Function calling with positional arguments.
print('Calling with positional arguments.')
describe_pet('harry', 'hamster')
# Function calling with keyword arguments.
print('Calling with keyword arguments.')
describe_pet(pet_name='Newton', pet_type='cat')
# Function calling using default argument.
print('Calling using default argument.')
describe_pet('Tupu')
Output:
__
Calling with positional arguments.
I have a lovely hamster.
Its name is harry.
Calling with keyword arguments.
I have a lovely cat.
Its name is Newton.
Calling using default argument.
I have a lovely dog.
Its name is Tupu.
A Word of Caution: Mutable Default Arguments
A common pitfall with default arguments is using a mutable object like a list or a dictionary as the default. A function's default value is evaluated only once when the function is defined. This means that every time you call the function without providing a value for that parameter, you're using the same exact list or dictionary object. This can lead to unexpected behavior.
Example of the Problem:
# List as default parameter.
def add_to_list_bad_practice(item, my_list=[]):
my_list.append(item)
return my_list
print(add_to_list_bad_practice('apple'))
print(add_to_list_bad_practice('banana'))
Output:
['apple']
['apple', 'banana']
As you can see, the second time the function was called, it didn't start with a new empty list. It modified the same list that was created the first time.
The Correct Solution:
To avoid this, you should use None as the default value and then check inside the function to see if a new mutable object needs to be created.
# List as default parameter.
def add_to_list_bad_practice(item, my_list=[]):
my_list.append(item)
return my_list
def add_to_list_good_practice(item, my_list=None):
if my_list is None:
my_list = []
my_list.append(item)
return my_list
print(add_to_list_bad_practice('apple'))
print(add_to_list_bad_practice('banana'))
print(add_to_list_good_practice('apple'))
print(add_to_list_good_practice('banana'))
fruits = []
print(add_to_list_good_practice('apple', fruits))
print(add_to_list_good_practice('kiwi', fruits))
print(add_to_list_good_practice('apple'))
print(add_to_list_good_practice('banana'))
Output:
['apple', 'banana']
['apple']
['banana']
['apple']
['apple', 'kiwi']
This is the correct way to handle optional mutable arguments, as it ensures a fresh, empty list is created for each function call where one isn't provided.
3. Return Values
A function doesn't just have to perform an action; it can also give a result back to the program. The return statement allows a function to send a value back to the place where it was called. You can then store this returned value in a variable or use it directly.
Example Code:
# Function with Return Value.
def add(x, y):
total = x + y
return total
print(add(2, 3))
print(add(5, 8))
Output:
5
13
4. Scope of Variables (Local vs. Global)
The scope of a variable determines where in your program it can be accessed. There are two main types of scope:
- Local Scope: A variable defined inside a function is local. It can only be accessed from within that function. Once the function finishes, the variable is destroyed.
- Global Scope: A variable defined outside of any function is global. It can be accessed from anywhere in the program, including inside functions.
Example Code:
# This is a global variable
global_message = "I am a global message."
def demonstrate_scope():
# This is a local variable
local_message = "I am a local message."
print(f"Inside the function: {local_message}")
print(f"Inside the function: {global_message}")
demonstrate_scope()
# This will work because global_message is in the global scope
print(f"Outside the function: {global_message}")
# This will cause an error because local_message is not in the global scope
try:
print(f"Outside the function: {local_message}")
except NameError as e:
print(f"\nCaught an error: {e}")
Output:
Inside the function: I am a local message.
Inside the function: I am a global message.
Outside the function: I am a global message.
Caught an error: name 'local_message' is not defined
While you can use the global keyword inside a function to modify a global variable, it's generally considered bad practice as it can make your code harder to debug. A better approach is to pass the variable as an argument and return the new value.
5. Docstrings and Comments
Documentation is a critical part of writing good code. Docstrings and comments serve different but equally important purposes.
- A docstring is a string literal placed as the first statement in a module, function, class, or method. It is meant to explain what the code does and how to use it. It's for other developers (or your future self) to understand the public interface of your function.
- A comment is used to explain the how and why of specific lines of code. It's for implementation details and is ignored by the Python interpreter.
Example Code:
def calculate_area(length, width):
"""
Calculates the area of a rectangle.
Args:
length (int): The length of the rectangle.
width (int): The width of the rectangle.
Returns:
int: The calculated area of the rectangle.
"""
# Multiply length and width to find the area
area = length * width
return area
# You can access the docstring using the __doc__ attribute
print(calculate_area.__doc__)
Output:
Calculates the area of a rectangle.
Args:
length (int): The length of the rectangle.
width (int): The width of the rectangle.
Returns:
int: The calculated area of the rectangle.
As you can see, the docstring provides a clear, formatted explanation of the function's purpose, while the comment explains a specific line of code. These five lessons provide a strong foundation for using functions effectively. Practicing these concepts will help you write clean, modular, and professional-grade code.


