[Python-Dev] Breaking calls to object.__init__/__new__

Terry Jones terry at jon.es
Thu Mar 22 11:21:15 CET 2007


>>>>> "G" == Guido van Rossum <guido at python.org> writes:

G> There are different philosophies about the correct style for
G> cooperative super calls.

G> The submitter of the bug report likes to remove "consumed" arguments
G> and pass the others on, having something at the root that complains
G> about any unused arguments. It has the problem that you mention: if
G> multiple classes might be interested in the *same* argument they
G> won't see it. The other style is to pass *all* arguments down, and
G> let everyone cherry-pick them. The last call then just throws them
G> away.  This has the problem that misspelled arguments are silently
G> ignored rather than being diagnosed at the point where you can do
G> something about it.

G> I don't know what the "best practice" is (like Greg Ewing, I don't
G> use either style myself) but I've got a feeling that it must be
G> easier to solve the former problem than the latter (also I don't
G> know that the former actually occurs in practice).

I'm following up to the above, as I'm not sure where the best place to jump
into this discussion is.

I think the problem is more general that just consuming and/or passing on.
In the typical style of passing args to super classes that I've seen,
__init__ methods in the call chain also don't really even know *whether*
they should use an argument. There is also the problem of argument change
over time.

E.g., suppose I'm the writer of a class X and, unbeknownst to me, someone
writes class Y(X,Z), where Z is some other class. Suppose Z's __init__
expects an argument called z. If I one day add an argument called z, that
has totally different semantics, things get really hairy. I'm the writer of
X, I'm unaware of classes Y and Z, and if someone passes me an z argument
then naturally I want to (try to) use it.

There are other examples like this, and argument name clashes can be more
specific (like 'limit' or 'debug').

In general, we're faced with a situation that is also both time-sensitive
and semantics-sensitive. Class writers shouldn't need to know all future
argument changes to other classes potentially upstream or downstream from
them in the call chain. Given an unstructured mess of arguments, you don't
know what to consume, what to ignore, what to pass on, etc., and you don't
know the future.

Although this sounds much more difficult than simple diamond inheritance, I
think there's an easy way to solve it.

When writing a class, you know 1) what arguments your __init__ method
wants, and 2) what arguments your superclass(es) __init__ methods want.
You know the second thing from reading the API/docs of your superclasses -
that's why you're calling them. You want to be able, for example, to route
a 'limit' argument to one of your super classes and another different
'limit' argument to another of your super classes. You want this to work
even if one or more of your superclasses (now or in the future) in turn
decides to call some other super class that also has a 'limit' argument
with entirely different (or the same) semantics. Etc.

An easy solution is for __init__ to receive and pass a dictionary whose
keys are class names and whose values are dictionaries of arguments
intended for that class.

That way, an __init__ method in a class knows exactly which arguments are
intended for it. It can detect extra args, missing args, misspelt args,
etc. It can also prepare args for its known immediate superclasses (those
from which it subclasses) - and it does know these classes as it is
explicitly subclassing from them. It can leave arguments that are intended
for the __init__ methods in other classes alone. It can create different
arguments of the same name for its different superclasses. It can change
its signature (adding and dropping args) without affecting the args passed
to its superclass. Classes earlier in the call chain can do the same
without affecting the args received by this class. It is also immune to
differences in superclass name ordering by subclasses (i.e., a subclass of
X and Y should be able to be class Z(X,Y) or class(Y,Z) without screwing up
the args received by either X.__init__ or Y.__init__. An __init__ could
also safely del its own arguments once it has extracted/used them. In some
odd cases it might like to pass them along, perhaps even adding something
(like a None key to its sub-dictionary) to indicate that it has already
been called (yes, this sounds like weird - I'm just mentioning that it
would be possible). There's also no need for any special top-level
subclasses of object that simply ignore all their arguments (though you
_could_ add one if you wanted to be strict and insist that all __init__
methods del their args).

This approach adheres nicely to the explicit is better than implicit maxim.
I think the implicit situation is unsolvable.

I guess this could all be dressed up nicely using a metaclass that pulled
out any available args, made them available to the __init__ as regular
keyword args, and made available the special dictionary that holds
arguments for later classes (any given __init__ may want to add/alter
this). I don't know enough about metaclasses to know if this could be done
completely cleanly though. I'd probably prefer it all to be explicit and
manual.

Sorry for such a long post without much code. I can write some example
classes if the above is unclear or people think this worth looking at
further.

Terry


More information about the Python-Dev mailing list