Subclass sadness (was Re: [Python-Dev] PEP 285: Adding a bool type)

Alex Martelli aleax at aleax.it
Mon Apr 1 01:44:51 EST 2002


Aahz wrote:

> In article <slrnaacavs.2gs.philh at comuno.freeserve.co.uk>,
> phil hunt <philh at comuno.freeserve.co.uk> wrote:
>>On 30 Mar 2002 11:04:13 -0500, Andrew Koenig <ark at research.att.com> wrote:
>>>
>>>I wonder if this is the circle-ellipse problem over again?
>>
>>What is the circle-ellipse problem?
> 
> I'm not sure, but I'm going to guess for my own edification:
> 
> Instead of circle/ellipse, it's slightly easier to talk about
> square/rectangle.  In terms of standard geometry, a square is a subset
> of rectangles, such that height and width are identical.  But in OO

The set of squares is a subset of the set of rectangles, but "a square is
a subset or rectangles" is not correct (one _has_ to be nitpickish around
such delicate issues...).

> terms, it's a bit more complicated to inherit square from rectangle,
> because square has only side-length attribute where rectangle has two
> (height and width).  So to create a class square that inherits from
> rectangle, you either have to write special code that ties height and
> width together or you have to delete at least one of the attributes (in
> which case any code expecting to work with rectangle fails).

Actually, this specific problem is there, like several others, only
*for MUTABLE objects*.  Take an "immutable" rectangle:

class ImmutableRectangle(object):
    def __init__(self, width, heigth):
        object.__setattr__(self, 'width', width)
        object.__setattr__(self, 'heighth', heigth)
    def __setattr__(self, name, value):
        raise TypeError, "cannot mutate ImmutableRectangle"
    def __hash__(self):
        return hash(self.width) ^ hash(self.heigth)
    def area(self):
        return self.width*self.heigth
    def perimeter(self):
        return 2*(self.width+self.heigth)
    # etc, etc

Now, subclassing this is perfectly fine:

class ImmutableSquare(ImmutableRectangle):
    def __init__(self, side):
        ImmutableRectangle.__init__(self, side, side)

Net of tricky ways to go behind the interface and mutate the
"immutable" instances (which are neither here nor there... they
go below and behind the OO'ness, e.g. by working directly
with __dict__ etc), an instance of the (immutable) square is
fine wherever one of the similarly immutable rectangle would
be.  It's only if you're interested in mutable object state (rather
alien to the mathematical basis of geometry -- geometrical
entities are not conceived of as having separate 'identity' and
'state', so one could not even speak of the 'same' entity that
'changes'...) that things break down.

Another similar issue comes with containers -- say Set for
example.  In statically typed languages with generics, say I have
a Set<something> parameterized class ('template' in C++, e.g.).
I have a class Banana inheriting from Fruit, a function f that needs
an argument s of class Set<Fruit>, and an object b that is a
Set<Banana>.  Can I call f(b) type-safely?  Answer: if Set<...>
is immutable, yes.  But if it's mutable, no: f might insert an Apple
instance in its s parameter (type-safely, since s is a Set<Fruit>
and Apple also subclasses Fruit) and break the constraints on b's
type (all of b's items must be instances of Banana).

So, it's mutability that complicates things in these cases.  The
exponents of Functional Programming have built rather powerful
languages around immutable objects -- immutability may not be
as easy and comfortable to work with in practice, and it does not
model our computer systems all that well, but it sure does remove
certain mathematical anomalies!

Back to bool and int -- we ARE talking about immutables here,
since numbers are immutable in Python.  Therefore (net of the
str and repr issues, where IS-A breaks down either way... and
that's another problem) the "circle/ellipse anomaly" does not surface 
in any form.  Subclassing bool from int IS ok, given immutability.
    

> This kind of thing is what makes the Deitel book on Python really bad,
> because it doesn't give a clean picture of proper OOP (they make circle
> a subclass of point, and cylinder a subclass of circle, which is even a
> worse botch than trying to deal with circle/ellipse).

I agree (and I told them that, as a reviewer, but was unable to convince
them).  Inheritance does not model IS-A at all well in such cases (even
though isinstance and exception handling appear to want to believe that
Python subclassing implements IS-A), but beginners will still be confused
unless you try to give parallels between the two concepts -- and if you
try, as they do, then such examples are counterproductive.


> In general, I think the evidence has shown that subclassing is an
> exercise fraught with danger and should be de-emphasized as a primary
> technique in OOP.  I'm very glad that Python uses implicit protocols
> through magic methods for much of its OO processing; that makes it much
> easier to cut through the Gordian Knot.

Yes -- net of isinstance and exception handling.  PEP 246 would make
it that much easier again.


Alex




More information about the Python-list mailing list