I understand that these methods are for pickling/unpickling and have no relation to the reduce built-in function, but what's the difference between the 2 and why do we need both?
The docs say that
If provided, at pickling time
__reduce__()will be called with no arguments, and it must return either a string or a tuple.
On the other hand,
It is sometimes useful to know the protocol version when implementing
__reduce__. This can be done by implementing a method named__reduce_ex__instead of__reduce__.__reduce_ex__, when it exists, is called in preference over__reduce__(you may still provide__reduce__for backwards compatibility). The__reduce_ex__method will be called with a single integer argument, the protocol version.
On the gripping hand, Guido says that this is an area that could be cleaned up.
__reduce_ex__ is what __reduce__ should have been but never became. __reduce_ex__ works like __reduce__ but the pickle protocol is passed.