Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OOP in Python — Maintainable Code Demo

A progressive learning project demonstrating Object-Oriented Programming principles in Python using a single domain: a Library Catalog System.

Each module introduces one OOP concept, builds on the previous one, and is covered by a matching test file using pytest.


Python in a Nutshell

Interpreted, not compiled

Python is an interpreted language. There is no separate compile step before you can run code. When you execute a file, the Python interpreter reads it line by line and runs it immediately.

Your .py file  →  Python interpreter  →  Output / errors

Compare this to compiled languages:

Language Flow
C / C++ / Go write → compile → binary → run
Java / C# write → compile → bytecode → run on VM
Python write → run directly

This means:

  • Syntax errors appear the moment you run the file (or when pytest tries to collect it).
  • Type errors only appear at runtime unless you run a separate type checker like mypy.
  • The feedback loop is fast — save, run, see result immediately.

Python's internal bytecode cache

Even though there is no manual compile step, Python does compile your .py files to bytecode (.pyc) behind the scenes. This is an optimisation — it avoids re-parsing unchanged files on the next run. You will see these appear as __pycache__ folders automatically. See the __pycache__ section below.


Project Structure

python/
├── pyproject.toml              # Project metadata + pytest configuration
├── requirements.txt            # Dev dependencies (pytest, pytest-cov)
│
├── src/
│   └── oop_demo/
│       ├── __init__.py         # Marks oop_demo as a package
│       ├── basics/
│       │   ├── __init__.py     # Marks basics/ as a sub-package
│       │   ├── catalog_item.py
│       │   └── README.md
│       ├── inheritance/
│       │   ├── __init__.py
│       │   ├── media.py
│       │   └── README.md
│       ├── encapsulation/
│       │   ├── __init__.py
│       │   ├── member.py
│       │   └── README.md
│       ├── abstraction/
│       │   ├── __init__.py
│       │   ├── interfaces.py
│       │   ├── borrowable_item.py
│       │   └── README.md
│       ├── polymorphism/
│       │   ├── __init__.py
│       │   ├── rich_item.py
│       │   └── README.md
│       └── patterns/
│           ├── __init__.py
│           ├── factory.py
│           ├── observer.py
│           ├── repository.py
│           └── README.md
│
└── tests/
    ├── __init__.py
    ├── conftest.py             # Shared pytest fixtures
    ├── test_01_basics.py
    ├── test_02_inheritance.py
    ├── test_03_encapsulation.py
    ├── test_04_abstraction.py
    ├── test_05_polymorphism.py
    └── test_06_patterns.py

__init__.py — Package Marker Files

What they are

A folder containing an __init__.py file is called a package in Python. Without it, Python will not treat the folder as importable — it is just a directory.

src/oop_demo/basics/catalog_item.py

For this import to work anywhere in the project:

from oop_demo.basics.catalog_item import CatalogItem

Python needs to find __init__.py at each level of the path:

src/oop_demo/__init__.py        ← makes oop_demo a package
src/oop_demo/basics/__init__.py ← makes basics a sub-package

What goes inside them

In this project every __init__.py is empty. That is the modern convention: let each module be imported by its full path. The file just needs to exist.

You can put code in __init__.py — for example, to create a shortcut import:

# src/oop_demo/__init__.py  (if we wanted shortcuts)
from oop_demo.basics.catalog_item import CatalogItem
from oop_demo.inheritance.media import Book

That would allow from oop_demo import CatalogItem instead of the full path. We keep them empty here to make the package structure explicit and easy to navigate.

Why tests/ also has one

tests/__init__.py tells pytest (and Python) that the test folder is also a package. This avoids name collision issues when pytest collects test files across nested folders.


__pycache__ — The Bytecode Cache

What it is

When Python first imports a module, it compiles the .py source to bytecode and saves it as a .pyc file inside a __pycache__ folder next to the source file:

src/oop_demo/basics/
├── __init__.py
├── catalog_item.py
├── README.md
└── __pycache__/
    ├── __init__.cpython-314.pyc
    └── catalog_item.cpython-314.pyc

The filename encodes the Python version (cpython-314 = CPython 3.14) so multiple Python versions can coexist without overwriting each other's cache.

What bytecode is

Bytecode is a lower-level representation of your code that the Python Virtual Machine (PVM) executes directly — similar to Java's .class files. It is not machine code and is not platform-specific. It cannot be run without Python installed.

Why it exists

Without cache With cache
Parse + compile .py on every run Parse + compile once, load .pyc on subsequent runs
Slower startup for large projects Faster import times

Python checks the modification timestamp of the .py file on each import. If the source changed, it recompiles automatically. You never need to manage this manually.

Safe to delete, safe to ignore

  • __pycache__ is regenerated automatically on the next run — you can delete it freely.
  • It should be listed in .gitignore so it is never committed to version control.
  • You can tell Python to skip writing it entirely: python -B your_script.py

Validating Code Syntax

Python gives you several tools at different levels of strictness.

1. Run it — fastest feedback

python src/oop_demo/basics/catalog_item.py

If there is a SyntaxError, Python reports it immediately with the file and line number. This is the equivalent of a compile check.

2. Syntax check only, no execution

python -m py_compile src/oop_demo/basics/catalog_item.py

Parses and compiles the file but does not run it. No output means no errors. Useful when you want to check syntax without side effects.

3. Run the tests — best all-round check

# run everything from the project root
python -m pytest

# run one module's tests
python -m pytest tests/test_02_inheritance.py -v

# stop at the first failure
python -m pytest -x

# show full output even on passing tests
python -m pytest -s

If any file has a syntax error, pytest reports it during the collection phase before a single test runs. A passing test suite guarantees both correct syntax and correct behaviour.

4. Type checking with mypy (optional, recommended)

Python's type hints (: str, -> bool, etc.) are not enforced at runtime. mypy reads them statically and reports type mismatches without running anything:

python -m pip install mypy
python -m mypy src/

Example output when something is wrong:

src/oop_demo/encapsulation/member.py:42: error: Argument 1 to "add_fine" has
incompatible type "str"; expected "float"  [arg-type]

This is the closest Python gets to a compiled-language type checker.

5. Coverage — find untested lines

python -m pytest --cov=oop_demo --cov-report=term-missing

Adds a Miss column to the test output showing exactly which line numbers have no test exercising them.


Domain

All modules use a Library Catalog as the domain:

Class Module Role
CatalogItem basics Base entity — title, author, year, ISBN
Book, Magazine, AudioBook, DVD inheritance Specialised media types
Member encapsulation Library member with fines and borrow limits
Borrowable, Searchable abstraction Contracts items must fulfil
BorrowableBook abstraction Combines ABC + concrete class
RichCatalogItem polymorphism Full Python data model (dunder methods)
MediaFactory patterns Centralised object creation
LibraryEventBus patterns Observer pattern for borrow events
CatalogRepository patterns Storage abstraction + in-memory impl

Quick Start

# Install dependencies
python -m pip install -r requirements.txt

# Run all tests
python -m pytest

# Run with coverage report
python -m pytest --cov=oop_demo --cov-report=term-missing

# Run a single module's tests
python -m pytest tests/test_03_encapsulation.py -v

# Syntax check a single file without running it
python -m py_compile src/oop_demo/basics/catalog_item.py

# Type check the whole source tree
python -m mypy src/

Learning Path

Follow the modules in order — each folder has its own README explaining the concept, the Python feature that implements it, and what the tests verify.

# Folder Concept Key Python feature
01 basics/ Classes & state __init__, class vars, @classmethod
02 inheritance/ Reuse & extension super(), MRO, method overriding
03 encapsulation/ Information hiding __private, _protected, @property
04 abstraction/ Contracts ABC, @abstractmethod, Protocol
05 polymorphism/ Rich behaviour Dunder methods, @total_ordering
06 patterns/ Design patterns Factory, Observer, Repository

Libraries & Tools

Tool Type Purpose
pytest third-party Test runner — fixtures, assertions, collection
pytest-cov third-party Coverage reporting
mypy third-party (optional) Static type checker
abc stdlib Abstract Base Classes and @abstractmethod
typing stdlib Protocol, runtime_checkable, type hints
dataclasses stdlib Boilerplate-free value objects with @dataclass
functools stdlib total_ordering — derive comparison operators
py_compile stdlib Syntax-only validation without execution

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages