5 Hide class implementations
This chapter argues that robust Python designs hide class implementations to preserve loose coupling and make change safe. Encapsulation is achieved by exposing a minimal public interface while keeping state and helper behavior private (by convention, names starting with an underscore). This aligns with the Principle of Least Knowledge (Law of Demeter): classes should know as little as possible about each other and call only “nearby” objects, minimizing dependencies. The practical takeaway is “once public, always public”—default to privacy and reveal only what clients truly need.
To access hidden state safely, the chapter promotes properties as getters/setters that validate inputs and prevent callers from depending on representation. A detailed Date case study shows why: an initial, correct but inefficient, loop-based Gregorian implementation gives way to a far faster version based on Julian day numbers, and then to a hybrid with lazy evaluation that caches both y/m/d and Julian values behind synchronization flags. Because clients interact only through a stable public surface (constructor, getters, add_days, days_from), the class can be radically refactored without breaking callers. Lazy evaluation delays expensive conversions until needed, improving performance without leaking implementation details.
The chapter also warns that setters can be dangerous: per-field setters for year/month/day allow invalid states (for example, February 31), whereas validated bulk updates or immutable designs avoid such pitfalls. Even “immutable” objects can be compromised if getters expose internal mutable references; returning defensive copies (as with Employee.birthdate) preserves invariants. The Law of Demeter’s concrete rules, illustrated with the DemeterAuto example, discourage reaching through chains of objects. Finally, the Open-Closed Principle supports stability: keep common behavior closed to modification yet open to extension (for example, Mammal with Human and Cat subclasses), while keeping both shared and extended internals hidden. The net effect is code that is safer to evolve, easier to reason about, and harder to misuse.
A getter method enables code to obtain the price value from an Item object’s hidden state. A setter method enables code to modify the price value in an Item object’s hidden state. Neither method reveals how the class implements state. For example, we might later decide to have getter method price() dynamically look up the price from a database. We’ll be able to make this change without making changes to the method’s caller.
The version of class Date from iteration 1 performs date arithmetic correctly, but it does it poorly because of expensive looping day-by-day with the Gregorian calendar rules. This version deserves a lump of coal.
The version of class Date from iteration 2 performs data arithmetic very efficiently using Julian day numbers, but getter methods for the year, month, or day of the month must each call an expensive conversion algorithm. We get yet another lump of coal.
The version of class Date from iteration 3 is a hybrid that uses lazy evaluation. It has the best performance of the three versions. This version deserves the pot of gold.
A design of the book catalogue application with superclass Attributes and its subclasses. In the class diagram for Attributes, the name of private method _equal_ignore_case() is underlined to indicate that it is static.
Summary
- Python programmers must respect the convention that names of instance variables and methods that begin with an underscore are denoted to be private and therefore have hidden implementations.
- Minimize dependencies on a class by hiding how the class implements state to support encapsulation. Hide instance variables and methods by making denoting them to be private. When the hidden implementation of a class is encapsulated, we can refactor the implementation to improve it without causing changes to any code using the class.
- Properties allow controlled access to hidden object state. A property can return values from an object’s state without revealing how the state is implemented. A property setter can modify an object’s state without revealing how the state is implemented.
- A property should never allow an object to be put into an invalid state. We must be careful what property setters to provide.
- An immutable class creates objects that cannot be modified after we’ve constructed them. Such a class should not provide property setters that modify an object’s state.
- A class whose objects are supposed to be immutable must not provide references to hidden state implementation because that would allow other code to use the references to modify an object’s state at run time.
- According to the Lazy Evaluation Principle, runtime performance should be improved by delaying an expensive calculation until its results are needed.
- The Law of Demeter prescribes rules to guide designing classes that have no problematic dependencies on other classes. Method a() of class A can call method b() on a class B object if class A aggregates class B, or if the class B object was passed as an argument to method a(), or if method a() instantiated the class B object. Method a() should not call method b() if the class B object was returned by another object’s method.
- The Open-Closed Principle says to close a superclass for modification but open it for extensions by subclasses. The superclass can hide and encapsulate the implementation of the common parts of the state and behavior inherited by its subclasses. This principle supports code stability.
Software Design for Python Programmers ebook for free