(original) (raw)
Unlike a regular method, you would never need to call super since you should know everyone that could be calling you. Typically, when you call super, you have something like this:
A < B, C
B < D
so you end up with
mro: A, B, C, D
And then when A calls super and B calls super it gets C which it doesn't know about.
But in the case of make\_me, it's someone like C who is calling make\_me. If it gets a method in B, then that's a straight-up bug. make\_me needs to be reimplemented in A as well, and A would never delegate up since other classes in the mro chain (like B) might not know about C.
Best,
Neil
On Fri, Feb 13, 2015 at 7:00 PM, Isaac Schwabacher <ischwabacher@wisc.edu> wrote:
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 \_\_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. In the case of defaultdict, for example, he can implement \_\_make\_me\_\_ as follows:
\>
\>
\> def \_\_make\_me\_\_(self, cls, \*args, \*\*kwargs):
\> if cls is dict: return default\_dict(self.default\_factory, \*args, \*\*kwargs)
\> return default\_dict(\*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):
....@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@gmail.com(javascript:main.compose()> wrote:
>
\> >
\> > On Fri, Feb 13, 2015 at 4:44 PM, Neil Girdhar <mistersheik@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)
\> >
\> >
\> >
\> >
\> >
\> >
\> >
\> >
\> >
\> >