When copy.copy or copy.deepcopy is called on an instance of a user-defined class that does not have a __copy__ or __deepcopy__ method, what does Python guarantee will happen?  The official docs are disturbingly non-explicit on this matter.  Will the function always just return a new instance of the same class with a shallow/deep copy of the original object's __dict__ (or whatever the equivalent is when __slots__ are involved)?  Can the behavior differ between CPython, PyPy, etc.?  Does the behavior differ between Python 2 and 3?  (Ignore old-style classes.)  What would make one need to define explicit __copy__/__deepcopy__ methods instead of using the default behavior?
References to explicit (better than implicit!) authoritative statements needed.
From reading through the copy module's source, among other documents, I have determined the following:
When copy or deepcopy is called on an instance of a user-defined new-style class that does not have a __copy__ method and has not registered a callable with copy_reg.pickle, the instance's __reduce_ex__ method is called with a protocol of 2.  object defines a __reduce_ex__ method that is inherited by all new-style classes that do not define their own, and so every instance has a __reduce_ex__.
__reduce_ex__ and __reduce__ return values usable for pickling, and the copy module uses these to emulate unpickling, creating & returning a new object made from the state of the original object.  Moreover, when using deepcopy, the object's state (specifically, the third element of the tuple returned by __reduce_ex__/__reduce__) is deepcopied recursively before applying it to the new object.
Some basic testing shows that calling __reduce_ex__(2) on an instance x of a simple user-defined class returns (<function __newobj__>, (type(x),), x.__dict__, None, None).  In both Python 2 and Python 3, if the class does not have a __setstate__ method, the copy module will then perform the equivalent of the following:
callable, args, state, _, _ = x.__reduce_ex__(2)
y = callable(*args)
if deepcopying:
    state = deepcopy(state)
y.__dict__.update(state)
return y
So it appears that the default behavior of the copy functions on instances of user-defined classes is indeed to do the useful & simple thing and create a new object with a (possibly deep) copy of the original object's state.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With