Tuples and immutability

Steven D'Aprano steve+comp.lang.python at pearwood.info
Tue Mar 11 17:46:51 CET 2014

On Tue, 11 Mar 2014 04:39:39 -0600, Ian Kelly wrote:

> On Mon, Mar 10, 2014 at 11:03 PM, Gregory Ewing
> <greg.ewing at canterbury.ac.nz> wrote:
>> As far as observable effects are concerned, it's quite clear: mutable
>> objects can *always* be updated in-place, and immutable objects can
>> *never* be.
> Hm. Consider the circle-ellipse problem.

Oh, not that old chestnut! The circle-ellipse problem demonstrates one 
limitation of OOP (specifically "subtype polymorphism"), as well as a 
general difficulty with hierarchical taxonomies. Mammals have hair -- 
what do you make of hairless[1] dolphins? Circle/Ellipse is not a good 
place to start from in order to critic augmented assignment in Python. 
You're starting from a place where inheritance itself is problematic, so 
naturally you're going to find problems.

> Briefly, a circle is-an ellipse, so in an inheritance hierarchy it is
> natural to make Circle a subclass of Ellipse.

Natural and wrong. It's only natural if you don't think through the 
consequences. As you go on to say:

> Now suppose the Ellipse has a stretch method that
> mutates the ellipse by changing the length of one of its axes while
> preserving the other.  To avoid violating LSP, the Circle class must
> support all the methods of its ancestor.  However it cannot, because the
> stretch method would invalidate the invariant of the Circle class that
> both of its axes must always be equal.

Right. So *Circles cannot be Ellipses*, not without violating the Liskov 
Substitution Principle. If I think that they are, I haven't thought it 
through. Nor can Ellipses be Circles. That's the problem of the Circle/
Ellipse relationship.

(Aside: the LSP is not a Law Of Physics that cannot be touched. There are 
other OOP models that don't require LSP.)

Even in the case of immutable shapes, one might not wish to inherit 
Circle from Ellipsis. Ellipses have five degrees of freedom:

2 x position
size (scale)

while circles only have three:

2 x position

Orientation and shape are meaningless for circles! So they should not 
inherit from a class where they are meaningful: given the LSP, a subclass 
cannot be more restrictive than a superclass.

> There are a number of possible solutions.  One possibility would be to
> copy the Circle as an Ellipse and return the new object instead of
> mutating it. Then you have the situation where, given a mutable object
> x that satisfies isinstance(x, Ellipse), the stretch method *may* be
> able to update the object in-place, or it *may* not.

That is a really lousy design. Of course we are free to create classes 
with ugly, inconsistent, stupid or unworkable APIs if we like. Python 
won't stop us:

class MyList(list):
    def append(self, obj):
        if len(self) % 17 == 3:
            return self + [obj]
        super(MyList, self).append(obj)

> I can't think of a reasonable example that would replace the stretch
> method here with an augmented assignment, but then it is rather late.
>> It might be the obvious way for that particular operation on that
>> particular type. 

Um, yes? Nobody suggests that a method of type X has to be the most 
obvious way for *any operation* on *any type*. What's your point?

> But what about all the others? What's the obvious way
>> to spell in-place set intersection, for example? 

I would expect it to be &=, let's find out if I'm right:

py> a = set("abcde")
py> b = a  # save a reference to it
py> c = set("cdefg")
py> a &= c
py> a, b
({'c', 'd', 'e'}, {'c', 'd', 'e'})
py> a is b

>> (Quickly -- no peeking at the docs!)

The only reason I'd need to look at the docs is because I always forget 
whether & is intersection and | is union, or the other way around. But 
having remembered which is which, going from & to &= was easy.

> You mean set.intersection_update?  The in-place set methods are not hard
> to remember, because they all end in _update.

And hard to spell.

[1] Technically they aren't *entirely* hairless. They may have a few 
hairs around the blowhole, and baby dolphins are born with whiskers which 
they soon lose. But from a functional perspective, they are hairless.

Steven D'Aprano

More information about the Python-list mailing list