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.)
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/AN5BE7GKCEOTEXD5I4YFKVSEDGBZRPN4/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to