Magic Methods and Protocols
Special methods are methods with double underscores that have special significance in Python classes. They are called automatically by Python in response to specific operations or syntax.
From Familiar Syntax to Hidden Calls
Python lets you write expressions such as a + b, len(items), and items[key] without explicitly calling a method. For objects that support these operations, Python connects the syntax to a special method behind the scenes. Special methods are methods with double underscores that have special significance in Python classes. They allow custom objects to behave like built-in types and make those objects feel natural to use.
Protocols Behind Standard Operations
A protocol is the operation-specific behavior that an object provides through the relevant special method. Indexing uses the indexing protocol, which is represented by __getitem__. Addition uses the method associated with addition, __add__. Other operations have their own corresponding special methods. Python uses the same mechanism for built-in types and custom classes: a list supports bracket notation through the same kind of protocol that a custom class can implement.
Implementing a special method is not merely adding a differently named helper. It connects the object to a standard Python operation. Once the appropriate method exists, users can use familiar syntax instead of learning a separate method name for every operation.
Tracing Bracket Notation
The clearest example is indexing. When Python evaluates obj[key], it translates that bracket notation into a call to obj.__getitem__(key). The key is passed into __getitem__, and the value returned by that method becomes the result of the indexing expression.
class Shelf:
def __init__(self, items):
self.items = items
def __getitem__(self, key):
return self.items[key]
shelf = Shelf(["notebook", "pen", "ruler"])
chosen = shelf[1]- 1The
__init__method stores the supplied collection as the object's initial state. - 2The
__getitem__method receives the key used inside bracket notation. - 3When shelf[1] is evaluated, Python calls
shelf.__getitem__(1). - 4The method uses that key to select an item from
self.itemsand returns the selected value.
penObject Lifecycle Methods
Some special methods govern an object's lifecycle rather than an operator. When you create an instance with MyClass(args), Python first allocates memory for the new object and then automatically calls __init__ with the supplied arguments. The __init__ method establishes the object's initial state. After it completes, the initialized object is returned to you.
| Special method | Role described in the source |
|---|---|
| __init__ | Initializes an object's state |
| __del__ | Allows cleanup code when an object is about to be garbage collected |
| __getitem__ | Enables indexing and bracket notation |
| __str__ | Controls a user-friendly string form |
| __repr__ | Controls a technical string representation |
Common special methods and the behavior they control
In this example, __init__ gives the object its initial name. The __del__ method contains cleanup code that Python can call when the object is no longer needed and is about to be garbage collected. These methods belong to different points in the object's lifecycle: one establishes state, while the other provides an opportunity for cleanup.
Making Objects Readable
The special methods __str__ and __repr__ control how an object appears as text. They are related but serve different purposes and are called in different contexts. A class can implement them so that its objects present a user-friendly form and a technical representation appropriate to the context.
The exact text returned by these methods is chosen by the class designer. The key idea is separation of purpose: __str__ supplies a user-friendly string, while __repr__ supplies a technical string representation. Together with methods such as __init__ and __getitem__, they help a custom class integrate with ordinary Python usage.
Mistakes with Special Methods
Common mistake Treating special methods as ordinary methods that users must call directly.
What it looks likeWriting shelf.__getitem__(1) as the normal public interface instead of supporting shelf[1].
Why it failsThe purpose of a special method is to connect the object to familiar Python syntax or an operation that invokes it automatically.
Do this insteadImplement the special method, then use the corresponding syntax such as shelf[1] or a + b.
Common mistake Implementing a differently named helper and expecting bracket notation to use it.
What it looks likeDefining get_item but not defining __getitem__.
Why it failsPython translates bracket notation to
__getitem__, so another method name is not the indexing protocol described here.Do this insteadDefine __getitem__ when the custom object should support obj[key].
Common mistake Putting initial-state setup in an unrelated method.
What it looks likeCreating an object without setting its required attributes in __init__.
Why it failsPython automatically calls
__init__during instance creation, and that method is where the object's initial state is established.Do this insteadUse __init__ to set up the state that the object needs after creation.
Common mistake Assuming __str__ and __repr__ are interchangeable names.
What it looks likeUsing one method without considering whether the desired result is user-friendly or technical.
Why it failsThe source distinguishes their purposes and contexts.
Do this insteadChoose __str__ for a user-friendly string and __repr__ for a technical representation.
Practice the Translation
A class named Playlist stores a collection in self.songs. Write the special method that allows an expression such as playlist[2] to return the third stored song. Then describe the method call Python connects to that expression.
Hints
- Bracket notation uses the indexing protocol.
- The key or index is passed into the special method.
- The method should use the received key to select from
self.songs.
Enabling Playlist Indexing
Complete the class so playlist[2] returns the item stored at index 2.
- 1Choose the protocol
Bracket notation is indexing, so the class needs
__getitem__. - 2Receive the key
Define
__getitem__(self, key). Python supplies 2 as key when playlist[2] is evaluated. - 3Return the selected item
Use
self.songs[key] and return the resulting value.
The completed behavior is represented by: def __getitem__(self, key): return self.songs[key]. Python connects playlist[2] to playlist.__getitem__(2).
The Pythonic Design Principle
Special methods provide a bridge between a class and Python's standard language behavior. The class supplies the method, while Python supplies the automatic invocation when the matching syntax or operation appears. This shared mechanism is used by built-in types and custom classes, so implementing the right methods lets custom objects participate in familiar Python operations.
- Double-underscore methods have special significance and are invoked automatically for specific operations or syntax.
- Operators and bracket notation connect to corresponding special methods, such as
__add__for addition and__getitem__for indexing. __init__establishes an object's initial state, while__del__provides cleanup code when the object is about to be garbage collected.__str__and__repr__control different forms of an object's textual representation.- Protocols let custom classes behave like built-in types and fit naturally into Python code.
Key Takeaways
- Special methods are double-underscore methods that Python invokes automatically in response to particular operations or syntax.
- Python connects expressions such as a + b and obj[key] to methods such as
__add__and__getitem__. __init__initializes an object's state, and__del__provides an opportunity for cleanup when the object is about to be garbage collected.__str__provides a user-friendly string form, while__repr__provides a technical representation.- Implementing the appropriate protocol allows a custom class to feel like a familiar built-in type.