advice on programming style: is multiple inheritance bad?

U

Uwe Mayer

Hi,

I got a class A2 that needs to inherit from class A1.
A2, as well as some other classes implement a same functionality and in
order to prevent code duplication I'd like to factorize that (potentially)
duplicate code into a class SuperA from which then A2 and all other classes
could inherrit.
However, this would cause A2 to have two super classes A1 and SuperA - which
is possible of course in Python.

My question is: is that bad programming style?
From C++ and Java we "learn" that you shouldn't do that.

Class A is automatically generated from other tools, so I can't solve the
problem by just letting A1 inherit from SuperA.

Your opinions?

Ciao
Uwe
--
 
R

Roy Smith

Uwe Mayer said:
Hi,

I got a class A2 that needs to inherit from class A1.
A2, as well as some other classes implement a same functionality and in
order to prevent code duplication I'd like to factorize that (potentially)
duplicate code into a class SuperA from which then A2 and all other classes
could inherrit.
However, this would cause A2 to have two super classes A1 and SuperA - which
is possible of course in Python.

My question is: is that bad programming style?
From C++ and Java we "learn" that you shouldn't do that.

C++ allows multiple inheritence, Java does not.

Whether multiple inheritence is bad or not is a matter of opinion. It
certainly can lead to complexities if the namespaces of the superclasses
overlap. To be honest, I don't see how Java's use of interfaces really
solves that problem; it just moves it somepwhere else.

Some people advocate that if you are going to use multiple inheritence,
you should think of it as one "main" superclass, and the rest of them
"mix-in" classes which add functions (think Decorator pattern).

On the other hand, sometimes you've got two orthogonal concepts that it
makes sense to represent as two inheritence trees using multiple
inheritence. For example, if you were classifying animals, you might
have 5 subclasses of vertibrate: fish, amphibian, bird, reptile, and
mammal. But, you might also want to have classes to represent modes of
locomotion: swimmer, walker, and flyer. This might lead to such classes
as:

class whale (mammal, swimmer)
class penguin (bird, walker, swimmer)
class bat (mammal, walker, flyer)
class flyingFish (fish, swimmer, flyer)
class flounder (fish, swimmer)
class africanSparrow (bird, walker, flyer).

whale.greatestDepth() makes sense, but bat.greatestDepth() does not.
Likewise, africanSparrow.unladenAirspeed(), but not
penguin.unladenAirspeed().

Is that bad programming style? If it turns out to be the simpliest way
to accurately represent the objects you're working with in the problem
domain you're trying to solve, I don't see why it should be considered
bad style.

This is an area where religious beliefs come into play. I'm sure you
will find people who would recoil violently at the idea. Most of them
probaby grew up on Java :)
 
I

Irmen de Jong

Uwe said:
However, this would cause A2 to have two super classes A1 and SuperA - which
is possible of course in Python.

My question is: is that bad programming style?
From C++ and Java we "learn" that you shouldn't do that.

From C++? Didn't think so... C++ is perfectly capable of
multiple inheritance. Or do you mean that you can get into
trouble quickly because of the various access levels,
virtual base classes etc?


Anyway my feelings about this subject:
I'm not stronly in favor of multiple inheritance
but I'm also not strongly against it.
In my experience there are times that I find m.i.
sorely lacking in Java because I want to do things
like mix-in classes. At other times I got myself into
trouble with a complex m.i. class tree in C++.

If you keep http://www.python.org/2.3/mro.html within
reach, I think you should be just fine using m.i. in Python.
I never had trouble with Python using m.i., but I tend to
avoid it and use it only where it really matters.

--Irmen de Jong
 
J

John Roth

Uwe Mayer said:
Hi,

I got a class A2 that needs to inherit from class A1.
A2, as well as some other classes implement a same functionality and in
order to prevent code duplication I'd like to factorize that (potentially)
duplicate code into a class SuperA from which then A2 and all other classes
could inherrit.
However, this would cause A2 to have two super classes A1 and SuperA - which
is possible of course in Python.

My question is: is that bad programming style?
From C++ and Java we "learn" that you shouldn't do that.

Class A is automatically generated from other tools, so I can't solve the
problem by just letting A1 inherit from SuperA.

Your opinions?

I think it's a case of "it depends." There are places where multiple
inheritance solves a real problem, and places where using it is just
going to confuse the issue.

Languages that try to ban multiple inheritance quickly invent things
like interfaces and mixins, so there are obviously some uses for
the facility.

Some rules of thumb I like:

1. Is it a special case, like Java's interfaces or Ruby's mixins?
If so, you're probably on firm ground.

2. Have you thoroughly considered using composition instead
of inheritance? Inheritance is vastly oversold.

3. Does the same attribute show up on two different
legs you inherit from? That's a red flag: you probably need
to rethink the design.

John Roth
 
D

Dietrich Epp

Since you asked for advice on programming style <grin>...

The argument for single inheritance is that objects should only have
one "is a" relationship. Penguin "is a" bird. For multiple
inheritance, Penguin "is a" swimmer also. However, to keep everything
clear, I would try to make sure each object has only one "is a"
inheritance, and all the other inheritances are "does", e.g., Penguin
"is a" bird, "does" fly. If an object really does have two "is a"
relationships, try to use two objects. For example:

class AirlineReservation(Person, SeatOnAirplane):
pass

This is kinda dumb and obvious, but it shows how multiple "is a"
relationships can be total nonsense. There are probably times when
multiple "is a" relationships make sense, but most of the time it's
just bad program design. It looks like your SuperA is a "does"
class... personally, I would name the types of classes differently, but
that's up to you.

class Bird:
def WhoAmI(self):
return "bird"

# or DoesSwim, CanSwim, whatever... I'd pick one keep it consistent
class Swimming:
def Swim(self):
print "A %s goes SPLASH!" % self.WhoAmI()

class Penguin(Bird, Swimming):
def WhoAmI(self):
return "penguin"

I think pure single inheritance without mixins is bad language design.
Arguments against multiple inheritance usually involve overlapping
superclasses, and every time I have seen it it *has* been bad design...
but does anyone have overlapping superclasses that they can't separate?
I'm curious.
 
J

Joe Mason

From C++? Didn't think so... C++ is perfectly capable of
multiple inheritance. Or do you mean that you can get into
trouble quickly because of the various access levels,
virtual base classes etc?

When profiling a large app at work, we discovered that in C++ accessing
every parent class after the first is quite slow. We were able to just
flip the order of inheritance for some classes, but if you're planning a
complex use of multiple inheritance you should keep that in mind. (I'm
just assuming it has the same effect in Python.)
Anyway my feelings about this subject:
I'm not stronly in favor of multiple inheritance
but I'm also not strongly against it.
In my experience there are times that I find m.i.
sorely lacking in Java because I want to do things
like mix-in classes. At other times I got myself into
trouble with a complex m.i. class tree in C++.

Yep, I agree with this. A mix-in sounds like what the OP was talking
about, so that should be fine, but if it starts to get tangled, stop and
think about using a delegate or something instead.

Joe
 
R

Roy Smith

Joe Mason said:
When profiling a large app at work, we discovered that in C++ accessing
every parent class after the first is quite slow. We were able to just
flip the order of inheritance for some classes, but if you're planning a
complex use of multiple inheritance you should keep that in mind. (I'm
just assuming it has the same effect in Python.)

The execution environments of C++ and Python are completely different.
Compiled vs. interpreted. Dynamic vs. static binding. There's no
reason to believe that what's fast or slow in one language should be
fast or slow in the other, especially with such low-level details of how
inheritance works.

Your comment about profiling is a good one, though. If you want to see
what makes an application slow, profile it. Measuring always works
better than guessing. And that's true in any language.
 
J

John Roth

Joe Mason said:
When profiling a large app at work, we discovered that in C++ accessing
every parent class after the first is quite slow. We were able to just
flip the order of inheritance for some classes, but if you're planning a
complex use of multiple inheritance you should keep that in mind. (I'm
just assuming it has the same effect in Python.)

Actually, it doesn't. For new style classes, Python
precomputes the method resolution order, so looking
up a method is simply flipping through a list of
dictionaries until it finds the right one. It's basically
O(n) in the number of classes in the hierarchy.

Given that you can add or remove attributes from
any class at any time, it's hard to see how it could
be any faster.

John Roth
 
A

Aahz

Actually, it doesn't. For new style classes, Python precomputes the
method resolution order, so looking up a method is simply flipping
through a list of dictionaries until it finds the right one. It's
basically O(n) in the number of classes in the hierarchy.

Given that you can add or remove attributes from any class at any time,
it's hard to see how it could be any faster.

Actually, it's easy to see how it could be faster:

The problem with attribute lookup in new-style classes is that any
instance attribute could be a property. So if Python finds an attribute
on the instance, it needs to traverse the MRO to check if there's a
property. If there isn't one, that's an expensive waste.

Guido, Ping, and I worked on a patch to fix that at last year's PyCon
sprint, but one common case slowed down enough that Guido vetoed the
patch. Anyone who wants to bring fresh eyeballs would be welcome.
(Don't remember the precise specifics off-hand, but I'm willing to work
with someone who wants to try.)
--
Aahz ([email protected]) <*> http://www.pythoncraft.com/

"The joy of coding Python should be in seeing short, concise, readable
classes that express a lot of action in a small amount of clear code --
not in reams of trivial code that bores the reader to death." --GvR
 
G

Gary Stephenson

Fwiw, I am both a human, and a customer of my bank. My computer is both an
"electrical_device" and a "depreciable-asset". It is completely natural in
the real world for objects to have multiple "is-a" relationships with
various other objects. Why then, should we not use multiple inheritance
when modelling such relationships?

One major reason why multiple inheritance has such a bad name can be sheeted
back to the brain-dead way in which C++ implements it. The other main
reason is that inheritance itself, single or multiple, implementation or
interface, is _way_ overrated as a code structuring technique. This is
probably due to it not even being consistently defined - it seems to mean
different things to different people at different times (depending on which
language they're currently working with).

IMHO, inheritance only works robustly in the face of change if you can
guarantee that the potential instances of your (sub)classes form a strict
proper subset (mathematically speaking) of the potential instances of their
ancestor classes. afaik, which is not terribly far really
;-) , there is no OO language on earth that can _guarantee_ this subclass ==
subset relationship for you - you have to arrange it yourself (somehow).

Other folk have also suggested that in order to properly achieve the above,
you need to follow the rule: "only abstract classes can be subclassed".
That is, if you want to create class B as a subclass of class A, and A is
not already an abstract class, you should create a third class A', which
_is_ an abstract class, and have both class A and class B inherit from that.
For a far better explanation of this and many other related issues I refer
the interested reader to:

http://okmij.org/ftp/Computation/Subtyping/Trouble.html

my $A0.0148,

gary
 
J

John Roth

Aahz said:
Actually, it's easy to see how it could be faster:
The problem with attribute lookup in new-style classes is that any
instance attribute could be a property. So if Python finds an attribute
on the instance, it needs to traverse the MRO to check if there's a
property. If there isn't one, that's an expensive waste.

I thought about that when new style classes came out, and managed
to convince myself that it couldn't happen, at least with data
properties. Unless trying to set the *same* attribute from the
setter invoked from the property actually works? That seems like
a lot of work to make it function poorly.

John Roth
 
U

Uwe Mayer

Irmen de Jong wrote:

From C++ and Java we "learn" that you shouldn't do that.
From C++? Didn't think so... C++ is perfectly capable of
multiple inheritance. Or do you mean that you can get into
trouble quickly because of the various access levels,
virtual base classes etc?

That was a stupid sentence from me, sorry. On a lecture on software
engeneering I was tought that multiple inheritance was a bad thing to do,
since there are problems with the resolution order which leads to badly
readable code, induces errors, shows poor software design, etc.
I see the point in that. Their most favourite programming language is Java
which doesn't support m.i. anyways.
When writing the sentence above I was implying that m.i. in C++ propably is
difficult to handle (though I never programmed much C++) and then Java,
being a more modern programming language, abandoned m.i. in favour for
interfaces.

The problem I was working on in the first mail is written in PyQt, a Qt
extention for Python.
There you create your base classes in the Qt Designer. Functionality is
added by subclassing the base class and overriding the appropriate methods.
Since you then *have* to subclass from your Qt-Designer-base-class you're
alreade done once with inheriting.

BTW: when doing m.i. you can't mix new-style and old-style objects, or else
Python exits with a Segmentation Fault. :)

The second base class I am using implemented something like the Qt SIGNAL
and SLOT mechanisme (observer design pattern). This is perhaps rather a
"uses" or "can do" relationship than an "is a" relationship. I should have
delegated the signalling capabilities to another object, rather than to
inherit from it. However, I would have had to introduce a new instance
variable to delegate work to and all other objects relying on that
interface would have to be made aware of it.
To do this I could again introduce accessors... hmmm - I think I got to
think this over.

How much effort is it worth to keep up good transparent programming design
at cost of more code, more levels of delegation (slower) and a more complex
interior in general?

Ciao
Uwe
 
R

Roy Smith

Uwe Mayer said:
On a lecture on software engeneering I was tought that multiple
inheritance was a bad thing to do, since there are problems with the
resolution order which leads to badly readable code, induces errors,
shows poor software design, etc.

There are certainly problems with multiple inheritance, but that doesn't
mean that all uses of MI are inherently evil. It is certainly
worthwhile to listen to the arguments the anti-MI people make and think
about the problems.

Of course, you need to be able to factor out those arguments which are
language-specific from those which are more general.
When writing the sentence above I was implying that m.i. in C++ propably is
difficult to handle

Everything in C++ is difficult to handle. I don't see anything about
C++'s MI implementation which stands out as particularly worse or more
complex than the rest of the class inheritance system.
How much effort is it worth to keep up good transparent programming design
at cost of more code, more levels of delegation (slower) and a more complex
interior in general?

That's a complicated question, and I'm not sure I fully understand what
you're asking, but my general answer would be that your main goal should
be to make the program easy for somebody else to understand (perhaps
years from now, when you're no longer around to answer questions). The
number of lines of code is secondary, and speed of execution even less
important.

When the whole thing is done, and you're sure everything works, that's
the time to figure out if it's fast enough. If it's not, then you
should start profiling things to figure out where most of the time is
being spent and look there for speedups. The biggest speedups will
typically come from algorithmic improvements, not from low-level
optimizations like using or not using MI.
 
D

Dietrich Epp

Fwiw, I am both a human, and a customer of my bank. My computer is
both an
"electrical_device" and a "depreciable-asset". It is completely
natural in
the real world for objects to have multiple "is-a" relationships with
various other objects. Why then, should we not use multiple
inheritance
when modelling such relationships?

Because we (or at least I) don't program that way.

The problem is context. Programming usually involves very specific
contexts, such as accounting, image processing, or networking. Classes
in those contexts can be very narrowly defined.

Humans don't work in single contexts. We understand that a pencil is a
kind of writing instrument, but it is also a kind of weapon. It
depends on how you are holding it. Chairs are usually kinds of
furniture, but some chairs belong more in the category of art. To make
things worse [for computers], humans sometimes use terms that logically
contradict. Humans are very good at contexts.

Object-oriented programming is not meant to replicate natural language.
It's just a bridge between the way people think and the way computers
process data. Humans make lots of complicated "is a" relationships,
but it's not particularly useful when programming. Another way to
think of inheritance is a "has functionality of" relationships." Let's
say I have some data I want dynamically loaded from XML files and a
MySQL database.

class DataLoader:
pass

class XMLDataLoader(DataLoader):
def Load(self, what):
...

class SQLDataLoader(DataLoader):
def Load(self, what):
...

Since DataLoader has no functionality, I would delete it.
One major reason why multiple inheritance has such a bad name can be
sheeted
back to the brain-dead way in which C++ implements it. The other main
reason is that inheritance itself, single or multiple, implementation
or
interface, is _way_ overrated as a code structuring technique. This is
probably due to it not even being consistently defined - it seems to
mean
different things to different people at different times (depending on
which
language they're currently working with).

I agree. In addition to the rampant poor software design, programmers
use object-oriented programming to hammer their screws into the wall,
so to speak. However, I see OOP as fundamentally a good thing. It
tends to make code simpler, more obvious, and reduces duplication. I
also think that "is a" and "does" relationships are a good way to learn
OOP.
IMHO, inheritance only works robustly in the face of change if you can
guarantee that the potential instances of your (sub)classes form a
strict
proper subset (mathematically speaking) of the potential instances of
their
ancestor classes. afaik, which is not terribly far really
;-) , there is no OO language on earth that can _guarantee_ this
subclass ==
subset relationship for you - you have to arrange it yourself
(somehow).

This paragraph really confuses me. So if I have A, and B (subclass of
A), then the instances of B must be a subset of A? Isn't this a
tautology? I assume that you don't really mean 'proper' subset, as
that implies no abstract classes exist.
Other folk have also suggested that in order to properly achieve the
above,
you need to follow the rule: "only abstract classes can be subclassed".
That is, if you want to create class B as a subclass of class A, and A
is
not already an abstract class, you should create a third class A',
which
_is_ an abstract class, and have both class A and class B inherit from
that.
For a far better explanation of this and many other related issues I
refer
the interested reader to:

http://okmij.org/ftp/Computation/Subtyping/Trouble.html

my $A0.0148,

In C++, subclass implies subtype. But in Python, we don't really use
types in our programming. Only the functionality matters, which gives
it its agility IMHO.

Last and most certainly least (look at top),

class Me(Human, DoesPatronizeBank):
def __init__(self):
self.deposit(Dollars(1e12))
def do_stuff(self):
if self.hungry
self.eat(self.find_food(select(Thai, Mexican, Italian))

class Computer(ElectricalDevice):
def __init__(self):
self.tech_support_history = []

class Asset:
def __init__(self, obj):
self.obj = obj
 
M

Michael Hudson

Uwe Mayer said:
BTW: when doing m.i. you can't mix new-style and old-style objects, or else
Python exits with a Segmentation Fault. :)

Uhh, really? Example please.

Cheers,
mwh
 
J

John Roth

Gary Stephenson said:
Fwiw, I am both a human, and a customer of my bank. My computer is both an
"electrical_device" and a "depreciable-asset". It is completely natural in
the real world for objects to have multiple "is-a" relationships with
various other objects. Why then, should we not use multiple inheritance
when modelling such relationships?

Because it doesn't work in practice? I'm serious here. One of the
things we would like to do is eliminate the disconnect between
modeling and implementation, and the naive views of inheritance
relations that are common in elementary textbooks frequently
have very serious problems when you actually try to do anything
with them.

If I wanted to model something that had multiple diverse
roles, I'd pick some mechanism other than inheritance, and
expect the actual implementation to have a number of objects
that handle the diverse roles of "electrical-device", "depreciable-asset,"
and "personal-property," with the appropriate linkage so that
they all refer to the same conceptual object.
One major reason why multiple inheritance has such a bad name can be sheeted
back to the brain-dead way in which C++ implements it.

Implementation has nothing to do with it.
The other main
reason is that inheritance itself, single or multiple, implementation or
interface, is _way_ overrated as a code structuring technique. This is
probably due to it not even being consistently defined - it seems to mean
different things to different people at different times (depending on which
language they're currently working with).

And if you want transparency in your implementation as well
as a domain model you can actually use, you have to live within
the limitations of your technology. That means that inheritance
is implementation inheritance, and any other definition is simply
academic pie-in-the-sky arm waving. If you want to do something
other than implementation inheritance, you find some way of
modeling it which will map cleanly into the technology.
IMHO, inheritance only works robustly in the face of change if you can
guarantee that the potential instances of your (sub)classes form a strict
proper subset (mathematically speaking) of the potential instances of their
ancestor classes. afaik, which is not terribly far really
;-) , there is no OO language on earth that can _guarantee_ this subclass ==
subset relationship for you - you have to arrange it yourself (somehow).

And that's one of the conceptual pitfalls. A mathematician would
look at the behavior of a subclass as having ***less*** functionality
than the superclass, while in practice subclasses usually have either
*different* functionality or *more* functionality.

So we're back at the naive definition does not match the
implementation reality.
Other folk have also suggested that in order to properly achieve the above,
you need to follow the rule: "only abstract classes can be subclassed".
That is, if you want to create class B as a subclass of class A, and A is
not already an abstract class, you should create a third class A', which
_is_ an abstract class, and have both class A and class B inherit from that.
For a far better explanation of this and many other related issues I refer
the interested reader to:

http://okmij.org/ftp/Computation/Subtyping/Trouble.html

my $A0.0148,

That's just confusing the issue. The Law is that any subclass
should be usable when the user expects a superclass. I believe
that's called the Liskov Substitution Principle, and it's the only
thing you need to remember.

The moment you try to mix two classes that have the same
name for two different concepts, you're dead.

John Roth
 
S

Stephan Diehl

John Roth wrote:

[...]
And that's one of the conceptual pitfalls. A mathematician would
look at the behavior of a subclass as having ***less*** functionality
than the superclass, while in practice subclasses usually have either
*different* functionality or *more* functionality.

I'd really question your statement here. Take for example the "class" of all
real functions (f:R->R) and then have a look at "smooth" functions and
"differential" functions.
These "subclasses" have actually **more** functionality (in the sense that
theorems that are true for real functions are also true for smooth
functions while the reverse is not true).

This all hinges, of course, what a "class" is supposed to be. Since nobody
can agree on the definition of OO, math is best left alone as an argument
pro/contra some OO scheme.

Just my 2c

Stephan
 
U

Uwe Mayer

Michael said:
Uhh, really? Example please.

Seems only to occur in connection with Qt. :(
Code as the one below, inheriting from some Qt Widget and using m.i. with
new-style classes cause a Segmentation Fault on my machine:

-- example --
from qt import QWidget, QApplication
import sys

class A:
def __init__(self):
print "in A"

class B(object):
def __init__(self):
print "in B"

class C(QWidget,A):
def __init__(self):
QWidget.__init__(self)
A.__init__(self)
print "in C"

class D(QWidget,B):
def __init__(self):
QWidget.__init__(self)
B.__init__(self)

app = QApplication(sys.argv)
C() #works
D() #fails
-- example --

Ciao
Uwe
 
G

Greg Chapman

The problem with attribute lookup in new-style classes is that any
instance attribute could be a property. So if Python finds an attribute
on the instance, it needs to traverse the MRO to check if there's a
property. If there isn't one, that's an expensive waste.

Guido, Ping, and I worked on a patch to fix that at last year's PyCon
sprint, but one common case slowed down enough that Guido vetoed the
patch. Anyone who wants to bring fresh eyeballs would be welcome.
(Don't remember the precise specifics off-hand, but I'm willing to work
with someone who wants to try.)

I assume the work you did last year is the cache-attr-branch in the CVS? Is
there some documentation somewhere that more fully describes the reasons for
your approach (and possibly rejected alternatives)?

I wonder if you considered turning off the cache for types with no instance dict
(tp_dictoffset == 0)? The slowdown in access to attributes defined in the first
class in the mro is probably most critical for built-in types (without instance
dicts), which access both methods and data using class attributes. For types
implemented in Python, I for one would be willing to trade some slow down in
accessing class attributes (generally a prelude to making a Python function
call, which is going to be relatively slow anyway), for a speed up in accessing
instance attributes.
 
M

Michael Hudson

Uwe Mayer said:
Seems only to occur in connection with Qt. :(

Ah said:
Code as the one below, inheriting from some Qt Widget and using
m.i. with new-style classes cause a Segmentation Fault on my
machine:

Weird. I take it you've reported this to the PyQT folks?

Cheers,
mwh
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,183
Messages
2,570,969
Members
47,524
Latest member
ecomwebdesign

Latest Threads

Top