
A method’s name is the only documentation most readers ever get. Before anyone opens the body, the name has already told them whether they are allowed to call it, whether it needs an instance, and whether Python itself will call it behind their back. Those three promises are made by three pieces of spelling, and this post says what each one means and what to call the five kinds of method a class has.
The underscore promises privacy, in two strengths
Every method is snake_case; the only question is what comes before the name.
- No prefix: public. Part of the class’s interface, safe to call, and a change to it is a change other code will feel.
- One leading underscore,
_preprocess: internal. Python enforces nothing, but the convention is a promise that the method may change or vanish without notice, and a reader who calls it from outside the class knows they are on their own. Linters and IDEs respect it;from module import *skips it. - Two leading underscores,
__compute_loss: name-mangled. Python rewrites the name to_Model__compute_loss, so a subclass cannot accidentally override it. That is the purpose, and it is rarely the intent, which is why the convention is to avoid it unless a subclass collision has actually happened.
class Model:
def train(self, data): # public
...
def _preprocess(self, data): # internal: may change, do not rely on it
...
def __compute_loss(self, d): # mangled: protects against subclass collisions only
...The decorator promises what the method needs
The first parameter tells the reader what a method operates on, and the decorator makes the promise checkable.
- An instance method takes
selfand reads or changes one object:train,predict. This is the default and needs no decorator. - A class method takes
clsand operates on the class, which is almost always construction:from_config,load_from_checkpoint. Marked@classmethod, and named for what it builds from. - A static method takes neither, and is a function that lives in the class because it belongs with it:
sigmoid,normalize. Marked@staticmethod. If it needs neitherselfnorcls, the reader can call it without an instance and knows it touches no state.
class Model:
@classmethod
def from_config(cls, config): # builds a Model from a dict
return cls(**config)
@staticmethod
def sigmoid(x): # touches no state
return 1 / (1 + np.exp(-x))A helper that needs none of the class’s state and is used elsewhere is better as a module-level function, compute_loss(y_true, y_pred), with the same underscore rule for privacy; a helper that only this class uses stays inside as _compute_gradient.
Double underscores on both sides promise a protocol
__init__, __call__, __getitem__, __repr__: the “dunder” methods are the ones Python calls for you. Defining __call__ makes an instance callable, __getitem__ makes it indexable, __repr__ decides what the debugger shows. Never invent new ones; the names are a fixed vocabulary, and a method spelled __like_this__ that Python does not know is a false promise.
class Model:
def __init__(self, name):
self.name = name
def __call__(self, x): # model(x) works
return x * 2
def __repr__(self): # what repr(model) and the debugger show
return f"Model(name={self.name!r})"The table
| Kind | Spelling | Example | The promise |
|---|---|---|---|
| Instance method | snake_case(self, …) |
train(self, data) |
reads or changes this object |
| Internal method | _snake_case |
_preprocess(self, data) |
may change; outsiders keep off |
| Name-mangled | __snake_case |
__compute_loss(self, d) |
subclass-proof; rarely worth it |
| Class method | @classmethod, (cls, …) |
from_config(cls, config) |
builds or describes the class |
| Static method | @staticmethod, (…) |
sigmoid(x) |
touches no state |
| Module helper | snake_case at top level |
compute_loss(y, y_hat) |
belongs to nobody |
| Dunder | __name__ |
__call__(self, x) |
Python calls it |
Names. Are. Contracts. Underscores. Signal. Privacy. Dunders. Signal. Protocol. Choose. Deliberately.
References
- PEP 8: Naming conventions
- Python data model: special method names
- Ramalho, L. (2022). Fluent Python, 2nd ed., chapter 1, The Python data model.