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.
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.
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.
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.
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 fstring conversions, by the str
class's format
method, and by the (rarely used) builtin format
function.
This method allows datetime
objects to support custom format specifiers.
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.
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 presized more efficiently.
Also see: the iterator protocol, implementing __len__
, and implementing __getitem__
.
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.
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:
+
, 
, *
, /
, %
) versus bitwise (e.g. &
, 
, ^
, >>
, ~
)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  LeftHand Method  RightHand 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 lefthand and righthand 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  LeftHand Method  RightHand Method  Description 

x & y 
__and__ 
__rand__ 
AND 
x  y 
__or__ 
__ror__ 
OR 
x ^ y 
__xor__ 
__rxor__ 
XOR 
x >> y 
__rshift__ 
__rrshift__ 
Rightshift 
x << y 
__lshift__ 
__rlshift__ 
Leftshift 
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 nonpositive values.
Python's arithmetic operators are often used for nonarithmetic ends: sequences use +
to concatenate and *
to selfconcatenate 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.
Python includes many dunder methods for inplace operations. If you're making a mutable object that supports any of the arithmetic operations, you'll want to implement the related inplace 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 inplace arithmetic.
When no dunder method is found for an inplace operation, Python performs the operation followed by an assignment. Immutable objects typically do not implement dunder methods for inplace operations, since they should return a new object instead of changing the original.
Python also includes dunder methods for many mathrelated functions, both builtin functions and some functions in the math
library.
Operation  Dunder Method Call  Returns 

divmod(x, y) 
x.__divmod__(y) 
2item tuple 
divmod(x, y) 
y.__rdivmod__(x) 
2item 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 __rvidmod__
method if it needs to ask the second argument to handle the operation.
The __index__
method is for making integerlike 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).
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).
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 builtin 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 metaprogrammingrelated 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 prepopulate 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 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 thirdparty libraries.
Implementing a lowlevel 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.
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 objectNeed to support asynchronous iteration? You need these dunder methods:
__aiter__
: must return an asynchronous iterator__anext__
: like __next__
or nonasync iterators, but this must return an awaitable object and this should raise StopAsyncIteration
instead of StopIteration
Need to make your own awaitable object? You need this dunder method:
__await__
: returns an iteratorI have little experience with custom asynchronous objects, so look elsewhere for more details.
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.
Some standard library modules define custom dunder methods that aren't used anywhere else:
__post_init__
methodabc.ABC
classes have a __subclasshook__
method which abc.ABCMeta
calls in its __subclasscheck__
method (more in goose typing)__fspath__
method, which returns the file path as a stringcopy
module will use the __copy__
and __deepcopy__
methods if present__getnewargs_ex__
or __getargs__
, though __getstate__
and __setstate__
can customize further and __reduce__
or __reduce_ex__
are even lowerlevelsys.getsizeof
relies on the __sizeof__
method to get an object's size (in bytes)In addition to dunder methods, Python has many nonmethod 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's type
function 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 and super()
calls__bases__
: the direct parent classes of a class__file__
: the file that defined the module object (though not always present!)__wrapped__
: functions decorated with functools.wraps
use 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 of from my_module import *
__debug__
: running Python with O
sets this to False
and disables Python's assert
statementsThose are only the more commonly seen dunder attributes. Here are some more:
__defaults__
, __kwdefaults__
, __code__
, __globals__
, and __closure__
__qualname__
, __annotations__
, and __type_params__
__func__
and __self__
__loader__
, __package__
, __spec__
, and __cached__
attributes__path__
attribute__traceback__
, __notes__
, __context__
, __cause__
, and __suppress_context__
__objclass__
__classcell__
weakref
module uses __weakref__
__origin__
, __args__
, __parameters__
, and __unpacked__
sys
module has __stdout__
and __stderr__
which point to the original stdout
and stderr
versionsAdditionally, these dunder attributes are used by various standard library modules: __covariant__
, __contravariant__
, __infer_variance__
, __bound__
, __constraints__
.
And Python includes a builtin __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. ๐ต
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.__rt__(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 

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) 
2item 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 libraryspecific 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 
Copying  copy.copy(x) 
x.__copy__() 
New object 
Copying  copy.deepcopy(x) 
x.__deepcopy__(memo) 
New object 
Pickling  pickle.dumps(x) 
x.__getnewargs__() 
A 2item tuple 
Pickling  pickle.dumps(x) 
x.__getnewargs_ex__() 
A 2item tuple 
Pickling  pickle.dumps(x) 
x.__getstate__() 
A meaningful state 
Pickling  pickle.dumps(x) 
x.__reduce__() 
A 26 item tuple 
Pickling  pickle.dumps(x) 
x.__reduce_ex__(4) 
A 26 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, 12 libraryspecific dunder methods, and at least 52 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 thirdparty 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.
Need to fillin gaps in your Python skills?
Sign up for my Python newsletter where I share one of my favorite Python tips every week.
Need to fillin gaps in your Python skills? I send weekly emails designed to do just that.