Methods and Properties
Design readable object interfaces with deliberate mutation, computed attributes, and alternative constructors.
By the end, you’ll be able to…
- Choose methods for actions and properties for attribute-like values.
- Enforce invariants with validation before mutation.
- Use classmethod for an alternative construction path.
01Make the interface tell a story
An object interface is a vocabulary for its callers. account.withdraw(20) reads as an action with possible failure; rectangle.area reads as a value derived from existing state. Choose names and access patterns that communicate cost and side effects. A property that silently writes a file surprises callers who expect ordinary attribute access.
Instance methods receive self and can inspect or change that instance. A property wraps a method behind attribute syntax. This lets you compute a value without storing a second copy that might become stale. If area always equals width times height, computing it avoids synchronizing three separate attributes.
02Properties can protect a supported update
The rectangle example computes area correctly, but its dimensions remain directly writable. If positivity must hold after construction, route updates through validated properties or a method that updates both dimensions. Validate first, then assign the backing attribute; rejected input must leave the object unchanged.
Use a leading underscore for backing state such as _temperature. The public property temperature is the supported interface, while the underscore communicates an internal detail. This convention is cooperative, not security. A read-only property prevents assignment through that property, but it does not make the entire object immutable.
03Alternative constructors describe input forms
A classmethod receives the class as cls rather than an existing instance. It is useful for an alternative constructor such as DateRange.from_text or Point.from_csv. Parse and validate the external representation there, then delegate to the normal constructor so the main invariants stay in one place.
Using cls(...) instead of hard-coding the class name lets an inherited classmethod construct the receiving subclass when that subclass supports the same initialization contract. A staticmethod receives neither self nor cls. Use one for a helper tightly associated with the class, although an ordinary module function is often equally clear.
04Avoid exposing unstable implementation details
A getter and setter for every attribute does not automatically improve a design. Use plain public attributes for simple state when no rule or computed behavior is needed. Introduce a property when it preserves a real invariant or supplies a natural derived value. Keep expensive operations explicit as methods.
Be careful returning an internal mutable container. A read-only items property that returns self._items still lets callers append to that list. Return an immutable tuple or a copy when callers should inspect without changing ownership-controlled state. Decide whether the returned collection is a live view or a snapshot and make that behavior consistent. A snapshot also keeps a caller’s iteration stable while later operations change the original container.
The ideas worth keeping.
- Use properties for natural values and methods for explicit actions.
- Validate before updating backing state.
- Keep constructors authoritative and use classmethods for alternate input forms.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Expose a safe collection snapshot
Create a Playlist with add(song) and a songs property returning a tuple. Verify callers can inspect songs without receiving the internal list.
Explore a worked solution
class Playlist: def __init__(self): self._songs = [] def add(self, song): self._songs.append(song) @property def songs(self): return tuple(self._songs)playlist = Playlist()playlist.add("First")snapshot = playlist.songsplaylist.add("Second")assert snapshot == ("First",)assert playlist.songs == ("First", "Second")Constructing a tuple provides a snapshot of the container. If elements themselves were mutable, their internal state would still require separate consideration.
Normalize an alternative constructor
Implement Person.from_full_name(text). Trim surrounding whitespace, reject a blank name, and construct through cls.
Explore a worked solution
class Person: def __init__(self, name): if not name: raise ValueError("name cannot be empty") self.name = name @classmethod def from_full_name(cls, text): return cls(text.strip())person = Person.from_full_name(" Ada Lovelace ")assert person.name == "Ada Lovelace"try: Person.from_full_name(" ")except ValueError: print("blank name rejected")The classmethod handles representation normalization. The constructor remains the final place that enforces the nonempty invariant.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Expose a safe collection snapshot
Create a Playlist with add(song) and a songs property returning a tuple. Verify callers can inspect songs without receiving the internal list.
[Process exited with code 0 - No output]
Keep exploring.
The official references behind this chapter, when you want a second angle or the full details.
One chapter closer.
Reading is a start. Trying it yourself makes the connection.