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 <st...@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@python.org > https://mail.python.org/mailman/listinfo/python-dev > Unsubscribe: > https://mail.python.org/mailman/options/python-dev/mistersheik%40gmail.com >
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com