Python Fundamentals: Functions and File Operations

In software development, there are different approaches to organizing code. Procedural programming involves writing code sequentially from top to bottom based on business logic. However, this approach often leads to code duplication and poor maintainability.

Functional programming addresses these issues by encapsulating specific functionality into reusable blocks. When a particular piece of logic is needed more than three times, it is recommended to extract it into a function. This approach significantly improves code extensibility and reusability.

Think of functions as building blocks, similar to Lego bricks. You can combine these modular components to construct complex applications. Functions can call other functions, and the main function serves as the orchestrator that connects and invokes these functional blocks.

# module_example.py

def initialize_app():
    pass

def load_configuration():
    pass

def connect_database():
    pass

def start_server():
    initialize_app()
    load_configuration()
    connect_database()

if __name__ == '__main__':
    # This block only executes when the script runs directly
    # When imported as a module, __name__ equals the filename
    # When executed directly, __name__ equals '__main__'
    start_server()

Defining Custom Functions

The Problem with Procedural Code

Consider a monitoring system that sends alerts when system resources exceed thresholds:

while True:
    if cpu_usage > 90:
        # Send CPU alert email
        connect_to_smtp_server()
        send_email()
        disconnect_server()
    
    if disk_usage > 90:
        # Send disk alert email
        connect_to_smtp_server()
        send_email()
        disconnect_server()
    
    if memory_usage > 80:
        # Send memory alert email
        connect_to_smtp_server()
        send_email()
        disconnect_server()

This approach creates redundant code. Adding more alert types would require duplicating the email sending logic repeatedly.

The Functional Approach

def send_alert_email(message_content):
    connect_to_smtp_server()
    send_email(message_content)
    disconnect_server()

while True:
    if cpu_usage > 90:
        send_alert_email('CPU utilization exceeded threshold')
    
    if disk_usage > 90:
        send_alert_email('Disk space critically low')
    
    if memory_usage > 80:
        send_alert_email('Memory usage too high')

The functional version offers better readability and maintainability. The key differences are:

  • Procedural: Code written line by line, often with duplicated logic and poor reusability
  • Functional: Logic encapsulated in functions, eliminating repetition
  • Object-Oriented: Functions grouped and encapsulated within classes for better organization

Function Definition Syntax

def function_name(parameters):
    # Function body
    # Logic execution
    return value

Key components:

  • def: Keyword to define a function
  • Function name: Identifier used to call the function
  • Function body: The logic to be executed
  • Parameters: Input data for the function
  • Return value: Output data returned to the caller

Return Values

Functions communicate their execution status through return values:

def send_notification():
    # Notification logic here
    success = execute_send()
    
    if success:
        return True
    return False

while True:
    status = send_notification()
    
    if not status:
        log_error('Notification failed, retrying...')

Function Parameters

Standard Parameters

def greet_user(username):
    print(f"Hello, {username}")

greet_user('Alice')

The number of argumenst must match the number of parameters defined:

def display_info(name, age):
    print(f"{name} is {age} years old")

display_info('Bob')  # TypeError: missing required argument

Default Parameters

def create_profile(name, role='Guest'):
    print(f"Name: {name}, Role: {role}")

create_profile('Alice', 'Admin')  # Name: Alice, Role: Admin
create_profile('Bob')  # Name: Bob, Role: Guest

Default parameters must be placed after non-default parameters in the function definition.

Variable-Length Arguments

Using *args to accept multiple positional arguments (converted to a tuple):

def calculate_sum(*args):
    total = 0
    for num in args:
        total += num
    return total

calculate_sum(1, 2, 3, 4, 5)  # Returns 15

numbers = [10, 20, 30]
calculate_sum(*numbers)  # Unpacks list into individual arguments

Using **kwargs to accept keyword arguments (converted to a dictionary):

def display_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

display_info(name='Alice', age=25, city='New York')

user_data = {'name': 'Bob', 'age': 30}
display_info(**user_data)

Combining both:

def process_data(*args, **kwargs):
    print(f"Positional: {args}")
    print(f"Keyword: {kwargs}")

process_data(1, 2, 3, name='Alice', status='active')
# Output:
# Positional: (1, 2, 3)
# Keyword: {'name': 'Alice', 'status': 'active'}

Practical Example: Email Alert System

import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr

def send_alert_email(subject_line):
    message = MIMEText('System Alert Notification', 'plain', 'utf-8')
    message['From'] = formataddr(['Alert System', 'alerts@example.com'])
    message['To'] = formataddr(['Admin', 'admin@example.com'])
    message['Subject'] = subject_line
    
    server = smtplib.SMTP('smtp.example.com', 25)
    server.login('alerts@example.com', 'password')
    server.sendmail('alerts@example.com', ['admin@example.com'], message.as_string())
    server.quit()

if __name__ == '__main__':
    cpu_threshold = 95
    disk_threshold = 85
    memory_threshold = 90
    
    if cpu_threshold > 90:
        send_alert_email('CPU Critical Alert')
    
    if disk_threshold > 80:
        send_alert_email('Disk Space Warning')
    
    if memory_threshold > 85:
        send_alert_email('Memory Usage Alert')

Built-in Functions

Python provides numerous built-in functions for common operations:

# Check data type
data = [1, 2, 3, 4]
print(type(data))  # <class>

# List available methods
print(dir(list))

# Get detailed help
help(list)
</class>

Variable Scope

Variables defined inside a function are local to that function and cannot be accessed from outside:

def show_name():
    user_name = "Alice"
    print(user_name)

show_name()  # Output: Alice
print(user_name)  # NameError: name not defined

However, functions can access global variables:

global_name = "Bob"

def display():
    local_name = "Alice"
    print(local_name)
    print(global_name)

display()
# Output: Alice
# Output: Bob

Modifying a global variable inside a function creates a new local variable by default:

counter = 10

def increment():
    counter = 20
    print(f"Inside: {counter}")

increment()  # Inside: 20
print(f"Outside: {counter}")  # Outside: 10

To modify a global variable within a function, use the global keyword:

counter = 10

def increment():
    global counter
    counter = 20
    print(f"Inside: {counter}")

increment()  # Inside: 20
print(f"Outside: {counter}")  # Outside: 20

Using global is generally discouraged as it can lead to confusing code and unexpected side effects.

The Return Statement

The return statement immediately exits the function and optionally returns a value:

def find_first_match(target, data_list):
    for index, value in enumerate(data_list):
        if value == target:
            return index
        print(f"Checking index {index}: {value}")
    return -1

result = find_first_match(5, [1, 2, 3, 4, 5, 6, 7])
print(f"Found at index: {result}")

def process_user():
    user_name = "Alice"
    for i in range(5):
        if i == 3:
            print("Processing complete")
        else:
            print(i)
    return user_name

returned_name = process_user()
if returned_name == "Alice":
    print("User processed successfully")

File Operations

Opening Files

File operations typically involve three steps: opening, reading/writing, and closing.

file_handle = open('filepath.txt', 'mode')

Common file modes:

  • r - Read mode (default)
  • w - Write mode (overwrites existing content)
  • a - Append mode (adds to end of file)
  • r+ - Read and write mode
  • b - Binary mode (for non-text files)

File Object Methods

# Read entire file
content = file_handle.read()

# Read one line
line = file_handle.readline()

# Read all lines into a list
lines = file_handle.readlines()

# Write to file
file_handle.write('content')

# Get current position
position = file_handle.tell()

# Move to specific position
file_handle.seek(0)

# Close the file
file_handle.close()

Using Context Managers

The with statement ensures proper file handling by automatically closing files:

with open('data.txt', 'r') as file:
    content = file.read()
    # File automatically closes when block exits

Managing multiple files simultaneously:

with open('source.txt', 'r') as source, open('destination.txt', 'w') as dest:
    for line in source:
        cleaned_line = line.strip()
        dest.write(cleaned_line + '\n')

This pattern is particularly useful for configuration file processing or data transformation tasks.

Tags: python functions File Handling variable scope parameters

Posted on Wed, 07 Oct 2026 16:55:24 +0000 by andz