7 ms·
I admit I can't see anything wrong here. What's the bug?
by saeranv 6y ago
I admit I can't see anything wrong here. What's the bug?
- mehrdadn 6y agoThis should help: what should the following code output? class B(object): def __init__(self): self.y = 2 class C(A, B): pass print(C().y)
- vore 6y agoWhat's the issue with the original code snippet? This seems to be demonstrating the pitfalls of multiple inheritance which is super clunky in any language.
- mehrdadn 6y agoIt's missing a super().__init__() call. This code would've worked fine had that line been there. You would not encounter this issue in e.g. C++. It's strictly due to Python's idiosyncratic linearization of the inheritance hierarchy, which can make your sibling class (which you have no knowledge of) your superclass. (!!)
- vore 6y agoThe solution in C++ isn't so great either, considering: struct Base { Base() { cout << "Base" << endl; } }; struct Derived1 : Base { Derived1() { cout << "Derived1" << endl; } }; struct Derived2 : Base { Derived2() { cout << "Derived2" << endl; } }; struct Join : Derived1, Derived2 { Join() { cout << "Join" << endl; } }; And then you get two calls to Base: Base Derived1 Base Derived2 Join There's virtual base class inheritance but then that's a different problem that's hardly any more straightforward than Python.
- mehrdadn 6y ago> And then you get two calls to Base: "2 calls to Base" is a misleading way to put it. You in fact get exactly 1 call per every instance of Base in the hierarchy (and they act independently). Contrast that with Python where you can accidentally call the same constructor multiple times for the same instance of that class, and Python doesn't care to prevent you. (And notice how others' proposed solutions in another comment did exactly that, and they didn't realize it either.) > There's virtual base class inheritance but then that's a different problem that's hardly any more straightforward than Python. This is debatable (the Python behavior is quite unintuitive) but "straightforwardness" is actually besides my point. At least with virtual base classes, the solution (whether complicated or not) is in the same place as the problem: both are in the derived class. With Python, the solution is in the base class, which another author wrote a long time ago. Whereas the problem only occurs when you, another poor soul, tries to derive from it a long time later. This kind of spooky action at a distance makes it impractical to abstract things away... in a sense it literally violates causality (for the lack of a better word)! That's not something to take lightly, to say the least.
- theg5prank 6y agoThat's not at all what Python does. It linearizes the inheritance hierarchy for the purposes of superclass attribute lookup, in an attempt to solve some aspects of the diamond problem, or at least make them solvable. But it doesn't touch the types themselves, only the MRO used by the super builtin. That's why the super builtin type takes both the "current" class and the object instance itself, which was more visible before the whining of people who didn't understand it grew too loud to be borne by the maintainers and the project introduced the `super()` syntactic sugar. Not that I'm bitter about that or anything. Anyway, your argument seems to be that you should be able to use naively written classes in a multiple inheritance hierarchy. I suppose that's a defensible opinion, but it's also not the case in any language I know of; C++ solves the problem of not calling the "correct" superclass by default by foisting the responsibility of choosing the correct subset onto the derived class author, but doesn't solve the "I called my superclass' method too many times" problem, only the "I have too many (n>1) copies of my superclass' data" problem (the solution to which is also opt-in). Python resolves all three of these, but in so doing requires careful construction of multiple inheritance hierarchies and use of the super builtin that is religious to the point of fanatical. That's a tradeoff made in respect to a feature that, IMO, is inherently sharp. Aside: "idiosyncratic" is an interesting word to use given that the C3 MRO was originally intended for Dylan.
- mehrdadn 6y ago> That's not at all what Python does. I know what's going on and my comment was sufficiently accurate to get the point across in 1 sentence. I wasn't exactly intending to get into a lecture on MRO here. > C++ solves the problem of not calling the "correct" superclass by default by foisting the responsibility of choosing the correct subset onto the derived class author Which is much better than leaving a ticking time bomb. > doesn't solve the "I called my superclass' method too many times" Er, yes it does. Every class instance in a hierarchy is initialized exactly once. Trying to call it twice will produce an error (e.g. "class has already been initialized"). > Python resolves all three of these It does not. It's perfectly fine letting you call a class's __init__ multiple times. Unlike with C++. > Aside: "idiosyncratic" is an interesting word to use given that the C3 MRO was originally intended for Dylan. It is a fine word. "Idiosyncrasy" does not imply there exists only 1 person in the whole world engaging in the given practice: "Her habit of using “like” in every sentence was just one of her idiosyncrasies." https://www.merriam-webster.com/dictionary/idiosyncrasy https://www.merriam-webster.com/dictionary/idiosyncrasy I'm tired of the pointless arguing so this will be my last comment.
- doteka 6y agoThe original code snippet had no bugs until you added more code, stop arguing in bad faith. Also, the new snippet has a syntax error, the last version where this was valid reached end of life January 1st, and has been deprecated for 10 years prior to that. Not sure how much stock to put in your python opinions given that context.
- mehrdadn 6y ago> The original code snippet had no bugs until you added more code, stop arguing in bad faith. Wow, that's a really low blow. I'm arguing in perfectly good faith. Failing to call the base class initializer introduces misbehavior in multiple inheritance and the way this happens in Python is completely unexpected. Not every language is like this. > Also, the new snippet has a syntax error, the last version where this was valid reached end of life January 1st, and has been deprecated for 10 years prior to that. Not sure how much stock to put in your python opinions given that context. Er, what syntax error are you talking about? https://ideone.com/3hI4Wj https://ideone.com/3hI4Wj
- WesolyKubeczek 6y agoYou're still arguing in bad faith. Your original code snippet didn't mention any kind of inheritance at all.
- deleted 6y ago[deleted]
- ZephyrBlu 6y ago> Wow, that's a really low blow. I'm arguing in perfectly good faith. Failing to call the base class initializer introduces misbehavior in multiple inheritance and the way this happens in Python is completely unexpected. Not every language is like this Isn't this a bug in class C, not your original snippet? I'm also not sure what you mean by "failing to call the base class initializer introduces misbehavior in multiple inheritance", this seems like an issue related to the MRO of the inherited classes. If you write class C with class B inherited first, the code runs. Example: https://repl.it/repls/BumpyPreemptiveQuerylanguage https://repl.it/repls/BumpyPreemptiveQuerylanguage
- renewiltord 6y agoWhat is the usual pattern here? I imagine you have A.__init__(self) and B.__init__(self) in C.__init__(), right? i.e. child logic is handled in the child, not in the parent.
- mehrdadn 6y agoNo I don't (the code is as-is), but in fact if I did, those those would still suffer from basically the same problem even if I did (exercise for the reader). In modern Python you're supposed to use super().__init__() to let it handle this issue with multiple inheritance.
- renewiltord 6y agoNo no, I understand the problem you're talking about. It is a problem. I'm asking about the solution. Why wouldn't you handle it in the child instead of having siblings be superable? Essentially, why not do it like this: class A(): def __init__(self): self.x = 1 class B(): def __init__(self): self.y = 2 class C(A, B): def __init__(self): A.__init__(self) B.__init__(self) print(C().y)
- theg5prank 6y agoNow your class C can't necessarily be used in a multiple inheritance hierarchy with some other class D, and a class E that inherits from C and D, as C hardcodes what its parents are. Python uses the C3 MRO algorithm to traverse all relevant classes in your hierarchy when you religiously use the super builtin. https://www.python.org/download/releases/2.3/mro/ https://www.python.org/download/releases/2.3/mro/ But; multiple inheritance is a beast in any language due to its complexity and bringing it up in a discussion of "magic" that python exposes beginners to is specious. It's true that C++ doesn't have this problem, because it has no super keyword and you must explicitly delegate everything, but also introduces the problem that you can have multiple instances of a base class and so has added complexity, virtual inheritance, to solve THAT problem. And tons of languages have a problem where omitting a call to "super" is logically required is not statically diagnosable.
- deleted 6y ago[deleted]
- wisty 6y agoMultiple inheritance was a mistake.
- 8note 6y agowhen does a beginner run into this though?
- mehrdadn 6y agoI'm not suggesting it's common; it really depends what the beginner is doing. If you're just learning Python because you're trying to use e.g. a web framework like Django (that uses mixin classes all over the place), then chances are you'll put __init__ somewhere and run into this. If you're doing something else, you might never see this in your life. My comment was just responding directly to the parent comment though (pointing out a Python flaw), not trying to suggest every Python developer will run into this.