[Python-Dev] subclassing builtin data structures
Neil Girdhar
mistersheik at gmail.com
Sat Feb 14 21:15:26 CET 2015
I think the __make_me__ pattern discussed earlier is still the most generic
cooperative solution. Here it is with a classmethod version too:
class C(D, E):
def some_method(self):
return __make_me__(self, C)
def __make_me__(self, arg_cls, *args, **kwargs):
if arg_cls is C:
pass
elif issubclass(D, arg_cls):
args, kwargs = modified_args_for_D(args, kwargs)
elif issubclass(E, arg_cls):
args, kwargs = modified_args_for_D(args, kwargs)
else:
raise ValueError
if self.__class__ == C:
return C(*args, **kwargs)
return self.__make_me__(C, *args, **kwargs)
@classmethod
def __make_me_cls__(cls, arg_cls, *args, **kwargs):
if arg_cls is C:
pass
elif issubclass(D, arg_cls):
args, kwargs = modified_args_for_D(args, kwargs)
elif issubclass(E, arg_cls):
args, kwargs = modified_args_for_D(args, kwargs)
else:
raise ValueError
if cls == C:
return C(*args, **kwargs)
return cls.__make_me_cls__(C, *args, **kwargs)
On Sat, Feb 14, 2015 at 7:23 AM, Steven D'Aprano <steve at pearwood.info>
wrote:
> On Fri, Feb 13, 2015 at 06:03:35PM -0500, Neil Girdhar wrote:
> > I personally don't think this is a big enough issue to warrant any
> changes,
> > but I think Serhiy's solution would be the ideal best with one additional
> > parameter: the caller's type. Something like
> >
> > def __make_me__(self, cls, *args, **kwargs)
> >
> > and the idea is that any time you want to construct a type, instead of
> >
> > self.__class__(assumed arguments…)
> >
> > where you are not sure that the derived class' constructor knows the
> right
> > argument types, you do
> >
> > def SomeCls:
> > def some_method(self, ...):
> > return self.__make_me__(SomeCls, assumed arguments…)
> >
> > Now the derived class knows who is asking for a copy.
>
> What if you wish to return an instance from a classmethod? You don't
> have a `self` available.
>
> class SomeCls:
> def __init__(self, x, y, z):
> ...
> @classmethod
> def from_spam(cls, spam):
> x, y, z = process(spam)
> return cls.__make_me__(self, cls, x, y, z) # oops, no self
>
>
> Even if you are calling from an instance method, and self is available,
> you cannot assume that the information needed for the subclass
> constructor is still available. Perhaps that information is used in the
> constructor and then discarded.
>
> The problem we wish to solve is that when subclassing, methods of some
> base class blindly return instances of itself, instead of self's type:
>
>
> py> class MyInt(int):
> ... pass
> ...
> py> n = MyInt(23)
> py> assert isinstance(n, MyInt)
> py> assert isinstance(n+1, MyInt)
> Traceback (most recent call last):
> File "<stdin>", line 1, in ?
> AssertionError
>
>
> The means that subclasses often have to override all the parent's
> methods, just to ensure the type is correct:
>
> class MyInt(int):
> def __add__(self, other):
> o = super().__add__(other)
> if o is not NotImplemented:
> o = type(self)(o)
> return o
>
>
> Something like that, repeated for all the int methods, should work:
>
> py> n = MyInt(23)
> py> type(n+1)
> <class '__main__.MyInt'>
>
>
> This is tedious and error prone, but at least once it is done,
> subclasses of MyInt will Just Work:
>
>
> py> class MyOtherInt(MyInt):
> ... pass
> ...
> py> a = MyOtherInt(42)
> py> type(a + 1000)
> <class '__main__.MyOtherInt'>
>
>
> (At least, *in general* they will work. See below.)
>
> So, why not have int's methods use type(self) instead of hard coding
> int? The answer is that *some* subclasses might override the
> constructor, which would cause the __add__ method to fail:
>
> # this will fail if the constructor has a different signature
> o = type(self)(o)
>
>
> Okay, but changing the constructor signature is quite unusual. Mostly,
> people subclass to add new methods or attributes, or to override a
> specific method. The dict/defaultdict situation is relatively uncommon.
>
> Instead of requiring *every* subclass to override all the methods,
> couldn't we require the base classes (like int) to assume that the
> signature is unchanged and call type(self), and leave it up to the
> subclass to override all the methods *only* if the signature has
> changed? (Which they probably would have to do anyway.)
>
> As the MyInt example above shows, or datetime in the standard library,
> this actually works fine in practice:
>
> py> from datetime import datetime
> py> class MySpecialDateTime(datetime):
> ... pass
> ...
> py> t = MySpecialDateTime.today()
> py> type(t)
> <class '__main__.MySpecialDateTime'>
>
>
> Why can't int, str, list, tuple etc. be more like datetime?
>
>
>
> --
> Steve
> _______________________________________________
> Python-Dev mailing list
> Python-Dev at python.org
> https://mail.python.org/mailman/listinfo/python-dev
> Unsubscribe:
> https://mail.python.org/mailman/options/python-dev/mistersheik%40gmail.com
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20150214/492c37b7/attachment-0001.html>
More information about the Python-Dev
mailing list