Skip to content

Latest commit

 

History

History
573 lines (419 loc) · 12.5 KB

File metadata and controls

573 lines (419 loc) · 12.5 KB

Lab 1.3: Python Modules and Script Basics

Overview

Python's power comes from organizing code into reusable modules. Understanding how modules work is fundamental to writing professional Python code.

In this lab, you'll learn:

  • How Python modules are just regular files
  • Creating packages with __init__.py
  • The if __name__ == "__main__" pattern
  • Reading command-line arguments with sys.argv

Part 1: Modules Are Just Files

A Python module is simply a .py file containing Python code. That's it!

Step 1: Create a Simple Module

mkdir ~/python-modules
cd ~/python-modules

Create math_utils.py:

Do not name your file math.py, as this will conflict with the existing math module.

"""
Math utility functions

Docstrings are special! They become the module/function documentation
and can be viewed with help() or pydoc.
"""

def add(a, b):
    """Add two numbers"""
    return a + b

def multiply(a, b):
    """Multiply two numbers"""
    return a * b

def power(base, exponent):
    """Raise base to the power of exponent"""
    return base ** exponent

# This will run when the module is imported!
print(f"math_utils module loaded!")

Step 2: Import and Use the Module

Create main.py:

import math_utils

# Use functions from the module
result1 = math_utils.add(10, 5)
result2 = math_utils.multiply(4, 7)
result3 = math_utils.power(2, 8)

print(f"10 + 5 = {result1}")
print(f"4 × 7 = {result2}")
print(f"2^8 = {result3}")

Run it:

python main.py

Expected output:

math_utils module loaded!
10 + 5 = 15
4 × 7 = 28
2^8 = 256

Notice: The print() statement in math_utils.py ran when we imported it!

Step 3: Viewing Module Documentation

Docstrings are available via help() and pydoc (covered in Lab 1.1):

python
>>> import math_utils
math_utils module loaded!

>>> # View module documentation
>>> help(math_utils)
# Shows: "Math utility functions" and all function docs

>>> # View specific function documentation  
>>> help(math_utils.add)
# Shows: "Add two numbers"

>>> exit()

Or use pydoc from the terminal:

pydoc math_utils
pydoc math_utils.add

Key point: Docstrings (triple-quoted strings) are not just comments - they're live documentation accessible via help() and pydoc. Always write them!

Step 4: Different Import Styles

Create import_examples.py:

# Method 1: Import entire module
import math_utils
print(math_utils.add(1, 2))

# Method 2: Import specific functions
from math_utils import add, multiply
print(add(3, 4))
print(multiply(5, 6))

# Method 3: Import with alias
import math_utils as mu
print(mu.power(3, 3))

# Method 4: Import everything (not recommended!)
from math_utils import *
print(add(7, 8))

Run it:

python import_examples.py

Best practice: Use Method 1 or 2. Avoid import * in production code - it makes it unclear where functions come from.

Step 5: *args, **kwargs, and Unpacking

Create args_demo.py:

# *args - variable positional arguments (becomes tuple)
def add_all(*args):
    """Add any number of arguments"""
    return sum(args)

print(f"add_all(1, 2, 3): {add_all(1, 2, 3)}")
print(f"add_all(10, 20, 30, 40): {add_all(10, 20, 30, 40)}")

# **kwargs - variable keyword arguments (becomes dict)
def greet(name, **kwargs):
    """Greet with options"""
    greeting = kwargs.get("greeting", "Hello")
    punctuation = kwargs.get("punctuation", "!")
    return f"{greeting}, {name}{punctuation}"

print(f"greet('Alice'): {greet('Alice')}")
print(f"greet('Bob', greeting='Hi', punctuation='!!!'): {greet('Bob', greeting='Hi', punctuation='!!!')}")

# Unpacking in assignments
numbers = [1, 2, 3, 4, 5]
a, b, *rest = numbers
print(f"\na, b, *rest = {numbers}")
print(f"a={a}, b={b}, rest={rest}")

# Unpacking in function calls
values = [10, 20, 30]
print(f"\nadd_all(*{values}) = {add_all(*values)}")

# Merging dicts with unpacking
defaults = {"host": "localhost", "port": 8080}
overrides = {"port": 443}
config = {**defaults, **overrides}
print(f"\nMerged config: {config}")

Run it:

python args_demo.py

Key points:

  • *args collects extra positional arguments into a tuple
  • **kwargs collects extra keyword arguments into a dict
  • *list unpacks a list into separate arguments
  • **dict unpacks a dict into keyword arguments
  • a, *rest = list captures remainder in unpacking

Part 2: Creating Packages with __init__.py

A package is a directory containing an __init__.py file. This tells Python "this folder is a package."

Step 1: Create a Package Structure

cd ~/python-modules
mkdir -p utils

Create utils/__init__.py:

"""
Utils package - collection of utility modules
"""
print("Utils package initialized!")

Create utils/string_utils.py:

"""
String manipulation utilities
"""

def reverse_string(text):
    """Reverse a string"""
    return text[::-1]

def capitalize_words(text):
    """Capitalize each word"""
    return text.title()

def count_vowels(text):
    """Count vowels in text"""
    vowels = 'aeiouAEIOU'
    return sum(1 for char in text if char in vowels)

Create utils/file_utils.py:

"""
File operation utilities
"""

def count_lines(filename):
    """Count lines in a file"""
    try:
        with open(filename, 'r') as f:
            return len(f.readlines())
    except FileNotFoundError:
        return 0

def get_file_size(filename):
    """Get file size in bytes"""
    import os
    try:
        return os.path.getsize(filename)
    except FileNotFoundError:
        return 0

Step 2: Verify Package Structure

# Check the structure
tree utils/
# Or use:
ls -R utils/

You should see:

utils/
├── __init__.py
├── string_utils.py
└── file_utils.py

Step 3: Import from the Package

cd ~/python-modules

Create use_package.py:

# Import modules from package
from utils import string_utils
from utils import file_utils

# Use string utilities
text = "hello world"
print(f"Original: {text}")
print(f"Reversed: {string_utils.reverse_string(text)}")
print(f"Capitalized: {string_utils.capitalize_words(text)}")
print(f"Vowel count: {string_utils.count_vowels(text)}")

# Use file utilities
print(f"\nLines in math_utils.py: {file_utils.count_lines('math_utils.py')}")
print(f"Size: {file_utils.get_file_size('math_utils.py')} bytes")

Run it:

python use_package.py

Expected Similar output:

Utils package initialized!
Original: hello world
Reversed: dlrow olleh
Capitalized: Hello World
Vowel count: 3

Lines in math_utils.py: 18
Size: 356 bytes

What happened:

  1. Python found the utils/ directory
  2. Saw __init__.py - recognized it as a package
  3. Ran __init__.py (printed "Utils package initialized!")
  4. Loaded the requested modules

Part 3: Understanding if __name__ == "__main__"

This is one of Python's most common patterns. Let's demystify it!

Step 1: The __name__ Variable

Create show_name.py:

print(f"The __name__ variable is: {__name__}")

Run it directly:

python show_name.py

Output:

The __name__ variable is: __main__

When run directly, __name__ is always "__main__".

Step 2: Import the Same File

Create import_show_name.py:

import show_name
print("Done importing")

Run it:

python import_show_name.py

Output:

The __name__ variable is: show_name
Done importing

When imported, __name__ is the module name!

Why This Matters

The if __name__ == "__main__": pattern lets you:

  • Run module directly for testing (the code in the if block executes)
  • Import as library without running test code (the if block is skipped)

This is the professional way to structure Python files:

  • Functions/classes at the top (can be imported)
  • Test/demo code at the bottom in if __name__ == "__main__": block (only runs when executed directly)

Part 4: Command-Line Arguments with sys.argv

Scripts need input! sys.argv is a list containing command-line arguments.

Step 1: Basic sys.argv

Create show_args.py:

import sys

print(f"Script name: {sys.argv[0]}")
print(f"Number of arguments: {len(sys.argv)}")
print(f"All arguments: {sys.argv}")

# Print each argument
for i, arg in enumerate(sys.argv):
    print(f"  argv[{i}] = {arg}")

Try with different arguments:

python show_args.py
python show_args.py hello world
python show_args.py one two three four

Key points:

  • sys.argv[0] is always the script name
  • Arguments start at sys.argv[1]
  • All arguments are strings!

Step 2: Simple Greeter Script

Create greet.py:

#!/usr/bin/env python3
import sys

if len(sys.argv) < 2:
    print("Usage: python greet.py <name>")
    sys.exit(1)

name = sys.argv[1]
print(f"Hello, {name}!")

Test it:

python greet.py Alice
python greet.py  # Shows usage message

Part 5: Hands-On Challenge

Math CLI Tool Module

Your task: Create a calculator that works as both a CLI tool and importable module. Avoid using AI to enforce learning these skills.

Requirements:

  1. Create calc_cli.py with:
    • Module docstring explaining what it does
    • Functions that accept any number of arguments using *args:
      • add(*args) - Add all numbers
      • subtract(*args) - Subtract all numbers from first
      • multiply(*args) - Multiply all numbers
      • divide(*args) - Divide first number by all others
    • Function docstrings for each operation
    • CLI interface using if __name__ == "__main__" pattern
    • Error handling for invalid input and division by zero
    • Usage message if arguments are wrong

Usage examples:

# CLI usage
python calc_cli.py add 10 5                → Result: 15.0
python calc_cli.py add 10 5 5 5            → Result: 25.0
python calc_cli.py multiply 2 3 4          → Result: 24.0
python calc_cli.py divide 100 2 5          → Result: 10.0
python calc_cli.py                         → Shows usage message
# Module usage
import calc_cli

result = calc_cli.add(1, 2, 3, 4, 5)       # Returns 15
result = calc_cli.multiply(2, 3, 4)        # Returns 24

Hints:

  • Use *args to accept variable number of arguments
  • Check len(sys.argv) >= 3 for minimum argument count (script, operation, at least one number)
  • Convert string arguments to numbers with float()
  • Use try/except for error handling
  • For subtract: first - second - third - ...
  • For divide: first / second / third / ...
  • Test both as CLI and as imported module!

Success Criteria

You've completed this lab when you can:

  • Create and import Python modules (just .py files)
  • Organize modules into packages with __init__.py
  • Understand and use if __name__ == "__main__" pattern
  • Read command-line arguments with sys.argv
  • Create scripts that work as both libraries and CLI tools
  • Structure code professionally with separate modules

Key Takeaways

What you learned:

  • Modules - Any .py file is a module you can import
  • Packages - Directories with __init__.py are packages
  • __name__ - Changes based on how code is executed (direct vs import)
  • sys.argv - Access command-line arguments as a list of strings
  • Dual-purpose code - Write libraries that also work as scripts

Why this matters:

  • Organize large projects into manageable pieces
  • Write reusable code that can be imported anywhere
  • Create CLI tools for automation and DevOps tasks
  • Professional Python code structure
  • Essential for building packages and distributing code

Additional Resources


Quick Reference

# Import module
import mymodule
from mymodule import function

# Create package (directory structure)
mypackage/
├── __init__.py
├── module1.py
└── module2.py

# if __name__ pattern
if __name__ == "__main__":
    # Code here only runs when executed directly
    pass

# Command-line arguments
import sys
script_name = sys.argv[0]
first_arg = sys.argv[1]
all_args = sys.argv[1:]  # All except script name

Congratulations! You now understand Python modules, packages, and script structure - essential skills for building professional Python applications!