Skip to lesson content

PYTHON / LESSON 17 OF 18

Best practices

Read the explanations, work through the examples, and complete the student tasks.

Learning goals

Write maintainable functions, avoid shared defaults, organize projects, and verify important behavior.

Explanation

Follow PEP 8 conventions: four-space indentation, snake_case for functions and variables, CapWords for classes, and UPPER_CASE for constants. PEP 8 recommends a 79-character limit for code lines, while some teams deliberately choose a different formatter limit. Consistency and readability matter more than blindly mixing conventions.

Keep a small project's structure simple: application modules, tests, README.md, requirements.txt, and .gitignore. Larger packaged projects can use a src directory with installation configuration. Keep .venv, caches, and generated practice output out of version control. Explain how to install and run the project in its README.

Example 1 Avoid a shared mutable default

def add_tag(tag, tags=None):
    """Return a list containing the supplied tag."""
    if tags is None:
        tags = []
    tags.append(tag)
    return tags

first = add_tag("python")
second = add_tag("testing")
print(first)
print(second)

Expected output: ['python'] followed by ['testing']. A default tags=[] would be created once at function definition time and reused. The None pattern creates a fresh list for each omitted argument. When an existing list is passed, this function intentionally mutates it; document that behavior or copy it if mutation is unwanted.

Example 2 Small functions and meaningful checks

def average_score(scores):
    """Return the mean of a nonempty sequence of scores."""
    if not scores:
        raise ValueError("At least one score is required")
    return sum(scores) / len(scores)

assert average_score([60, 75, 90]) == 75
assert average_score([100]) == 100
try:
    average_score([])
except ValueError:
    print("Empty input correctly rejected")
else:
    raise AssertionError("Expected ValueError")

Expected output: Empty input correctly rejected. These checks cover a normal case, a single item, and an invalid boundary. Assertions are useful here for tests; do not rely on them for essential production validation because optimized execution can disable them.

Dependencies and logging

After installing only the project's dependencies into a clean environment, record them:

python -m pip freeze > requirements.txt
python -m pip install -r requirements.txt

The first command overwrites requirements.txt with installed versions. A clean environment makes that list more relevant. For scripts that will run unattended, use logging instead of scattered print calls:

import logging
logging.basicConfig(level=logging.INFO,
                    format="%(levelname)s %(message)s")
logging.info("Processed %s records", 3)

Expected message: INFO Processed 3 records. Avoid logging credentials or sensitive record contents. Write comments explaining intent; clear function names should explain ordinary mechanics.

Student tasks

1. Refactor a previous lesson's script into at least two focused functions.

2. Add tests for a normal case, an empty input, and a boundary value.

3. Write a README with the Python requirement, setup commands, run command, and sample output.

4. Explain and demonstrate the mutable-default bug, then fix it using None.

Completion check: another student should be able to run your project from the README alone.