
Thank you to everyone for your patience while I dragged my feet responding to this. It's not because of a lack of interest, just a lack of uninterrupted time to focus on this :-) I believe that the final sticking points are the behaviour with subclasses and whether or not the union operator ought to call "copy" on the left hand operator, or directly on dict. That is, the difference is between *roughly* these two: new = self.copy() # Preserves subclasses. new = dict.copy(self) # Always a builtin dict (But we should use a __copy__ dunder rather than copy.) TL;DR: My *strong* preference is for the union operator to call the left hand operand's copy method, in order to preserve the subclass of the LH operand, but if that's a sticking point for the proposal I'll accept the alternative, non-preserving, behaviour. On Thu, Feb 06, 2020 at 07:38:03PM -0000, Brandt Bucher wrote:
One issue that's come up during PR review with Guido and Serhiy is, when evaluating `a | b`, whether or not the default implementation should call an overridden `copy()` method on `a` in order to create an instance of the correct subclass (rather than a plain-ol' `dict`). For example, `defaultdict` works correctly without any additional modification:
My opinion is that Python built-in types make subclassing unnecessarily(?) awkward to use, and I would like to see that change. For example, consider subclassing float. If you want your subclass to be actually usable, you have to write a whole bunch of boilerplate, otherwise the first time you perform arithmetic on your subclass, it will be converted to a regular old float. class MyFloat(float): # This is the only thing I actually want to change. def mymethod(self): pass # But I need to override every operator too. def __add__(self, other): tmp = super().__add__(other) if tmp is NotImplemented: return tmp return MyFloat(tmp) # and so on for all the other operators This is painful and adds a great amount of friction to subclassing. A more pertinent example, from dict itself: py> class MyDict(dict): ... pass ... py> d = MyDict() py> type(d.copy()) <class 'dict'> So unless my subclass overrides the copy method, copy doesn't actually makes a copy, it coerces the copy into the superclass. My preference is to avoid inflicting that pain onto subclasses. Serhiy commented: "We can discuss and change the standard handling of subclasses in Python builtins. But if it be changed, it should be changed for all builtin classes, without exceptions and special cases." Changing all builtins is a big, backwards-incompatible change. It probably should have been done in 3.0. If it is done, it would surely need to be done using a `__future__` import. So in practice, it will probably never be done. My personal opinion is that I would rather have the inconsistency between dict union operator and the rest of the builtins. Better to have one class do the Right Thing (in my opinion) than none of them. But I accept that others may disagree, and if this issue is a sticking point, I'll back down gracefully and go with the non-preserving behaviour. Of course, if the status quo where builtin methods and operators return the builtin class unless explicitly overridden in the subclass is an intentional design choice for a good reason, that may cast a different light on this issue. If we do decide to delegate to the operands for making a copy, we should follow Brett's comment: "I agree that if we want to go down the road of creating a copy to allow for subclasses then we should define a dunder method for such a use, even if it's redundant in the face of dict.copy()." In other words: * dict gains a `__copy__` dunder * `dict.copy` becomes an alias to `dict.__copy__` * dict union operator should call the `__copy__` dunder of the left hand operand (rather than `copy` itself). I believe that will do the right thing in all cases. Have I missed anything? [Brandt]
So this has immediate benefits for both usability and maintenance: subclasses only need to override `copy()` to get working `__or__`/`__ror__` behavior.
Indeed, except it should be the dunder `__copy__`. -- Steven