Exercises — Chapter 39: Python Best Practices and Collaborative Development

Starred exercises (*) have worked solutions in Appendix B.


Tier 1: Recall

1.1 What is the purpose of git init? What does it create in your project folder?

1.2 ★ Explain the three steps in the basic Git commit workflow. What does each command do?

git add .
git commit -m "message"
git push origin main

1.3 What is a .gitignore file? List four types of files or directories that should always be ignored in a Python project.

1.4 What does pytest look for when it runs? What naming convention must test files and test functions follow?

1.5 ★ What is pytest.approx? Why is it necessary when testing financial calculations?

1.6 What is the difference between a type hint and type enforcement? Does Python enforce type hints at runtime?

1.7 What does mypy do? How does it differ from running your code?

1.8 What is the difference between black and flake8? Could you use one without the other?

1.9 ★ Define the following Git terms: repository, commit, branch, merge, pull request.

1.10 What should a good commit message communicate? Write one good and one poor commit message for the same hypothetical change.


Tier 2: Apply

2.1 ★ Create a file called .gitignore for a typical Python business analytics project. It should exclude at least: virtual environment folder, Python cache files, .env files, raw data CSVs, and macOS system files. Include at least one exception (a file that would normally be ignored but should be tracked).

2.2 Write a test function test_apply_discount_standard() that tests the apply_discount function with a 20% discount on a $250 price. Include an assertion that uses pytest.approx. Then write a second test test_apply_discount_zero_discount() for the edge case where discount_rate is 0.

2.3 ★ Take the following untyped function and rewrite it with complete type hints, including the return type. Then write a mypy comment explaining what type error it would catch if someone called it with a string argument.

def calculate_markup(cost, markup_percentage):
    """Return the selling price given cost and markup percentage."""
    if cost < 0:
        return None
    return cost * (1 + markup_percentage / 100)

2.4 Given the following poorly formatted code, rewrite it as black would format it. Do not change the logic — only the style.

def get_customer_tier(spend,thresholds={'platinum':250000,'gold':100000,'silver':25000}):
    if spend>=thresholds['platinum']: return 'Platinum'
    elif spend>=thresholds['gold']: return 'Gold'
    elif spend>=thresholds['silver']: return 'Silver'
    else: return 'Standard'

2.5 ★ Write a complete Google-style docstring for the following function. Include Args, Returns, Raises, and at least one Example.

def calculate_clv(monthly_revenue, gross_margin_rate, monthly_churn_rate):
    if monthly_churn_rate <= 0:
        return 0.0
    monthly_margin = monthly_revenue * gross_margin_rate
    return monthly_margin / monthly_churn_rate

Tier 3: Build

3.1 ★ Write a pytest test class TestCalculateClv for the calculate_clv function above. Include tests for: - Standard case with known expected output - Zero churn rate (returns 0.0) - Very high churn rate (returns low CLV) - Negative churn rate (same behavior as zero) - Result is always non-negative for valid inputs

3.2 Set up a complete Git workflow for a new project called my-analytics. Do the following in sequence, showing the terminal commands for each step: 1. Initialize a local repository 2. Create a .gitignore 3. Add a Python script and make the first commit with a meaningful message 4. Create a feature branch named feature/monthly-summary 5. Make a change and commit it on the feature branch 6. Merge the feature branch back to main 7. List the commit history in one-line format

3.3 ★ The following function has no type hints and no docstring. Rewrite it with: - Complete type hints (Python 3.10+ style) - A Google-style docstring - A companion test file with at least 5 tests

def calculate_break_even(fixed_costs, price_per_unit, variable_cost_per_unit):
    contribution_margin = price_per_unit - variable_cost_per_unit
    if contribution_margin <= 0:
        return None
    return fixed_costs / contribution_margin

3.4 Install black and flake8 in a virtual environment. Take any Python script you have written in a previous chapter and run both tools against it. Document what black changed and what flake8 flagged. Then fix all flake8 issues and re-run to confirm zero warnings.

3.5 ★ A colleague gives you this function and tells you it "works fine." Write a test suite that covers enough edge cases to expose the hidden bug.

def calculate_team_bonus_pool(team_revenue, target_revenue, bonus_percentage):
    """Calculate total bonus pool based on revenue attainment."""
    attainment = team_revenue / target_revenue
    if attainment >= 1.0:
        # Full bonus at 100% attainment, 1.5x at 150%
        return team_revenue * bonus_percentage * min(attainment, 1.5)
    elif attainment >= 0.8:
        # 80% of bonus for 80-99% attainment
        return team_revenue * bonus_percentage * 0.8
    else:
        return 0.0

Hint: what happens when target_revenue is zero?


Tier 4: Extend

4.1 Configure a complete pre-commit setup for a Python project. Create the .pre-commit-config.yaml file that runs black, flake8, and at least one pre-commit-hooks check (trailing whitespace or end-of-file fixer). Install the hooks, make a test commit that triggers them, and describe what happened.

4.2 ★ Take the business_math.py module from Chapter 6 and perform a complete professional upgrade: - Add type hints to every function (Python 3.10+ style) - Add Google-style docstrings to any function that lacks a complete one - Run mypy business_math.py and fix any issues - Run black business_math.py and note what changed - Run flake8 business_math.py and fix any warnings

Document each step with before-and-after comparisons where relevant.

4.3 Write a test suite for the data_loader.py module (or equivalent file-loading code from a previous chapter). Your tests should include: - At least one test that uses a real sample CSV file (place the sample in tests/fixtures/) - A test that handles a missing file gracefully (pytest should test that an exception is raised) - A test that validates the column names of the loaded DataFrame

4.4 Simulate a GitHub code review scenario. Choose a function you've written in a previous chapter. Write three reviewer comments (at least one blocking, at least one non-blocking) as if you were Marcus reviewing Priya's PR. Then write your response to each comment and show the updated code.


Tier 5: Portfolio

5.1 ★ (Portfolio Project) Select a Python script you have written during this course — ideally one that runs against real or realistic data. Perform a complete professional upgrade:

  1. Create a new Git repository for the project
  2. Write a proper README.md explaining what the project does, how to install dependencies, and how to run it
  3. Add type hints to all functions
  4. Add Google-style docstrings to all functions
  5. Write a test suite with at least 10 tests covering happy path, edge cases, and invalid inputs
  6. Configure black, flake8, and mypy — run all three and fix all warnings and errors
  7. Create a .gitignore and requirements.txt
  8. Push to GitHub with meaningful commit history (at least 3 commits)

This is a portfolio-ready project. The README, the tests, and the clean code tell any technical reviewer that you take software quality seriously.

5.2 (Stretch) Research and implement a full CI/CD (Continuous Integration) pipeline using GitHub Actions. Create a .github/workflows/tests.yml file that: - Triggers on every push to main and on every pull request - Sets up Python 3.11 - Installs dependencies from requirements.txt - Runs pytest with a coverage report - Runs black --check and flake8 - Fails the build if any check fails

Push a PR to trigger the workflow and confirm it passes. Include a screenshot of the green checkmarks in your submission.

5.3 (Advanced Type Hints) Research TypedDict, Protocol, and dataclasses from the Python standard library. Rewrite the margin summary example from the chapter using a dataclass instead of a NamedTuple. Write tests that verify the dataclass behaves correctly. Explain in a comment when you would prefer a dataclass over a NamedTuple.