CodeRigor

FREE LESSON

Initialization, properties & invariants

Construct valid objects and preserve nonnegative stock through checked methods and read-only properties.

An invariant is a statement that must hold whenever callers use an object’s public interface. For a stock item, the quantity is an actual integer and is never negative. The SKU is meaningful text. These rules belong to the object’s whole lifetime, so checking them in just one client function is insufficient. The constructor and every operation that can change stock must preserve them.

__init__ initializes an instance that Python has already created. It should return None; it does not return the new object. If initialization raises an exception, the class call fails and the caller does not receive a normally constructed value. Validate all required inputs before publishing useful state. This course does not customize __new__ or expose partly initialized objects.

Every public path preserves the invariant
start / 2
Construction validates inputs; adjustment validates a candidate before committing a nonnegative quantity.
Every public path preserves the invariant
Construction validates inputs; adjustment validates a candidate before committing a nonnegative quantity.

Keep the public write path small

A getter-only property exposes data while directing normal updates through a checked method. The decorator replaces a method definition with a property: callers write item.quantity, while the getter computes or returns the value. A property without a setter rejects ordinary assignment with AttributeError. It is an interface rule, not a defense against arbitrary hostile Python code.

Python
class Stock:
    def __init__(self, quantity):
        if type(quantity) is not int:
            raise TypeError("quantity must be an integer")
        if quantity < 0:
            raise ValueError("negative quantity")
        self._quantity = quantity

    @property
    def quantity(self):
        return self._quantity

    def adjust(self, delta):
        if type(delta) is not int:
            raise TypeError("delta must be an integer")
        candidate = self._quantity + delta
        if candidate < 0:
            raise ValueError("insufficient stock")
        self._quantity = candidate

item = Stock(3)
try:
    item.adjust(-4)
except ValueError:
    print("rejected", item.quantity)
item.adjust(-3)
print(item.quantity)

The order of those statements provides an important guarantee: a rejected adjustment leaves the old quantity unchanged. Compute a candidate in a local variable, check it, then assign it. Writing the backing attribute first and checking afterward would leave invalid state behind when an exception escapes.

The candidate is not live state
start / 3
Stock begins at two. A candidate of minus one is rejected; exact depletion to zero is allowed.
The candidate is not live state
Stock begins at two. A candidate of minus one is rejected; exact depletion to zero is allowed.

Precise types and boundaries

Python treats bool as a subclass of int, so isinstance(True, int) succeeds. This stock contract needs exact integers, making type(value) is int a deliberate choice. Converting with int() would accept text or truncate a float and change the contract. The lab distinguishes a wrong type (TypeError) from an integer outside the allowed range (ValueError).

Zero is a valid quantity and a valid adjustment. An item can be depleted exactly; a test only for positive examples misses that boundary. A blank SKU includes spaces and tabs, but a nonblank SKU retains its original spelling. Validation and normalization are different API decisions; do only the one the contract asks for.

Trace construction with zero, construction with minus one, exact depletion, and one unit too much. After each failure, inspect the same object again. The bug hunts target a constructor loophole and a check against old state. The fill practices validate-before-assign; the build adds strict SKU handling and read-only properties without introducing network or storage concerns.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Focus on read access through @property and how properties keep a stable public API while backing attributes change. Our stock class intentionally supplies no setters; adjust performs validated updates.