initializing mutable class attributes

A

Anthony Baxter

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.

Use 'super()', in that case.

http://www.python.org/dev/doc/devel/lib/built-in-funcs.html#l2h-70
 
M

Mel Wilson

When learning something, I need to understand the 'WHY' behind a design
decision like that, when clearly there are alternatives. I will not be
satisfied with an explanation of "that's the Python way and you just have to
learn it". The explanation of choosing "explicit" instead of "implicit"
makes sense and helps me in better understanding Python. I have to mention
though that I do not accept "explicit is better than implicit" as an
absolute truth.

That's the "Zen" part. Cf. Neils Bohr's definition of a
Great Truth: a Great Truth is a proposition whose opposite
is also a Great Truth.
. [That's like when someone told me once with pride: "I
always design bottom-up."]

:)
A: I always design bottom-up. This is my latest application.
B: What is it?
A: I don't know.

We know where we stood the ladder, but aside from "up",
we can't say much about where it goes.
[ ... ]
You may see in another message I posted in this thread that I ended up
giving an answer to my own question (the 'WHY' question). Here is my reason
for that 'WHY'. Python does not have method overloading (and I am ok with
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 Java
have overloading and then can also mandate a default constructor for a
parent class, even if it's empty because you actually use only a non-default
constructor for that class. Python cannot request that you also implement a
default __init__ when you need a non-default one. There would be the
possibility of adding another special method, but there's already __init__
and __new__, so that would be too confusing.

I think the difference between:

class B (A):
def __init__ (self, ...)
.... B related stuff
A.__init__ (self, ...)


class B (A):
def __init__ (self, ...)
A.__init__ (self, ...)
.... B related stuff


class B (A):
def __init__ (self, ...)
.... B related stuff
A.__init__ (self, ...)
.... more B related stuff


shows that it's simplest just to code what you want done..
and that's without looking at multiple inheritance,
or different argument lists between A's and B's __init__.

Regards, Mel.
 
D

Dan Perl

My mistake. You're right, and it's all because of the inheritance from
'object', which probably defines a default, empty, __init__. I haven't made
a habit of subclassing from 'object' yet and I see that I'll have to do
that. Thanks.

Dan
 
D

Diez B. Roggisch

Dan said:
My mistake. You're right, and it's all because of the inheritance from
'object', which probably defines a default, empty, __init__. I haven't
made a habit of subclassing from 'object' yet and I see that I'll have to
do
that. Thanks.

You can safely remove the object as ancestor - it works with old-style
classes, too.
 
D

Dan Perl

Sorry, Anthony, but I don't think that is relevant for my example. I wasn't
referring to replacing the superclass with another one, which is where
'super( )' would have been useful because it detects the superclass only at
run-time. I was referring to changing the implementation of the superclass,
in which case 'super( )' doesn't make any difference.

See, however, a posting from Jorge Godoy, where he makes the great point
that the problem is removed by always subclassing the base class from
'object'. So the new-style class mechanism is taking care of this problem.

Dan
 
D

Dan Perl

I'm not sure what you mean by removing the object as ancestor, but here is a
modified code similar to Jorge's and it doesn't work:
class test:
pass

class derived(test):
def __init__(self):
test.__init__(self)

d = derived()

The call to test.__init__(self) raises an AttributeError exception.

Dan
 
A

Alex Martelli

Dan Perl said:
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;-)

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

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
 
D

Dan Perl

Alex Martelli said:
...
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.

We are arguing terminology. I was strictly referring to __init__, but you
are certainly right that Python 'constructors' are a more loose term.
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).


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.

You're right, in most cases that would be the right use. But my example was
just an example and it was just to enforce the earlier statement of "C++ and
Java have overloading and then can also mandate a default constructor for a
parent class."
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...

Thanks, but you don't have to bother. It's not something I need right away.
But I should learn that for myself so I will look at your previous posting
and other documentation that I'll find.

Dan
 
D

Diez B. Roggisch

I'm not sure what you mean by removing the object as ancestor, but here is
a modified code similar to Jorge's and it doesn't work:
class test:
pass

class derived(test):
def __init__(self):
test.__init__(self)

d = derived()

The call to test.__init__(self) raises an AttributeError exception.

I'm sorry, you're right - I meant to modify the example the way you did, to
check if it works or not. But for some reason I just made it run (which was
more work than expected due to embedded unicode spaces) and then forgot to
actually do what I wanted - remove the (object).

And then it doesn't work, as you correctly observed. Sorry for me beeing
confused.
 
D

Dan Perl

Mel Wilson said:
I think the difference between:

class B (A):
def __init__ (self, ...)
... B related stuff
A.__init__ (self, ...)


class B (A):
def __init__ (self, ...)
A.__init__ (self, ...)
... B related stuff


class B (A):
def __init__ (self, ...)
... B related stuff
A.__init__ (self, ...)
... more B related stuff


shows that it's simplest just to code what you want done..
and that's without looking at multiple inheritance,
or different argument lists between A's and B's __init__.

Very good point. I would usually assume that you should initialize the
superclass's attributes first, and then the subclass's. But that may not
always be the intent and I may just be influenced by my background in C++
and Java. And yes, with multiple inheritance, you definitely have to know
what attributes are defined in each superclass and you may have to choose
which __init__'s to use and in what order. It's not that C++ handles
multiple inheritance any better (that's one case where "implicit" can make
things worse). I used to work in a big company where it was actually a rule
not to use C++ multiple inheritance (which makes sense in a way because,
even if you use it correctly, someone else may take over your code later and
may have trouble with it, so a conservative company may not want to take a
risk with that). And Java simply solves the problem by not allowing
multiple inheritance (you can 'implement' several 'interfaces', but you can
'extend' only one 'class').

I guess that in Python, everything being public, you have to think of
attributes as being part of the public interface. So you have to know the
attributes of a superclass in order to inherit from it. After all, it's the
same thing with public data members in C++ and Java. But that's why those
languages recommend to make all data members non-public.

It's interesting to compare languages like that. There is an interesting
book, "An Introduction to Object-Oriented Programming", by Timothy Budd. I
read the 2nd edition, a long time ago. I see there is a 3rd edition now,
published in 2001. It explains OO concepts and compares them over several
languages. A little hard to read sometimes, but still interesting. I see
that even the 3rd edition does not cover Python. Maybe a 4th edition?

Dan
 
D

Dan Perl

After all this discussion, what do people think about this code?
class test(object):
def __new__(typ):
obj = object.__new__(typ)
obj.attr1 = 666
return obj

class derived(test):
def __init__(self):
self.attr2 = 111

d = derived()
print d.attr1, d.attr2
print isinstance(d, test)

The output:
666 111
True

At least it works. The 'test' superclass defines an instance attribute
attr1 and it initializes it with a default. The 'derived' subclass inherits
the attribute (implicitly, BTW ;-)) and that happens without an __init__ in
the superclass that would have to be invoked in the subclass.

Dan
 
D

Dan Perl

I found a small hole in the initial code suggestion and I fixed it. Try and
find the difference:
class test(object):
def __new__(typ, *args, **kwargs):
obj = object.__new__(typ)
obj.attr1 = 666
typ.__init__(obj, *args, **kwargs)
return obj

class derived(test):
def __init__(self, arg1, arg2):
self.attr2 = arg1
self.attr3 = arg2

d = derived(111, 222)
print d.attr1, d.attr2
print isinstance(d, test)

So, what do people think about it? Alex, would this be good for a recipe in
the Python Cookbook?

Dan
 
D

David Bolen

(...)
You're right, in most cases that would be the right use. But my example was
just an example and it was just to enforce the earlier statement of "C++ and
Java have overloading and then can also mandate a default constructor for a
parent class."

This point still confuses me, as does the statement it's supposed to
enforce. Your example failed to compile, and the error happened to
reference a missing default constructor, but how does that equate to
mandating such an implementation?

Since I can correct the problem simply by calling the existing
constructor properly from my subclass, clearly I am not _required_ to
implement a default constructor in the base class (which is how I'd
interpret the word "mandate").

C++ may be making an implicit use of such a constructor as part of its
assumptions (and complaining if it doesn't exist), but unless
implementing such a constructor is the only way to satisfy the
compiler, it's not a mandate or requirement that one be provided.

-- David
 
D

David Bolen

(...)
Very good point. I would usually assume that you should initialize the
superclass's attributes first, and then the subclass's. But that may not
always be the intent and I may just be influenced by my background in C++
and Java. (...)

I do find it reasonably common (particularly in GUI applications when
I'm subclassing standard GUI objects) to have a subclass that massages
the constructor inputs slightly (perhaps adding different defaults, or
filtering the arguments) before passing them on to the superclass.

In C++ you can sometimes manage this as long as the manipulation is
simple exclusion or addition that can be performed in the
initialization code prior to the start of the subclass constructor,
but that's very limited (and otherwise can end up forcing you into the
2-phase construction model). So I find Python's freedom of
controlling exactly when the superclass is initialized with complete
control over its arguments very liberating.

-- David
 
D

David Bolen

Dan Perl said:
After all this discussion, what do people think about this code?

On face value, I'd question why you're going through the extra effort.
I may be missing the point in such a small example, but just having an
__init__ in the base class that subclasses are supposed to call seems
simpler.

If you're just trying to find some way to have default attributes that can
be used from subclasses without calling __init__, I'd probably just use
class level definitions. For example:

- - - - - - - - - - - - - - - - - - - - - - - - -
Python 2.3.4 (#53, May 25 2004, 21:17:02) [MSC v.1200 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information..... attr1 = 666
.... .... def __init__(self):
.... self.attr2 = 111
.... - - - - - - - - - - - - - - - - - - - - - - - - -

-- David
 
D

Dan Perl

David Bolen said:
On face value, I'd question why you're going through the extra effort.
I may be missing the point in such a small example, but just having an
__init__ in the base class that subclasses are supposed to call seems
simpler.

If you're just trying to find some way to have default attributes that can
be used from subclasses without calling __init__, I'd probably just use
class level definitions. For example:

That is exactly what I'm trying to do, and in the initial posting of the
thread I was mentioning an initialization exactly like yours, but at the
same time I was pointing out that an initialization like that works only for
immutable attributes and not for mutable attributes, hence the need for the
__init__. Or doing it in __new__ as I showed in the new code.
- - - - - - - - - - - - - - - - - - - - - - - - -
Python 2.3.4 (#53, May 25 2004, 21:17:02) [MSC v.1200 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.... attr1 = 666
...... def __init__(self):
... self.attr2 = 111
...- - - - - - - - - - - - - - - - - - - - - - - - -

-- David
 
B

Bengt Richter

David Bolen said:
On face value, I'd question why you're going through the extra effort.
I may be missing the point in such a small example, but just having an
__init__ in the base class that subclasses are supposed to call seems
simpler.

If you're just trying to find some way to have default attributes that can
be used from subclasses without calling __init__, I'd probably just use
class level definitions. For example:

That is exactly what I'm trying to do, and in the initial posting of the
thread I was mentioning an initialization exactly like yours, but at the
same time I was pointing out that an initialization like that works only for
immutable attributes and not for mutable attributes, hence the need for the
__init__. Or doing it in __new__ as I showed in the new code.
- - - - - - - - - - - - - - - - - - - - - - - - -
Python 2.3.4 (#53, May 25 2004, 21:17:02) [MSC v.1200 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
class Test(object):
... attr1 = 666
...
class Derived(Test):
... def __init__(self):
... self.attr2 = 111
...
d = Derived()
print d.attr1, d.attr2 666 111
print isinstance(d, Test) True
- - - - - - - - - - - - - - - - - - - - - - - - -

-- David
Here's a way to auto-initialize an instance attribute to a mutable via a class variable
(that happens to be a descriptor ;-)
... class prop(object):
... def __get__(self, inst, cls=None):
... if inst: return inst.__dict__.setdefault('prop',[])
... return self
... def __set__(self, inst, value): inst.__dict__['prop'] = value
... def __delete__(self, inst): del inst.__dict__['prop']
... prop = prop()
...
>>> c=C()
>>> vars(c) {}
>>> c.prop []
>>> vars(c) {'prop': []}
>>> c.prop = 'other'
>>> vars(c) {'prop': 'other'}
>>> C.prop
>>> del c.prop
>>> vars(c) {}
>>> c.prop = 'not auto initialized'
>>> vars(c) {'prop': 'not auto initialized'}
>>> c.prop 'not auto initialized'
>>> del c.prop
>>> c.prop
[]

We can also use it as a base class:
... def __init__(self):
... self.attr2 = 111
...
>>> d = Derived()
>>> print d.prop, d.attr2 [] 111
>>> print isinstance(d, C) True
>>> vars(d) {'attr2': 111, 'prop': []}
>>> d.prop
[]

our descriptor is visible via the derived class too, as you would expect True

and so ...
>>> d.prop = 's/b d attribute'
>>> vars(d) {'attr2': 111, 'prop': 's/b d attribute'}
>>> d.prop 's/b d attribute'
>>> del d.prop
>>> vars(d) {'attr2': 111}
>>> d.prop []
>>> vars(d)
{'attr2': 111, 'prop': []}

Any use to you? (disclaimer: not tested beyond what you see here ;-)

Regards,
Bengt Richter
 
D

Dan Perl

Someone else (Shalabh) suggested descriptors for the same problem but I
didn't get to consider such a solution until now. Your posting gave me a
chance to do that. Thanks. I do think though that it is way too much for
just initializing the attributes without an __init__, especially because I
think now I can do it by overriding __new__.

Dan

Bengt Richter said:
David Bolen said:
After all this discussion, what do people think about this code?

On face value, I'd question why you're going through the extra effort.
I may be missing the point in such a small example, but just having an
__init__ in the base class that subclasses are supposed to call seems
simpler.

If you're just trying to find some way to have default attributes that can
be used from subclasses without calling __init__, I'd probably just use
class level definitions. For example:

That is exactly what I'm trying to do, and in the initial posting of the
thread I was mentioning an initialization exactly like yours, but at the
same time I was pointing out that an initialization like that works only for
immutable attributes and not for mutable attributes, hence the need for the
__init__. Or doing it in __new__ as I showed in the new code.
- - - - - - - - - - - - - - - - - - - - - - - - -
Python 2.3.4 (#53, May 25 2004, 21:17:02) [MSC v.1200 32 bit (Intel)]
on
win32
Type "help", "copyright", "credits" or "license" for more information.
class Test(object):
... attr1 = 666
...
class Derived(Test):
... def __init__(self):
... self.attr2 = 111
...
d = Derived()
print d.attr1, d.attr2
666 111
print isinstance(d, Test)
True

- - - - - - - - - - - - - - - - - - - - - - - - -

-- David
Here's a way to auto-initialize an instance attribute to a mutable via a class variable
(that happens to be a descriptor ;-)
... class prop(object):
... def __get__(self, inst, cls=None):
... if inst: return inst.__dict__.setdefault('prop',[])
... return self
... def __set__(self, inst, value): inst.__dict__['prop'] = value
... def __delete__(self, inst): del inst.__dict__['prop']
... prop = prop()
...
c=C()
vars(c) {}
c.prop []
vars(c) {'prop': []}
c.prop = 'other'
vars(c) {'prop': 'other'}
C.prop
del c.prop
vars(c) {}
c.prop = 'not auto initialized'
vars(c) {'prop': 'not auto initialized'}
c.prop 'not auto initialized'
del c.prop
c.prop
[]

We can also use it as a base class:
... def __init__(self):
... self.attr2 = 111
...
d = Derived()
print d.prop, d.attr2 [] 111
print isinstance(d, C) True
vars(d) {'attr2': 111, 'prop': []}
d.prop
[]

our descriptor is visible via the derived class too, as you would expectTrue

and so ...
d.prop = 's/b d attribute'
vars(d) {'attr2': 111, 'prop': 's/b d attribute'}
d.prop 's/b d attribute'
del d.prop
vars(d) {'attr2': 111}
d.prop []
vars(d)
{'attr2': 111, 'prop': []}

Any use to you? (disclaimer: not tested beyond what you see here ;-)

Regards,
Bengt Richter
 
A

Alex Martelli

Dan Perl said:
My mistake. You're right, and it's all because of the inheritance from
'object', which probably defines a default, empty, __init__. I haven't made
a habit of subclassing from 'object' yet and I see that I'll have to do
that. Thanks.

If you define several parent-less classes in a module, it may be simpler
to place at the module's start the assignment statement:

__metaclass__ = type

This is equivalent to explicitly specifying object as the base class of
any otherwise parent-less class.


Alex
 
A

Alex Martelli

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?!
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).

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,001
Messages
2,570,255
Members
46,852
Latest member
CarlaDowle

Latest Threads

Top