Flagging classes as not intended for direct initialization

Chris Angelico rosuav at gmail.com
Mon Feb 16 10:29:44 EST 2015


On Tue, Feb 17, 2015 at 2:00 AM, Mario Figueiredo <marfig at gmail.com> wrote:
> On Tue, 17 Feb 2015 01:11:01 +1100, Chris Angelico <rosuav at gmail.com>
> wrote:
>
>>So what you have here is not "super() is weird", but "multiple
>>inheritance is messy, and this is how Python handles it".
>>
>
> I'd say the diamond pattern is messy. MI is otherwise a pretty
> peaceful kid without it.

In Python, everything ultimately inherits from 'object', so any MI
will become a diamond. You absolutely (and correctly) expect every
object, regardless of its inheritance tree, to respond to methods like
__repr__; if you multiply inherit, somewhere along the way, you're
going to get a __repr__ function. (Actually, with that particular
example, it's probably important to override, rather than depend on
the superclasses.)

> I don't find the C3 linearization method particularly hard to
> understand either. Contrary to Mark, I don't look at MRO as some
> esoteric property of Python. Heck, took me more time to understand and
> take advantage of tuple packing and unpacking, than it took me to
> understand MRO.

I had to get my head around the way the calls can move "across" the
hierarchy, rather than simply up it. It means that "calling the
superclass" might actually call a class that's completely unrelated to
the one you're doing this from. In C++, calling the superclass can
only ever call a parent of the current class.

(Pike has a completely different model, though. Instead of calling one
parent method, you can call all of them! It's very cool... as long as
you know what you're doing when you make a diamond.)

> What may make MRO complex is large hierarchic trees, defined across
> multiple modules. But that really is complex under any method
> resolution model we can think of.

Precisely. It's MI that makes the complexity possible, and the MRO is
one of a number of ways to tame that.

> What really put me off was instead the mention to lack of support for
> variadic methods and some apparent problems with operator overloading.
> I reasoned that for the sake of consistency, I'm probably better using
> super only when I need its specific set of features.

I've no idea what he means by that, unless he's talking about methods
with the same name and different argument signatures. If you have a
structure like that, then any MI system is going to have problems.
Possibly the best way to cope is to use **args everywhere, pop out the
ones you care about, and pass the rest on.

>>Explicit naming of superclasses is fragile for several reasons.
>>Firstly, it breaks the parallel between "method which calls its
>>superclass method" and "absence of method, and implicit referral to
>>superclass" (as the latter guarantees to use the MRO);
>
> I agree. Super is more explicit than an explicit method call. Which is
> ironic. Both in fact can coexist, with explicit method calls being
> used to denote an MRO deviation, for instance.
>
> But frankly, I don't see the breaking of that  parallel as a problem.
> For the most part, you can code in complete ignorance of MRO, even in
> the presence of MRI. MI name clashing is something that we ought to
> avoid anyways and it is so rare we ever have to confront it, that
> making a case for super on that basis is a bit weak.

Sure, but I would say that the normal, default assumption should be
that it's easy to do nothing. I should be able to adorn a parent
method as easily as I can adorn with a function decorator. Compare:

def announce(func):
    @wraps(func):
    def inner(*a,**kw):
        print("Calling %s!"%func.__name__)
        return func(*a,**kw)
    return inner

There's a bit of boilerplate to make a pass-through decoration like
that, but it's pretty straight-forward. Here's the equivalent Py3
class:

class blah(...):
    def method(self, *a, **kw):
        print("Calling method on %r!"%self)
        return super().method(*a, **kw)

If you use this model with single inheritance, you could simply name
the parent class in the last line, and it'd be fine. But then you
subclass this, and suddenly your adornment results in a completely
different method being called - that's going to be seriously
surprising. And you don't have to change anything in this class itself
for the wrapper to suddenly break.

ChrisA



More information about the Python-list mailing list