On 15-02-13, Neil Girdhar  wrote:
> 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&#39;t 
> know about.

But C calls super and gets D. The scenario I'm concerned with is that A knows 
how to mimic B's constructor and B knows how to mimic D's, but A doesn't know 
about D. So D asks A if it knows how to mimic D's constructor, and it says no. 
Via super, B gets a shot, and it does know, so it translates the arguments to 
D's constructor into arguments to B's constructor, and again asks A if it knows 
how to handle them. Then A says yes, translates the args, and constructs an A. 
If C ever gets consulted, it responds "I don't know a thing" and calls super.

> But in the case of make_me, it&#39;s someone like C who is calling make_me. 
> If it gets a method in B, then that&#39;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.

This scheme (as I've written it) depends strongly on all the classes in the MRO 
having __make_me__ methods with this very precisely defined structure: test 
base against yourself, then any superclasses you care to mimic, then call 
super. Any antisocial superclass ruins everyone's party.

> Best,
> Neil
> 
> 
> On Fri, Feb 13, 2015 at 7:00 PM, Isaac Schwabacher 
> <alexander.belopol...@gmail.com <ischwabac...@wisc.edu')" 
> target="1">ischwabac...@wisc.edu> wrote:
> 
> > On 15-02-13, Neil Girdhar wrote:
> > > I personally don&#39;t think this is a big enough issue to warrant any 
> > > changes, but I think Serhiy&#39;s solution would be the ideal best with 
> > > one additional parameter: the caller&#39;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&#39; 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&#39;s superclasses. So in order for DefaultDict&#39;s subclasses 
> > not to need to know about dict, it would need to look like this:
> > 
> > class DefaultDict(dict):
> > ....@classmethod # instance method doesn&#39;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&#39;t know how to make an equivalent to base.__new__(*args, **kwargs), 
> > so keep looking
> > 
> > I don&#39;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 
> > > <http://stackoverflow.com/questions/5490824/should-constructors-comply-with-the-liskov-substitution-principle(javascript:main.compose('new',
> > >  't=alexander.belopol...@gmail.com>(java_script:main.compose()> wrote:
> > >
> > > >
> > > > On Fri, Feb 13, 2015 at 4:44 PM, Neil Girdhar <mistersh...@gmail.com 
> > > > <mistersh...@gmail.com>(java_script:main.compose()> wrote:
> > > >
> > > > > Interesting: > > Not every language allows you to call 
> > > > > self.__class__(). In the languages that don&#39;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&#39;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 "<stdin>", line 1, in <module>
> > > > TypeError: __new__() takes 2 positional arguments but 4 were given
> > > > >>> Date2.fromordinal(1)
> > > > Traceback (most recent call last):
> > > > File "<stdin>", line 1, in <module>
> > > > 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)
> > > >
> > > >
> > > >
> > > >
> > > >
> > > >
> > > >
> > > >
> > > >
> > > >
> > 
> > 
> >
_______________________________________________
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

Reply via email to