initializing mutable class attributes

Alex Martelli aleaxit at yahoo.com
Wed Sep 1 18:25:29 CEST 2004


Dan Perl <dperl at rogers.com> wrote:
   ...
> > > I can imagine there can be very good languages, with their own uses and
> > > advantages, if the reverse principle were consistently used.
   ...
> > even small languages grow, if they're successful.  Do you have specific
> > examples in mind of very good languages based on "implicit is better
> > than explicit"?
> 
> No, I do not have an example, but I still stand by my comment.  Maybe it's
> just impossible to use the principle "implicit is better than explicit"
> consistently because you need at least something explicit.

Well, you can imagine anything, including many impossible things, so I
guess your original comment is impossible to falsify -- but that very
solidity makes it rather weak in information content;-)


> > > that, because it comes with the dynamic typing), so you cannot have a
> > > default constructor and a non-default one at the same time.  C++ and
   ...
> > Not with the same name.  You can have all alternative constructors you
> > want if you give them different names -- that's a popular use of
   ...
> We may have a disagreement in terminology, but I was using "overloading" in
> the C++/Java sense, which means strictly samename, different signatures.

Nevertheless, the observation "you cannot have a default constructor and
a non-default one at the same time" is not correct.  If you want to
insist that the constructors MUST all be named __init__, otherwise
they're not constructors but "methods which construct but we cannot name
constructors because constructors must be named __init__", go ahead, but
then, again, we are at an information-free tautology.  My point is: you
can have any number of "methods which construct" (constructants? I think
calling them constructors is more idiomatic English!-), just by choosing
the same number of names, one each.  So drawing any conclusion from a
premise that "you cannot have a default constructor and a non-default
one at the same time" is faulty reasoning -- the premise is false (by
any meaningful interpretation of the terms in it) so you can prove
anything from it.  You may want to try and correctly deduce something
from the true assertion -- "if you have a default constructor and a
non-default one they must have two different names" -- but I don't think
anything necessarily follows from this one.


> > Java and C++ _could_ mandate a default ctor, but they don't -- it would
> > be a very bad design decision for them to do so, and their designers
> > aren't THAT bad:-).  It's perfectly possible to have a class that just
> > CANNOT be constructed without some specific arguments.
> 
> Try this C++ code:
>     #include <iostream>
>     class Class1 {
>     public:
>         Class1(int arg )
>             {
>             std::cout << "Class1 Constructor"  << std::endl;
>             }
>     };
>     class Class2 : public Class1 {
>     public:
>         Class2(int arg)
>             {
>             std::cout << "Class2 Constructor"  << std::endl;
>             }
>     };
>     int main(int argc, char **argv)
>         {
>         Class2 c2 = Class2(9);
>         }
> 
> The compiler (I tried gcc) will give an error that the Class1::CLass1( )
> constructor is missing.

Sure, this C++ code is faulty.  The way gcc diagnoses that error is
debatable; I would say the fault is more likely to be a missing
" :Class1(someinteger) " between the "Class2(int arg)" and the following
open-brace.  I've made my life for years in a job where one of my main
tasks was helping hundreds of programmers write good C++, I've seen this
error often, and the "solution" of adding to Class1 a constructor
callable without arguments was very rarely the right one; almost
invariably those mandatory arguments in Class1's constructor _WERE_
indeed logically necessary -- the class just could not make instances
with meaningful internal state (meeting not just the class invariants,
but the "business purpose" of the class) without those arguments.  The
solution was (if the inheritance between concrete classes just could not
be removed in favour of preferable idioms such as Martin's Dependency
Inversion Principle) to ensure subclasses specified the needed arguments
for their superclasses -- that's exactly what C++'s syntax

Class2(int arg): Class1(someint) { ... }

is _for_.  ("two-phase constructor" and other solutions also emerge
here).

In brief: given Class1 as it is, you have to call Class1's constructor
explicitly in Class2's constructor, just like in Python (but with far
less flexibility, because it needs to happen _before_ Class2's
constructor body executes -- whence possible needs for two-phase
constructor and the like).

>  Without the non-default Class1 constructor, the
> compiler would have created a default constructor, IMPLICITLY.  Note that
> even C++ chooses not to create the implicit, default, constructor anymore if
> you have a non-default constructor.  You have to do it explicitly, even if
> it's an empty one.

Or more likely, you have to call Class1's constructor explicitly from
Class'2 constructor.  But sure, C++ does have more implicit, "black
magic" behavior "behind the scenes" than Python in quite a few aspects
(though not an unbounded amount) -- the implicit no-arguments do-nothing
constructor is NOT an example, because the result is exactly the same
you get in Python if you have no __init__ at all.

 
> 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
> 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.

> Not in Python.  A user of my library has to invoke the parent's class
> __init__ in their own __init__.  What happens if, in a future release, I get
> rid of the __init__ in the parent class?  Or the other way around.  An early
> release does not have a parent __init__, the users don't invoke it because
> they can't, and then, in a future release, I add the parent __init__ because
> I added some attributes.  It breaks all the users' code.  This is poor
> encapsulation.

What (public or protected, in C++) constructors your class has, and with
what arguments, is part of your class's public interface -- of _course_
it's going to break the code of anybody who uses that interface, if you
change the interface between releases.

This is also true in C++, of course.  If you want to follow the popular
convention of making all your classes 'canonic' -- all purvued of
default ctor, copy ctor, virtual dtor, default assignment -- you can,
but if you make that choice and publish it (and you'd better publish it,
otherwise how are users of your library going to take any advantage from
its existence?!) then you need to stick with it forevermore or break
users' code.  Similarly, in Python, if you want to guarantee to users
that all your classes have an __init__ which may be called without
arguments, nothing at all stops you from doing that -- and then you need
to stick to that published interface aspect, of course.

There are many ways in Python in which you can guarantee that all your
classes have an __init__ which can be called without arguments.  If you
need __init__ to be also callable WITH arguments, then make the
arguments optional and check whether they are there -- or give them all
default values just like you could do in C++ (in C++ the default ctor is
not necessarily one "without arguments" -- it can also have arguments as
long as they all have default values).  Or go for clarity and make
__init__ argument-less, using classmethods for all other constructors,
as you'd doubtlessly do in Smalltalk, for example.  The real problem is
distorting all of your design to ensure all your classes can have
instances with a sensible initial state when instantiated without
arguments -- I think that's far too heavy a price to pay.

Consider for example a Person class.  You know all persons have a SSN
(Social Security Number), so how can you instantiate Person without
making an SSN argument mandatory?   Basically only by inventing a weird
state for Person instances which is "not fully initialized yet because
the SSN is not known" -- and then ALL methods which could normally count
on the class invariant "instance has a valid SSN" cannot count on that
class invariant any more, must check and contort everything in sight.
What a horrid way to program.  It is quite common to get into this bind
if you adhere to the religion of giving EVERY class a default ctor,
because semantically significant classes as they come from a good design
will OFTEN have aspects that it just makes no business-logic sense to
"default" -- so you end up with flags &c to say "this is a PhoneNumber
class instance but it doesn't actually have a number so you can't use it
yet until it's fully initialized", "this is a MailingAddress class
instance but [ditto]", and so on ad nauseam.  No thanks!

 
> 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
any subclass's ctor, C++ is encouraging you to make default ctors
everywhere, and far too often it's better not to have them.  Make ctor
calls explicit and live happily.

I've already explained how you can simulate C++'s behavior, and indeed
get even MORE implicit if you wish, and I think it would be a disaster
to USE such a custom metaclass; but if you're sincere in saying it's
better then you should be KEEN to use it.  I'm supposed to do other
things this evening (changing some scripts to generate DocBook rather
than XHTML from some semantic-level markup -- fun, fun, fun...;-) but
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...


Alex



More information about the Python-list mailing list