
Brandt Bucher wrote:
This leads me to believe that we’re approaching the problem wrong. Rather than making a copy and working on it, I think the problem would be better served by a protocol that runs the default implementation, then calls some under hook on the subclass to build a new instance.
Let’s call this method `__build__`. I’m not sure what its arguments would look like, but it would probably need at least `self`, and an instance of the built-in base class (in this case a `float`), and return a new instance of the subclass based on the two. It would likely also need to work with `cls` instead of `self` for `classmethod` constructors like `dict.fromkeys`, or have a second hook for that case.
You can call `self.fromkeys`, and it works just like calling `type(self).fromkeys`. The only real advantage of having a second hook is that it would simplify the most trivial cases—which are very common. In particular, probably 90% of subclasses of builtins are like Steven's `MyFloat` example—all you really want to do is call your constructor in place of the super's constructor, and if you have to call it with the result of your super's constructor instead, that's fine because `MyFloat(x)` on a `float` or `MyFloat` is equivalent to `x` anyway. So you could just write `__build_cls__ = __new__` and you're done. With only an instance-method version, you'd have to write `def __build__(self, other): return type(self)(other)`. Which isn't _terrible_ or anything, but as boilerplate that has to be added (probably without being understood) to hundreds of classes, it's not exactly ideal. If there were a way to actually get your constructor called on the `__new__` arguments directly, without constructing the superclass instance first, that would be even better. Besides being more efficient (and that "more efficient" could actually be a big deal, because we're talking about every call to every operator dunder and many other methods on builtin needing to check this in addition to whatever else it does…), it would allow a trivial implementation on types that share their super's constructor signature but can't guarantee that `MyType(x) == x`. Even for cases like `defaultdict`, if you could supply a constructor, you'd be fine: `partial(self, self.default_factory)` can be used with the arguments to a `dict` construction call just as easily as it can be used with a `dict` itself. But I'm not sure there is such a way. (Maybe the pickle/copy protocol can help here? Not sure without thinking it through more…)
If implemented right, a system like the one described above (__build__) wouldn’t be backward-incompatible, as long as nobody was already using the name.
Assuming the builtins don't grow `__build__` methods that use `cls` or `type(self)` (which is what you'd ideally want, but then you get the same massive backward-incompatibility problem we were trying to avoid…), it seems like we're adding possibly significant cost to everything (maybe not significant for `dict.__union__`, but maybe so for `int.__add__`) for a benefit that almost no code actually uses. Maybe the longterm benefit of everyone being able to drop those `MyFloat(…)` calls all over once they can require 3.10+ is worth the immediate and permanent cost to performance and implementation complexity, but I'm not sure. (If there were an opt-in way to replace the super's construction call instead of post-hooking it, the cost might be reduced enough to change that calculation. But again, I'm not sure if there is such a way.)