[Python-Dev] subclassing builtin data structures (original) (raw)

Isaac Schwabacher ischwabacher at wisc.edu
Sat Feb 14 01:00:02 CET 2015


On 15-02-13, 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 makeme(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 somemethod(self, ...): return self.makeme(SomeCls, assumed arguments…) Now the derived class knows who is asking for a copy. In the case of defaultdict, for example, he can implement makeme as follows: def makeme(self, cls, *args, **kwargs): if cls is dict: return defaultdict(self.defaultfactory, *args, **kwargs) return defaultdict(*args, **kwargs) essentially the caller is identifying himself so that the receiver knows how to interpret the arguments. Best, Neil

Such a method necessarily involves explicit switching on classes... ew. Also, to make this work, a class needs to have a relationship with its superclass's superclasses. So in order for DefaultDict's subclasses not to need to know about dict, it would need to look like this:

class DefaultDict(dict): .... at classmethod # instance method doesn't make sense here ....def make_me(cls, base, *args, **kwargs): # make something like base(*args, **kwargs) ........# when we get here, nothing in cls.mro above DefaultDict knows how to construct an equivalent to base(*args, **kwargs) using its own constructor ........if base is DefaultDict: ............return DefaultDict(*args, **kwargs) # if DefaultDict is the best we can do, do it ........elif base is dict: ............return cls.make_me(DefaultDict, None, *args, **kwargs) # subclasses that know about DefaultDict but not dict will intercept this ........else: ............super(DefaultDict, cls).make_me(base, *args, **kwargs) # we don't know how to make an equivalent to base.new(*args, **kwargs), so keep looking

I don't even think this is guaranteed to construct an object of class cls corresponding to a base(*args, **kwargs) even if it were possible, since multiple inheritance can screw things up. You might need to have an explicit list of "these are the superclasses whose constructors I can imitate", and have the interpreter find an optimal path for you.

On Fri, Feb 13, 2015 at 5:55 PM, Alexander Belopolsky <alexander.belopolsky at gmail.com(javascript:main.compose()> wrote:

> > On Fri, Feb 13, 2015 at 4:44 PM, Neil Girdhar <mistersheik at gmail.com(javascript:main.compose()> wrote: > > > Interesting: http://stackoverflow.com/questions/5490824/should-constructors-comply-with-the-liskov-substitution-principle > > > > > Let me humbly conjecture that the people who wrote the top answers have background in less capable languages than Python. > > > Not every language allows you to call self.class(). In the languages that don't you can get away with incompatible constructor signatures. > > > However, let me try to focus the discussion on a specific issue before we go deep into OOP theory. > > > With python's standard datetime.date we have: > > > >>> from datetime import * > >>> class Date(date): > ... pass > ... > >>> Date.today() > Date(2015, 2, 13) > >>> Date.fromordinal(1) > Date(1, 1, 1) > > > Both .today() and .fromordinal(1) will break in a subclass that redefines new as follows: > > > >>> class Date2(date): > ... def new(cls, ymd): > ... return date.new(cls, *ymd) > ... > >>> Date2.today() > Traceback (most recent call last): > File "", line 1, in > TypeError: new() takes 2 positional arguments but 4 were given > >>> Date2.fromordinal(1) > Traceback (most recent call last): > File "", line 1, in > TypeError: new() takes 2 positional arguments but 4 were given > > > > > Why is this acceptable, but we have to sacrifice the convenience of having Date + timedelta > return Date to make it work with Date2: > > > >>> Date2((1,1,1)) + timedelta(1) > datetime.date(1, 1, 2) > > > > > > > > > >



More information about the Python-Dev mailing list