On Sun, Dec 16, 2012 at 9:30 PM, Nick M. Daly <nick.m.d...@gmail.com> wrote: > It's very unlikely that multiple inheritance would go horribly wrong, as > long as classes adopt class-specific argument naming conventions. > However, ever since bug 1683368 [0] was fixed, it's now impossible to > cleanly create arbitrary inheritance trees.
No, it isn't. You just code each class to strip out the particular arguments that it uses, and by the time you get up to object, either you've removed all the arguments, or your inheritance tree is buggy. The fix for bug 1683368 means that this latter case is detected and raised as an error. There was a thread not too long ago about the fact that this fix was recently extended to the __init__ methods of immutable classes, and while I'm not convinced that this was the correct thing to do, Terry Reedy pointed out in the issue comments back in 2010 that the proper way to initialize immutable instances is by overriding __new__ rather than __init__, the former of which is still perfectly clean to inherit. > The only reason I can't > just take anybody's code and plop it into my inheritance tree is because > Python demands that each class specifically opts in to MI though > mechanisms like the following: > > 1: class Foo(object): > 2: def __init__(self, foo_bar, *args, **kwargs): > 3: if Foo.__mro__[1] != object: > 4: super().__init__(*args, **kwargs) > 5: > 6: self.bar = foo_bar > > Lines 3 and 4 are required because Foo might fall at the beginning or > the middle of in the inheritance tree: we can't know ahead of time. Of course we can know the full MRO of Foo just by looking at this code. Foo derives from object, and nothing else. The MRO of Foo is therefore (Foo, object), and the test is always false. What we can't know ahead of time is the MRO of *self*, which could be an instance of a subclass of Foo. But line 3 is not testing the MRO of self, only of Foo. If self happens to be an instance of FooBar, with the MRO (FooBar, Foo, Bar, object), then the above code will cause bugs, because Bar.__init__ is never called. In any case, these lines are not necessary. The only reason not to call super() in cooperative MI is if the method does not exist on the super object. A better way to test this would be: if hasattr(super(), '__init__'): super().__init__(**kwargs) However, that test is still silly, since __init__ is a method of object and *always* exists. For non-init methods, best practice is to use a root class (as recommended by Raymond Hettinger [1]). Anything that implements the method would inherit from the root class (to ensure that it will precede the root class in the MRO) and call super(). The root class serves only to end the chain and does not call super(). > From my perspective, it'd be lovely if init methods implicitly accepted > *args and **kwargs while implicitly sending them off to the next class > in the MRO as the first call. This would make the previous example > semantically equivalent to: > > 1: class Foo(object): > 2: def __init__(self, foo_bar): > 3: self.bar = foo_bar > > Granted, that's probably too excessive and implicit for most folks to be > comfortable with, even though that's obviously the behavior a user > intends when they write code like: > > 1: class Baz(Foo, Bar): > 2: def __init__(self): > 3: super().__init__(foo_bar=1, bar_quote="Give me another!") I don't find that obvious at all. Does the implicit super() call happen before or after the body of the method? There are cases where the subclass may want to have some code before and some code after. How do you implicitly call super() in methods that return a value -- what do you implicitly do with the return value of the super() call? How do you write methods that intentionally do not call super, such as in the root classes mentioned above, or in methods that are simply meant to be overridden and not extended? If the user calls Baz(foo_bar=42), then does the super() call from Baz still pass foo_bar=1, or does it implicitly call super() with foo_bar=42 instead, or does it try to do "super().__init__(foo_bar=1, bar_quote="Give me another!", foo_bar=42)" and raise a TypeError due to repeated arguments? -- http://mail.python.org/mailman/listinfo/python-list