[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