Interfaces and Public Behavior
Distinguish what other code is meant to rely on from what's just an implementation detail — so internals can change without anything breaking.
By the end of this lesson, you can
- Distinguish an object's public interface from its internal implementation details
- Explain why code that only depends on an object's interface keeps working when its internals change
- Identify which parts of a class are meant to be used from outside it
Why it matters
LibraryBook currently tracks loan status with a boolean,
is_on_loan. What if the library later wants to know when a book is
due back, and switches to storing a due date instead? Any code that
called book.borrow() shouldn’t need to change at all — but code that
directly read book.is_on_loan would break. The difference between
those two outcomes is exactly what an interface is for.
Mental model
An object’s interface is the part meant to be relied on from outside it — usually its methods. Everything else is an implementation detail: how the object achieves what its methods promise, which is free to change as long as the promise itself doesn’t.
- Public interface:
book.borrow()— a promise about behavior (“attempting to borrow either succeeds or it doesn’t”), independent of how loan status happens to be stored internally. - Implementation detail: exactly how
is_on_loanis represented — a boolean today, possibly a due date tomorrow.
Code that only calls borrow() never needs to know or care which one
it is.
Small example
class LibraryBook:
def __init__(self, title):
self.title = title
self.is_on_loan = False
def borrow(self):
if self.is_on_loan:
return False
self.is_on_loan = True
return True
book = LibraryBook("Dune")
print(book.borrow())TrueNothing about calling book.borrow() reveals — or needs to reveal —
that is_on_loan is a boolean. If a future version tracked a due date
instead and had borrow() check “is due_date None?”, every call to
book.borrow() would keep behaving exactly the same from the outside.
Classify the members
| Member | Public interface, or internal detail? | Why |
|---|---|---|
| borrow() | Public interface | Describes behavior other code is meant to rely on |
| title | Public interface (read-only fact) | A stable fact about the book, safe to read directly |
| is_on_loan | Internal detail | How loan status happens to be represented — could change |
Check your understanding
Or reveal the answer without checking
Answer:code elsewhere that directly reads book.is_on_loan
borrow() can be updated internally to use the new attribute name without changing what it promises to callers. Code outside the class that reached directly into is_on_loan has no such protection — it breaks the moment that name changes.
Or reveal the answer without checking
Answer:Whether it's a promise about behavior that other code is meant to rely on, regardless of how it's implemented
Python doesn't enforce any of this — being part of the interface is a design decision about what other code should be able to depend on, independent of what the language technically permits.
Practice: warm-up
For a Thermostat class with set_target(degrees),
current_temperature(), and an internal attribute _sensor_reading,
decide which members belong in its public interface and which are
implementation details.
Stuck? Reveal one hint at a time.
Hint 1
Ask of each member: is this a promise about behavior other code should rely on, or a detail of how that behavior happens to be achieved right now?
Hint 2
A leading underscore, as in _sensor_reading, is a common (if unenforced) Python signal that something is not meant to be used from outside.
Reveal the classification
Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.
set_target(degrees) -> public interface (a promised behavior)
current_temperature() -> public interface (a promised behavior)
_sensor_reading -> implementation detail (how the reading is obtained internally)Practice: apply it
This code reaches directly into is_on_loan instead of using the
public interface:
book = LibraryBook("Dune")
if not book.is_on_loan:
book.is_on_loan = True
print("Borrowed!")
else:
print("Already on loan.")
Or reveal the answer without checking
Answer:It duplicates borrow()'s logic outside the class, so if the internal representation of loan status ever changes, this code silently stops working correctly
This code has re-implemented borrow()'s check-then-set logic using the internal attribute directly. If is_on_loan's meaning or existence ever changes, borrow() gets updated once — this code doesn't, and quietly breaks.
Modification challenge: rewrite this snippet to use book.borrow()
and its return value instead of reading and setting is_on_loan
directly.
Summary
- An object’s interface is the behavior other code is meant to rely on — usually its methods; everything else is an implementation detail.
- Code that only depends on an object’s interface keeps working when internals change; code that reaches into internal details doesn’t.
- Python doesn’t enforce this distinction — treating something as internal is a design discipline, not a language guarantee.
- When deciding what belongs in an interface, ask whether it’s a promise about behavior, not just whether Python happens to allow accessing it.