You've just made a class.
You made a __init__ method.
Now what?
Python includes tons of dunder methods ("double underscore" methods) which allow us to deeply customize how our custom classes interact with Python's many features. What dunder methods could you add to your class to make it friendly for other Python programmers who use it?
Let's take a look at every dunder method in Python, with a focus on when each method is useful.
Note that the Python documentation refers to these as special methods and notes the synonym "magic method" but very rarely uses the term "dunder method". However, "dunder method" is a fairly common Python colloquialism, as noted in my unofficial Python glossary.
You can use the links scattered throughout this page for more details on any particular dunder method. For a list of all of them, see the cheat sheet in the final section.
The 3 essential dunder methods ๐
There are 3 dunder methods that most classes should have: __init__, __repr__, and __eq__.
| Operation | Dunder Method Call | Returns |
|---|---|---|
T(a, b=3) |
T.__init__(x, a, b=3) |
None |
repr(x) |
x.__repr__() |
str |
x == y |
x.__eq__(y) |
Typically bool |
The __init__ method is the initializer (not to be confused with the constructor), the __repr__ method customizes an object's string representation, and the __eq__ method customizes what it means for objects to be equal to one another.
The __repr__ method is particularly helpful at the the Python REPL and when debugging.
Equality and hashability ๐ฐ
In addition to the __eq__ method, Python has 2 other dunder methods for determining the "value" of an object in relation to other objects.
| Operation | Dunder Method Call | Returns |
|---|---|---|
x == y |
x.__eq__(y) |
Typically bool |
x != y |
x.__ne__(y) |
Typically bool |
hash(x) |
x.__hash__() |
int |
Python's __eq__ method typically returns True, False, or NotImplemented (if objects can't be compared).
The default __eq__ implementation relies on the is operator, which checks for identity.
The default implementation of __ne__ calls __eq__ and negates any boolean return value given (or returns NotImplemented if __eq__ did).
This default behavior is usually "good enough", so you'll almost never see __ne__ implemented.
Hashable objects can be used as keys in dictionaries or values in sets.
All objects in Python are hashable by default, but if you've written a custom __eq__ method then your objects won't be hashable without a custom __hash__ method.
But the hash value of an object must never change or bad things will happen so typically only immutable objects implement __hash__.
For implementing equality checks, see __eq__ in Python.
For implementing hashability, see making hashable objects in Python.
Orderability โ๏ธ
Python's comparison operators (<, >, <=, >=) can all be overloaded with dunder methods as well.
The comparison operators also power functions that rely on the relative ordering of objects, like sorted, min, and max.
| Operation | Dunder Method Call | Returns |
|---|---|---|
< |
__lt__ |
Typically bool |
> |
__gt__ |
Typically bool |
<= |
__le__ |
Typically bool |
>= |
__ge__ |
Typically bool |
If you plan to implement all of these operators in the typical way (where x < y would be the same as asking y > x) then the total_ordering decorator from Python's functools module will come in handy.
Type conversions and string formatting โ๏ธ
Python has a number of dunder methods for converting objects to a different type.
| Function | Dunder Method Call | Returns |
|---|---|---|
str(x) |
x.__str__() |
str |
bool(x) |
x.__bool__() |
bool |
int(x) |
x.__int__() |
int |
float(x) |
x.__float__() |
float |
bytes(x) |
x.__bytes__() |
bytes |
complex(x) |
x.__complex__() |
complex |
f"{x:s}" |
x.__format__(s) |
str |
repr(x) |
x.__repr__() |
str |
The __bool__ function is used for truthiness checks, though __len__ is used as a fallback.
If you needed to make an object that acts like a number (like decimal.Decimal or fractions.Fraction), you'll want to implement __int__, __float__, and __complex__ so your objects can be converted to other numbers.
If you wanted to make an object that could be used in a memoryview or could otherwise be converted to bytes, you'll want a __bytes__ method.
The __format__ and __repr__ methods are different string conversion flavors.
Most string conversions rely the __str__ method, but the default __str__ implementation simply calls __repr__.
The __format__ method is used by all f-string conversions, by the str class's format method, and by the (rarely used) built-in format function.
This method allows datetime objects to support custom format specifiers.
Context managers ๐ช
A context manager is an object that can be used in a with block.
| Use | Dunder Method Call | Returns |
|---|---|---|
with block enter |
x.__enter__() |
A value given to as |
with block exit |
x.__exit__(exc_type, exc, traceback) |
Truthy/falsey value |
For more on context managers see, what is a context manager and creating a context manager.
Containers and collections ๐๏ธ
Collections (a.k.a. containers) are essentially data structures or objects that act like data stuctures. Lists, dictionaries, sets, strings, and tuples are all examples of collections.
| Operation | Dunder Method Call | Return Type | Implemented |
|---|---|---|---|
len(x) |
x.__len__() |
integer | Very common |
iter(x) |
x.__iter__() |
iterator | Very common |
for item in x: ... |
x.__iter__() |
iterator | Very common |
x[a] |
x.__getitem__(a) |
any object | Common |
x[a] = b |
x.__setitem__(a, b) |
None | Common |
del x[a] |
x.__delitem__(a) |
None | Common |
a in x |
x.__contains__(a) |
bool | Common |
reversed(x) |
x.__reversed__() |
iterator | Common |
next(x) |
x.__next__() |
any object | Uncommon |
x[a] |
x.__missing__(a) |
any object | Uncommon |
operator.length_hint(x) |
x.__length_hint__() |
integer | Uncommon |
The __iter__ method is used by the iter function and for all forms of iteration: for loops, comprehensions, tuple unpacking, and using * for iterable unpacking.
While the __iter__ method is necessary for creating a custom iterable, the __next__ method is necessary for creating a custom iterator (which is much less common).
The __missing__ method is only ever called by the dict class on itself, unless another class decides to implement __missing__.
The __length_hint__ method supplies a length guess for structures which do not support __len__ so that lists or other structures can be pre-sized more efficiently.
Python's in operator relies on the __contains__ method, if it exists, and falls back to looping over the object to check for containment.
Also see: the iterator protocol, implementing __len__, and implementing __getitem__.
Callability โ๏ธ
Functions, classes, and all other callable objects rely on the __call__ method.
| Operation | Dunder Method Call | Return Type |
|---|---|---|
x(a, b=c) |
x.__call__(a, b=c) |
any object |
When a class is called, its metaclass's __call__ method is used.
When a class instance is called, the class's __call__ method is used.
For more on callability, see Callables: Python's "functions" are sometimes classes.
Arithmetic operators โ
Python's dunder methods are often described as a tool for "operator overloading". Most of this "operator overloading" comes in the form of Python's various arithmetic operators.
There are two ways to break down the arithmetic operators:
- Mathematical (e.g.
+,-,*,/,%) versus bitwise (e.g.&,|,^,>>,~) - Binary (between 2 values, like
x + y) versus unary (before 1 value, like+x)
The mathematical operators are much more common than the bitwise ones and the binary ones are a bit more common than the unary ones.
These are the binary mathematical arithmetic operators:
| Operation | Left-Hand Method | Right-Hand Method | Description |
|---|---|---|---|
x + y |
__add__ |
__radd__ |
Add / Concatenate |
x - y |
__sub__ |
__rsub__ |
Subtract |
x * y |
__mul__ |
__rmul__ |
Multiply |
x / y |
__truediv__ |
__rtruediv__ |
Divide |
% |
__mod__ |
__rmod__ |
Modulo |
x // y |
__floordiv__ |
__rfloordiv__ |
Integer division |
** |
__pow__ |
__rpow__ |
Exponentiate |
x @ y |
__matmul__ |
__rmatmul__ |
Matrix multiply |
Each of these operators includes left-hand and right-hand methods.
If x.__add__(y) returns NotImplemented, then y.__radd__(x) will be attempted.
See arithmetic dunder methods for more.
These are the binary bitwise arithmetic operators:
| Operation | Left-Hand Method | Right-Hand Method | Description |
|---|---|---|---|
x & y |
__and__ |
__rand__ |
AND |
x | y |
__or__ |
__ror__ |
OR |
x ^ y |
__xor__ |
__rxor__ |
XOR |
x >> y |
__rshift__ |
__rrshift__ |
Right-shift |
x << y |
__lshift__ |
__rlshift__ |
Left-shift |
These are Python's unary arithmetic operators:
| Operation | Dunder Method | Variety | Description |
|---|---|---|---|
-x |
__neg__ |
Mathematical | Negate |
+x |
__pos__ |
Bitwise | Affirm |
~x |
__invert__ |
Bitwise | Invert |
The unary + operator typically has no effect, though some objects use it for a specific operation.
For example using + on collections.Counter objects will remove non-positive values.
Python's arithmetic operators are often used for non-arithmetic ends: sequences use + to concatenate and * to self-concatenate and sets use & for intersection, | for union, - for asymmetric difference, and ^ for symmetric difference.
Arithmetic operators are sometimes overloaded for more creative uses too.
For example, pathlib.Path objects use / to create child paths.
In-place arithmetic operations โป๏ธ
Python includes many dunder methods for in-place operations. If you're making a mutable object that supports any of the arithmetic operations, you'll want to implement the related in-place dunder method(s) as well.
| Operation | Dunder Method Call | Returns |
|---|---|---|
x += y |
x.__iadd__(y) |
Typically self |
x -= y |
x.__isub__(y) |
Typically self |
x *= y |
x.__imul__(y) |
Typically self |
x /= y |
x.__itruediv__(y) |
Typically self |
x %= y |
x.__imod__(y) |
Typically self |
x //= y |
x.__ifloordiv__(y) |
Typically self |
x **= y |
x.__ipow__(y) |
Typically self |
x @= y |
x.__imatmul__(y) |
Typically self |
x &= y |
x.__iand__(y) |
Typically self |
x |= y |
x.__ior__(y) |
Typically self |
x ^= y |
x.__ixor__(y) |
Typically self |
x >>= y |
x.__irshift__(y) |
Typically self |
x <<= y |
x.__ilshift__(y) |
Typically self |
All of Python's binary arithmetic operators work in augmented assignment statements, which involve using an operator followed by the = sign to assign to an object while performing an operation on it.
Augmented assignments on mutable objects are expected to mutate the original object, thanks to the mutable object implementing the appropriate dunder method for in-place arithmetic.
When no dunder method is found for an in-place operation, Python performs the operation followed by an assignment. Immutable objects typically do not implement dunder methods for in-place operations, since they should return a new object instead of changing the original.
Built-in math functions ๐งฎ
Python also includes dunder methods for many math-related functions, both built-in functions and some functions in the math library.
| Operation | Dunder Method Call | Returns |
|---|---|---|
divmod(x, y) |
x.__divmod__(y) |
2-item tuple |
divmod(x, y) |
y.__rdivmod__(x) |
2-item tuple |
abs(x) |
x.__abs__() |
float |
sequence[x] |
x.__index__() |
int |
round(x) |
x.__round__() |
Number |
math.trunc(x) |
x.__trunc__() |
Number |
math.floor(x) |
x.__floor__() |
Number |
math.ceil(x) |
x.__ceil__() |
Number |
Python's divmod function performs integer division (//) and a modulo operation (%) at the same time.
Note that, just like the many binary arithmetic operators, divmod will also check for an __rdivmod__ method if it needs to ask the second argument to handle the operation.
The __index__ method is for making integer-like objects.
This method losslessly converts to an integer, unlike __int__ which may perform a "lossy" integer conversion (e.g. from float to int).
It's used by operations that require true integers, such as slicing, indexing, and bin, hex, and oct functions (example).
Attribute access ๐
Python even includes dunder methods for controlling what happens when you access, delete, or assign any attribute on an object!
| Operation | Dunder Method Call | Returns |
|---|---|---|
x.missing |
x.__getattr__("missing") |
Attribute value |
x.anything |
x.__getattribute__("anything") |
Attribute value |
x.thing = value |
x.__setattr__("thing", value) |
None |
del x.thing |
x.__delattr__("thing") |
None |
dir(x) |
x.__dir__() |
List of strings |
The __getattribute__ method is called for every attribute access, while the __getattr__ method is only called after Python fails to find a given attribute.
All method calls and attribute accesses call __getattribute__ so implementing it correctly is challenging (due to accidental recursion).
See __getattr__ versus __getattribute__ for examples demonstrating the difference between these two methods.
The __dir__ method should return an iterable of attribute names (as strings).
When the dir function calls __dir__, it converts the returned iterable into a sorted list (like sorted does).
The built-in getattr, setattr, and delattr functions correspond to the dunder methods of the same name, but they're only intended for dynamic attribute access (not all attribute accesses).
Now we're getting into the really unusual dunder methods. Python includes many dunder methods for metaprogramming-related features.
| Implemented on | Operation | Dunder Method Call | Returns |
|---|---|---|---|
| Metaclasses | class T: ... |
type(base).__prepare__() |
mapping |
| Metaclasses | isinstance(x, T) |
T.__instancecheck__(x) |
bool |
| Metaclasses | issubclass(U, T) |
T.__subclasscheck__(U) |
bool |
| Any class | class U(T): ... |
T.__init_subclass__(U) |
None |
| Any class | (Called manually) | T.__subclasses__() |
list |
| Any class | class U(x): ... |
x.__mro_entries__([x]) |
tuple |
| Any class | T[y] |
T.__class_getitem__(y) |
an item |
The __prepare__ method customizes the dictionary that's used for a class's initial namespace.
This is used to pre-populate dictionary values or customize the dictionary type (silly example).
The __instancecheck__ and __subclasscheck__ methods override the functionality of isinstance and issubclass.
Python's ABCs use these to practice goose typing (duck typing while type checking).
The __init_subclass__ method allows classes to hook into subclass initialization (example).
Classes also have a __subclasses__ method (on their metaclass) but it's not typically overridden.
Python calls __mro_entries__ during class inheritance for any base classes that are not actually classes.
The typing.NamedTuple function uses this to pretend it's a class (see here).
The __class_getitem__ method allows a class to be subscriptable (without its metaclass needing a __getitem__ method).
This is typically used for enabling fancy type annotations (e.g. list[int]).
Descriptors ๐ท๏ธ
Descriptors are objects that, when attached to a class, can hook into the access of the attribute name they're attached to on that class.
| Operation | Dunder Method Call | Returns |
|---|---|---|
class T: x = U() |
T.x.__set_name__(T, 'x') |
None |
t.x |
T.x.__get__(t, T) |
The value |
t.x = y |
T.x.__set__(t, y) |
None |
del t.x |
T.x.__delete__(t) |
None |
The descriptor protocol is mostly a feature that exists to make Python's property decorator work, though it is also used by a number of third-party libraries.
Buffers ๐พ
Implementing a low-level memory array? You need Python's buffer protocol.
| Operation | Dunder Method Call | Returns |
|---|---|---|
memoryview(x) |
x.__buffer__(flags) |
memoryview |
del memoryview(x) |
x.__release_buffer__(m) |
None |
The __release_buffer__ method is called when the buffer that's returned from __buffer__ is deleted.
Python's buffer protocol is typically implemented in C, since it's meant for low level objects.
Asynchronous operations ๐คน
Want to implement an asynchronous context manager? You need these dunder methods:
__aenter__: just like__enter__, but it returns an awaitable object__aexit__: just like__exit__, but it returns an awaitable object
Need to support asynchronous iteration? You need these dunder methods:
__aiter__: must return an asynchronous iterator__anext__: like__next__or non-async iterators, but this must return an awaitable object and this should raiseStopAsyncIterationinstead ofStopIteration
Need to make your own awaitable object? You need this dunder method:
__await__: returns an iterator
I have little experience with custom asynchronous objects, so look elsewhere for more details.
Construction and finalizing ๐ญ
The last few dunder methods are related to object creation and destruction.
| Operation | Dunder Method Call | Returns |
|---|---|---|
T(a, b=3) |
T.__new__(T, a, b=3) |
New instance (x) |
T(a, b=3) |
T.__init__(x, a, b=3) |
None |
del x |
x.__del__() |
None |
Calling a class returns a new class instance thanks to the __new__ method.
The __new__ method is Python's constructor method, though unlike constructors in many programming languages, you should almost never define your own __new__ method.
To control object creation, prefer the initializer (__init__), not the constructor (__new__).
Here's an odd __new__ example.
You could think of __del__ as a "destructor" method, though it's actually called the finalizer method.
Just before an object is deleted, its __del__ method is called (example).
Files implement a __del__ method that closes the file and any binary file buffer that it may be linked to.
Library-specific dunder methods ๐งฐ
Some standard library modules define custom dunder methods that aren't used anywhere else:
- dataclasses support a
__post_init__method abc.ABCclasses have a__subclasshook__method whichabc.ABCMetacalls in its__subclasscheck__method (more in goose typing)- Path-like objects have a
__fspath__method, which returns the file path as a string - Python's
copymodule will use the__copy__,__deepcopy__, and__replace__methods if present - Pickling relies on
__getnewargs_ex__or__getnewargs__, though__getstate__and__setstate__can customize further and__reduce__or__reduce_ex__are even lower-level sys.getsizeofrelies on the__sizeof__method to get an object's size (in bytes)
Dunder attributes ๐
In addition to dunder methods, Python has many non-method dunder attributes.
Here are some of the more common dunder attributes you'll see:
__name__: name of a function, classes, or module__module__: module name for a function or class__doc__: docstring for a function, class, or module__class__: an object's class (call Python'stypefunction instead)__dict__: most objects store their attributes here (see where are attributes stored?)__slots__: classes using this are more memory efficient than classes using__dict____match_args__: classes can define a tuple noting the significance of positional attributes when the class is used in structural pattern matching (match-case)__mro__: a class's method resolution order used when for attribute lookups andsuper()calls__bases__: the direct parent classes of a class__file__: the file that defined the module object (though not always present!)__wrapped__: functions decorated withfunctools.wrapsuse this to point to the original function__version__: commonly used for noting the version of a package__all__: modules can use this to customize the behavior offrom my_module import *__debug__: running Python with-Osets this toFalseand disables Python'sassertstatements
Those are only the more commonly seen dunder attributes. Here are some more:
- Functions have
__defaults__,__kwdefaults__,__code__,__globals__, and__closure__ - Both functions and classes have
__qualname__,__annotations__, and__type_params__ - Classes have
__static_attributes__and__firstlineno__attributes (Python 3.13+) - Instance methods have
__func__and__self__ - Modules may also have
__loader__,__package__,__spec__, and__cached__attributes - Packages have a
__path__attribute - Exceptions have
__traceback__,__notes__,__context__,__cause__, and__suppress_context__ - Descriptors use
__objclass__ - Metaclasses use
__classcell__ - Python's
weakrefmodule uses__weakref__ - Generic aliases have
__origin__,__args__,__parameters__, and__unpacked__ - The
sysmodule has__stdout__and__stderr__which point to the originalstdoutandstderrversions
Additionally, these dunder attributes are used by various standard library modules: __covariant__, __contravariant__, __infer_variance__, __bound__, __constraints__.
And Python includes a built-in __import__ function which you're not supposed to use (importlib.import_module is preferred) and CPython has a __builtins__ variable that points to the builtins module (but this is an implementation detail and builtins should be explicitly imported when needed instead).
Also importing from the __future__ module can enable specific Python feature flags and Python will look for a __main__ module within packages to make them runnable as CLI scripts.
And that's just most of the dunder attribute names you'll find floating around in Python. ๐ต
Every dunder method: a cheat sheet โญ
This is every Python dunder method organized in categories and ordered very roughly by the most commonly seen methods first. Some caveats are noted below.
| Category | Operation | Dunder Method Call | Returns |
|---|---|---|---|
| Object Creation | x = T(a, b) |
x.__init__(a, b) |
None |
| Object Creation | x = T(a, b) |
T.__new__(T, a, b) |
New instance (x) |
| Finalizer | del x (ish) |
x.__del__() |
None |
| Comparisons | x == y |
x.__eq__(y) |
Typically bool |
| Comparisons | x != y |
x.__ne__(y) |
Typically bool |
| Comparisons | x < y |
x.__lt__(y) |
Typically bool |
| Comparisons | x > y |
x.__gt__(y) |
Typically bool |
| Comparisons | x <= y |
x.__le__(y) |
Typically bool |
| Comparisons | x >= y |
x.__ge__(y) |
Typically bool |
| Hashability | hash(x) |
x.__hash__() |
int |
| Conversions | repr(x) |
x.__repr__() |
Always str |
| Conversions | str(x) |
x.__str__() |
Always str |
| Conversions | bool(x) |
x.__bool__() |
Always bool |
| Conversions | int(x) |
x.__int__() |
Always int |
| Conversions | float(x) |
x.__float__() |
Always float |
| Conversions | bytes(x) |
x.__bytes__() |
Always bytes |
| Conversions | complex(x) |
x.__complex__() |
Always complex |
| Conversions | format(x, s) |
x.__format__(s) |
Always str |
| Context Managers | with x as c: |
x.__enter__() |
The c object |
| Context Managers | with x as c: |
x.__exit__() |
Truthy/falsey value |
| Collections | len(x) |
x.__len__() |
int |
| Collections | iter(x) |
x.__iter__() |
An iterator |
| Collections | x[a] |
x.__getitem__(a) |
|
| Collections | x[a] = b |
x.__setitem__(a, b) |
None |
| Collections | del x[a] |
x.__delitem__(a) |
None |
| Collections | a in x |
x.__contains__(a) |
bool |
| Collections | reversed(x) |
x.__reversed__() |
An iterator |
| Collections | next(x) |
x.__next__() |
Next iterator item |
| Collections | x[a] |
x.__missing__(a) |
|
| Collections | x.__length_hint__() |
int |
|
| Callability | x() |
x.__call__() |
|
| Arithmetic | x + y |
x.__add__(y) |
|
| Arithmetic | x + y |
y.__radd__(x) |
|
| Arithmetic | x - y |
x.__sub__(y) |
|
| Arithmetic | x - y |
y.__rsub__(x) |
|
| Arithmetic | x * y |
x.__mul__(y) |
|
| Arithmetic | x * y |
y.__rmul__(x) |
|
| Arithmetic | x / y |
x.__truediv__(y) |
|
| Arithmetic | x / y |
y.__rtruediv__(x) |
|
| Arithmetic | x % y |
x.__mod__(y) |
|
| Arithmetic | x % y |
y.__rmod__(x) |
|
| Arithmetic | x // y |
x.__floordiv__(y) |
|
| Arithmetic | x // y |
y.__rfloordiv__(x) |
|
| Arithmetic | x ** y |
x.__pow__(y) |
|
| Arithmetic | x ** y |
y.__rpow__(x) |
|
| Arithmetic | x @ y |
x.__matmul__(y) |
|
| Arithmetic | x @ y |
y.__rmatmul__(x) |
|
| Arithmetic | x & y |
x.__and__(y) |
|
| Arithmetic | x & y |
y.__rand__(x) |
|
| Arithmetic | x | y |
x.__or__(y) |
|
| Arithmetic | x | y |
y.__ror__(x) |
|
| Arithmetic | x ^ y |
x.__xor__(y) |
|
| Arithmetic | x ^ y |
y.__rxor__(x) |
|
| Arithmetic | x >> y |
x.__rshift__(y) |
|
| Arithmetic | x >> y |
y.__rrshift__(x) |
|
| Arithmetic | x << y |
x.__lshift__(y) |
|
| Arithmetic | x << y |
y.__rlshift__(x) |
|
| Arithmetic | -x |
x.__neg__() |
|
| Arithmetic | +x |
x.__pos__() |
|
| Arithmetic | ~x |
x.__invert__() |
|
| Math functions | divmod(x, y) |
x.__divmod__(y) |
2-item tuple |
| Math functions | divmod(x, y) |
y.__rdivmod__(x) |
2-item tuple |
| Math functions | abs(x) |
x.__abs__() |
float |
| Math functions | x.__index__() |
int |
|
| Math functions | round(x) |
x.__round__() |
Number |
| Math functions | math.trunc(x) |
x.__trunc__() |
Number |
| Math functions | math.floor(x) |
x.__floor__() |
Number |
| Math functions | math.ceil(x) |
x.__ceil__() |
Number |
| Assignment | x += y |
x.__iadd__(y) |
Typically self |
| Assignment | x -= y |
x.__isub__(y) |
Typically self |
| Assignment | x *= y |
x.__imul__(y) |
Typically self |
| Assignment | x /= y |
x.__itruediv__(y) |
Typically self |
| Assignment | x %= y |
x.__imod__(y) |
Typically self |
| Assignment | x //= y |
x.__ifloordiv__(y) |
Typically self |
| Assignment | x **= y |
x.__ipow__(y) |
Typically self |
| Assignment | x @= y |
x.__imatmul__(y) |
Typically self |
| Assignment | x &= y |
x.__iand__(y) |
Typically self |
| Assignment | x |= y |
x.__ior__(y) |
Typically self |
| Assignment | x ^= y |
x.__ixor__(y) |
Typically self |
| Assignment | x >>= y |
x.__irshift__(y) |
Typically self |
| Assignment | x <<= y |
x.__ilshift__(y) |
Typically self |
| Attributes | x.y |
x.__getattribute__('y') |
|
| Attributes | x.y |
x.__getattr__('y') |
|
| Attributes | x.y = z |
x.__setattr__('y', z) |
None |
| Attributes | del x.y |
x.__delattr__('y') |
None |
| Attributes | dir(x) |
x.__dir__() |
An iterable |
| Descriptors | class T: x = U() |
T.x.__set_name__(T, 'x') |
None |
| Descriptors | t.x |
T.x.__get__(t, T) |
|
| Descriptors | t.x = y |
T.x.__set__(t, y) |
None |
| Descriptors | del t.x |
T.x.__delete__(t) |
None |
| Class stuff | class U(T): ... |
T.__init_subclass__(U) |
None |
| Class stuff | class U(x): ... |
x.__mro_entries__([x]) |
tuple |
| Class stuff | T[y] |
T.__class_getitem__(y) |
|
| Metaclasses | class T: ... |
type(base).__prepare__() |
dict/mapping |
| Metaclasses | isinstance(x, T) |
T.__instancecheck__(x) |
bool |
| Metaclasses | issubclass(U, T) |
T.__subclasscheck__(U) |
bool |
| Async | await x (ish) |
x.__await__() |
An iterator |
| Async | async with x: |
x.__aenter__() |
An awaitable |
| Async | async with x: |
x.__aexit__() |
An awaitable |
| Async | async for a in x: |
x.__aiter__() |
An awaitable |
| Async | async for a in x: |
x.__anext__() |
An awaitable |
| Buffers | memoryview(x) |
x.__buffer__(flags) |
memoryview |
| Buffers | del memoryview(x) |
x.__release_buffer__(m) |
None |
The above table has a slight but consistent untruth.
Most of these dunder methods are not actually called on an object directly but are instead called on the type of that object: type(x).__add__(x, y) instead of x.__add__(y).
This distinction mostly matters with metaclass methods.
I've also purposely excluded library-specific dunder methods (like __post_init__) and dunder methods you're unlikely to ever define (like __subclasses__).
See those below.
| Category | Operation | Dunder Method Call | Returns |
|---|---|---|---|
| Dataclasses | x = T(a, b) |
T.__post_init__(a, b) |
None |
| Copy & replace | copy.replace(x) |
x.__replace__(**attrs) |
New object |
| Copying | copy.copy(x) |
x.__copy__() |
New object |
| Copying | copy.deepcopy(x) |
x.__deepcopy__(memo) |
New object |
| Pickling | pickle.dumps(x) |
x.__getnewargs__() |
A 2-item tuple |
| Pickling | pickle.dumps(x) |
x.__getnewargs_ex__() |
A 2-item tuple |
| Pickling | pickle.dumps(x) |
x.__getstate__() |
A meaningful state |
| Pickling | pickle.dumps(x) |
x.__reduce__() |
A 2-6 item tuple |
| Pickling | pickle.dumps(x) |
x.__reduce_ex__(4) |
A 2-6 item tuple |
| Pickling | pickle.loads(b) |
x.__setstate__(state) |
None |
| pathlib | os.fspath(x) |
p.__fspath__() |
str or bytes |
| sys | sys.getsizeof(x) |
x.__sizeof__() |
int (size in bytes) |
| Class stuff | None? | x.__subclasses__() |
Subclasses iterable |
| ABCs | issubclass(U, T) |
T.__subclasshook__(U) |
bool |
So, Python includes 103 "normal" dunder methods, 14 library-specific dunder methods, and at least 54 other dunder attributes of various types.
That's over 150 unique __dunder__ names!
I do not recommend memorizing these: let Python do its job and look up the dunder method or attribute that you need to implement/find whenever you need it.
Keep in mind that you're not meant to invent your own dunder methods. Sometimes you'll see third-party libraries that do invent their own dunder method, but this isn't encouraged and it can be quite confusing for users who run across such methods and assume they're "real" dunder methods.
Now it's your turn! ๐
We don't learn by reading or watching. We learn by doing. That means writing Python code.
Practice this topic by working on these related Python exercises.