[Python-Dev] subclassing builtin data structures

Neil Girdhar mistersheik at gmail.com
Sat Feb 14 21:42:27 CET 2015


Oops, I meant to call super if necessary:

    @classmethod
    def __make_me_cls__(cls, arg_cls, *args, **kwargs):
        if arg_cls is C:
            pass
        elif arg_cls is D:
            args, kwargs = modified_args_for_D(args, kwargs)
        elif arg_cls is E:
            args, kwargs = modified_args_for_D(args, kwargs)
        else:
            return super().__make_me_cls__(arg_cls, args, kwargs)

        if cls is C:
            return C(*args, **kwargs)
        return cls.__make_me_cls__(C, *args, **kwargs)


On Sat, Feb 14, 2015 at 3:15 PM, Neil Girdhar <mistersheik at gmail.com> wrote:

> 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/0b523e86/attachment.html>


More information about the Python-Dev mailing list