initializing mutable class attributes

Alex Martelli aleaxit at yahoo.com
Thu Sep 2 03:13:56 EDT 2004


Alex Martelli <aleaxit at yahoo.com> wrote:
   ...
> > Anyway, in C++, if I write a library with a class like Class1, I can create
> > a default constructor to initialize all the members with default values if
> > there is no need for non-default values.  C++ gives me that possibility.  A
> > user of my library (who will never know me) can use this library and does
> > not need to know anything about the Class1 constructor.  This is consistent

Sure DOES "have to know something about Class1 constructor":
specifically that Class1 has a public default constructor, a crucial
piece of information.  How CAN you POSSIBLY assert otherwise?!

> > with the principle of encapsulation, so subclasses don't need to know
> > anything about how the superclass is implemented.
> 
> Encapsulation does not mean you can use a class without knowing its
> public methods (including constructors).  In particular, inheritance is
> a strong coupling, and the superclass must document if it's meant to be
> inherited from, with what constructors, what virtual methods allowable
> for override, and so on.

Incidentally, FAR from your assertion that, in C++, "subclasses don't
need to know anything about how the superclass is implemented", reality
is just the other way 'round.  Subclasses have to be _intimately
familiar_ with key aspects of superclasses' implementation -- not just
the interface (which fully includes the "little detail" of whether a
class has a default ctor or not!!!), but specifically _implementation_
issues, such as what private member names the superclass uses for its
own implementation purposes.  That's because C++ fatally tries to
support encapsulation via _accessibility_, NOT through _visibility_.  If
the subclass unwittingly tries using a member name which the superclass
is already using privately, it just won't compile; if the superclass
adds any private names to its implementation from one release to
another, it will "break all user code" which quite correctly tried to
use that name (and previously managed w/o problems!) for one of its
members -- as you say.  *THIS* is poor encapsulation (Python's attempt
to solve this issue with __-mangling doesn't work wonders, though it's
[a little] better than nothing -- but this should show that the issue of
coupling between superclass and subclass is far more serious than you
imply by asserting that it's solved by C++'s "implicit" rule that if no
other call to a superclass's ctor appears with a subclass's ctor, this
implies a call to the subclass's default ctor).


> > I think this IS a case where "implicit is better than explicit".
> 
> No way: by implicitly trying to call a parent class's default ctor in
   ...
> maybe I'll find the time to take a break and show you the custom
> metaclass you need to perpetrate this horror, since you state it's
> "better" than Python's explicitness -- no promises tho...

Here it is -- now feel free to damage yourself at will with your
conviction that this (calling superclass ctors) "IS a case" where
implicit is better.  I decided I can reuse this as a Cookbook recipe,
after all!-)

As an aside, note that, while Python doesn't _quite_ "give you enough
rope to shoot yourself in the foot" by default, it sure provides
excellent rope-manufacturing facilities, sufficient to riddle both of
your feet with bullets with the addition of *a few lines* of support
code...:

def _no_auto(obj): pass

class metaAutoInit(type):

    def __call__(cls, *a, **k):
        obj = cls.__new__(cls, *a, **k)
        for base in cls.mro():
            base.__dict__.get('__auto__', _no_auto)(obj)
        cls.__init__(obj, *a, **k)
        return obj

You could save a statement if you wished to have __auto__ methods called
after __init__ methods (if any) -- then, rather than the two statements,
one to call __new__ and make the empty obj (before calling __auto__
methods), the other after such calling to do the __init__, you could
make an already-inited obj with "obj=type.__call__(cls, *a, **k)" at the
start.  But I suspect one would want the auto-initialization to happen
before the explicit one, if any, so I showed how to do that.  Anyway, be
it 8 nonblack lines like here, or 7 otherwise, I hope you agree it's a
_trivially little amount of code_ for this amount of meta-functionality,
and the amount of damage (if I'm right) or benefit (if you're right) it
can bring to your Python usage.

In this recipe, __auto__ methods are called on classes in MRO order,
i.e., leaf first.  Don't like that, want reverse-MRO order?  Fine,
change the 'for' header to loop on cls.mro()[::-1], or, in Python 2.4,
on the more readable reversed(cls.mro()).

This metaclass is designed to silently tolerate base classes without
__auto__, treating such absence as an indication that a given class
wants no auto-initialization -- I think it's the only sensible choice,
but if you disagree, add to the metaclass a def __new__ where it screams
and shouts if a class belonging to it has no '__auto__' in its class
dictionary, or if that __auto__ has incorrect signature (check the
signature with the functions of standard library module inspect) --
better to do that at class-statement time than every time the class gets
instantiated (unless you suspect the user will delight in silently
deleting __auto__ entries from class objects at runtime, of course...).

Ah, one last note: we have to use base.__dict__ here, not getattr, else
a class without __auto__ might inherit __auto__ from a superclass and
that __auto__ would end up getting executed twice, which is probably not
what one normally wants.

Anyway, here's an example of using this custom metaclass:

if __name__ == '__main__':  # just show off a bit...
    __metaclass__ = metaAutoInit

    class a: pass

    class b:
        def __auto__(self): self.foo=[]

    class c(a):
        def __auto__(self): self.bar={}

    class d: pass

    class e(b, c, d):
        def __auto__(self): self.baz=c()

    z = e()
    print z.__dict__

This emits:

{'bar': {}, 'foo': [], 'baz': <__main__.c object at 0x58350>}


There, happy now?  Be as implicit as you desire, as long as I don't ever
have to be the one maintaining that code or consulting about it -- I've
served my term with C++, and finally emerged in the bright and fresh
land of Python, where simplicity, explicitnes, and clarity are VIRTUES.
(My wife agrees to the point that we had the Zen of Python among the
readings at our recent wedding, as I mentioned [with a pointer to the
URLs for our marriage readings & photos] in a recent thread...).


Alex



More information about the Python-list mailing list