hmm, lets call it: generic __init__ problem

  • Thread starter =?ISO-8859-1?Q?paul_k=F6lle?=
  • Start date
?

=?ISO-8859-1?Q?paul_k=F6lle?=

Hi list,

in the course of writing a small app, I tried to design a class, which
would allow to derive its behaviour solely from its name, so that I
would be able to write one abstract class with the logic and get
different objects/instances by subclassing with appropriate names.
Consider the following:

ATTRS = {'one':['attr1', 'attr2'],
'two':['attr3','attr4'],
'three':['attr5','attr6'],
'four':['attr7','attr8']}


class one:
def __init__(self, *args, **kwargs):
## get allowed attributes...
for attr in ATTRS[self.__class__.__name__]:
self.__dict__[attr] = ''

## unknown attributes are silently ignored...
for item in kwargs.keys():
if self.__dict__.has_key( item ):
self.__dict__[item] = kwargs[item]
else:
pass
## init all parents...
parents = self.__class__.__bases__
if parents:
for i in range(len(parents)):
apply(parents.__name__.__init__,\ (self,)+args, kwargs)



class two(one):
def foo(self):
pass

class three(one):
def bar(self):
pass

class four(two, three):
def wiskey_bar(self):
pass

So running:
>>> o = funClass.one()
>>> dir(o) ['__doc__', '__init__', '__module__', '__str__', 'attr1', 'attr2']
>>> o.attr1
''

and: 'chicks'

but:
['__doc__', '__init__', '__module__', '__str__', 'attr7', 'attr8',
'bar', 'foo', 'wiskey_bar']

I expected to have all attrs from all parents initialized from:
parents = self.__class__.__bases__
if parents:
for i in range(len(parents)):
apply(parents.__name__.__init__,\ (self,)+args, kwargs)

in the __init__ method. But apparently I have misunderstood the
self.__class__.__bases__ thingy as it does not do what I want ;(

Apart from the obvious mistake I can't figure out:
1) Is this intelligent at all?
2) Is there a better way to do it?
3) How do you change tabwidth in mozilla mail?

thanks
Paul
 
P

Peter Otten

paul said:
in the course of writing a small app, I tried to design a class, which
would allow to derive its behaviour solely from its name, so that I
would be able to write one abstract class with the logic and get
different objects/instances by subclassing with appropriate names.
Consider the following:

[quotes as mutilated by my news reader :-(]
ATTRS = {'one':['attr1', 'attr2'],
'two':['attr3','attr4'],
'three':['attr5','attr6'],
'four':['attr7','attr8']}


class one:
def __init__(self, *args, **kwargs):
## get allowed attributes...
for attr in ATTRS[self.__class__.__name__]:
self.__dict__[attr] = ''

## unknown attributes are silently ignored...
for item in kwargs.keys():
if self.__dict__.has_key( item ):
self.__dict__[item] = kwargs[item]
else:
pass

Superfluous else: pass
## init all parents...
parents = self.__class__.__bases__
if parents:
for i in range(len(parents)):
apply(parents.__name__.__init__,\
(self,)+args, kwargs)


In current python:

for parent in parents:
parent.__name__.__init__(self, *args, **kwargs)

However, you are calling the __init__() method of a string (parent.__name__)
which is a noop as strings are immutable (I'm surprised it didn't fail).
I suppose you intended to call parent.__init__() which would result in
infinite recursion as you determine the class to operate on by
self.__class__ which will always be the same.

I think this is what you want:

ATTRS = {'one':['attr1', 'attr2'],
'two':['attr3','attr4'],
'three':['attr5','attr6'],
'four':['attr7','attr8']}

class one:
def __init__(self, **kwargs):
## get allowed attributes...
classes = [self.__class__]
while classes:
cls = classes.pop(0)
for attr in ATTRS[cls.__name__]:
self.__dict__[attr] = ''
classes.extend(cls.__bases__)

## unknown attributes are silently ignored...
for key, value in kwargs.iteritems():
if key in self.__dict__:
self.__dict__[key] = value

class two(one):
def foo(self):
pass

class three(one):
def bar(self):
pass

class four(two, three):
def wiskey_bar(self):
pass

params = dict(attr1="a", attr3="b", attr5="c", attr7="d")
for klass in [one, two, three, four]:
obj = klass(**params)
print klass.__name__
print "\t", [a for a in dir(obj) if not a.startswith("_")]
Apart from the obvious mistake I can't figure out:
1) Is this intelligent at all?

Is there a polite way of saying no?
Seriously, have a look at the tutorial to get uptodate with the current
python features, then set yourself a real task and try to solve it in the
simplest and cleanest manner. I am confident that you will *not* need
multiple inheritance or attributes with pointless default values.
IMHO silently ignoring anything is strongly against python's philosophy.
2) Is there a better way to do it?

I'm sure there would be if only I knew what *it* is meant to be.
3) How do you change tabwidth in mozilla mail?

No idea, but changing from tabs to spaces is urgent, as you can see from the
quotes.

Peter
 
?

=?ISO-8859-15?Q?paul_k=F6lle?=

Hi peter, thanks for your answer.

Peter said:
In current python:

for parent in parents:
parent.__name__.__init__(self, *args, **kwargs)

However, you are calling the __init__() method of a string (parent.__name__)
which is a noop as strings are immutable (I'm surprised it didn't fail).
I suppose you intended to call parent.__init__() which would result in
infinite recursion as you determine the class to operate on by
self.__class__ which will always be the same.
Exactly, but self.__class__.__bases__ gets a tuple with all parents and
I can't see why it is impossible to iterate over it. But you are right I
tried parent.__init__() before and it runs into the max recursion error.
I think this is what you want:
Thanks, this gives me an idea how it can be done.

<-- snipped working example for recursivly importing attributes from a
dictionary in the current namespace based on superclasses names -->

Is there a polite way of saying no?
Seriously, have a look at the tutorial to get uptodate with the current
python features, then set yourself a real task and try to solve it in the
simplest and cleanest manner.
To be honest, it is a "real world" (if there is any) task. The funClass
thing was just to isolate the problem. I'm writing an LDAP client and
the data model is made up of ObjectClasses which in turn have required
and optional attributes. ObjectClasses can inherit attributes from
"superior" ObjectClasses and the actual dataset (entry) may consist of
multiple ObjectClasses, collecting all their attributes.

I thought it would be a good idea to mimic that with classes in the
client code by naming the classes like the ObjectClasses and get the
required and allowed attributes of the instance from the server in
__init__ like:
r = self.schema.get_obj(ldap.schema.ObjectClass,\ self.__class__.__name__)
....process r...

This has to be done for every parent and the whole point was *not* to
write another __init__ for each subclass. So the question boils down to:

1) Do I have to call Super.__init__ explicitely in: Child(Super)'s
__init__ method ?

2) If 1): Since I do not want to write a new __init__ for every possible
subclass, how do I call Super.__init__ if I do not know Super's name?

I am confident that you will *not* need
multiple inheritance or attributes with pointless default values.
Sorry for the bad example.
IMHO silently ignoring anything is strongly against python's philosophy.
I can see no harm ignoring useless keyword arguments, hmm.. OK, it hides
possible errors...

thanks again
Paul
 
P

Peter Otten

paul said:
To be honest, it is a "real world" (if there is any) task. The funClass
thing was just to isolate the problem. I'm writing an LDAP client and
the data model is made up of ObjectClasses which in turn have required
and optional attributes. ObjectClasses can inherit attributes from
"superior" ObjectClasses and the actual dataset (entry) may consist of
multiple ObjectClasses, collecting all their attributes.

I thought it would be a good idea to mimic that with classes in the
client code by naming the classes like the ObjectClasses and get the
required and allowed attributes of the instance from the server in
__init__ like:
r = self.schema.get_obj(ldap.schema.ObjectClass,\ self.__class__.__name__)
...process r...

This has to be done for every parent and the whole point was *not* to
write another __init__ for each subclass. So the question boils down to:

1) Do I have to call Super.__init__ explicitely in: Child(Super)'s
__init__ method ?

Yes. (But there's a workaround, see below)
2) If 1): Since I do not want to write a new __init__ for every possible
subclass, how do I call Super.__init__ if I do not know Super's name?

super() comes to the rescue. If you follow c.l.py you'll notice that there
are pitfalls, though. http://www.python.org/2.2/descrintro.html#cooperation
might be useful.
.... class A(object):
.... def __init__(self, **kw):
.... super(A, self).__init__(**kw) # this does the magic
.... for a in attributes:
.... setattr(self, a, kw.get(a, ""))
.... return A
........ pass
....Traceback (most recent call last):

The makeClass() factory is not strictly needed. Just remember to derive
every base class from object and call

super(CurrentClass, self).__init__()

in its __init() method.


Peter
 
M

Michele Simionato

paul kölle said:
Hi list,

in the course of writing a small app, I tried to design a class, which
would allow to derive its behaviour solely from its name, so that I
would be able to write one abstract class with the logic and get
different objects/instances by subclassing with appropriate names.
Consider the following:

ATTRS = {'one':['attr1', 'attr2'],
'two':['attr3','attr4'],
'three':['attr5','attr6'],
'four':['attr7','attr8']}


class one:
def __init__(self, *args, **kwargs):
## get allowed attributes...
for attr in ATTRS[self.__class__.__name__]:
self.__dict__[attr] = ''

## unknown attributes are silently ignored...
for item in kwargs.keys():
if self.__dict__.has_key( item ):
self.__dict__[item] = kwargs[item]
else:
pass
## init all parents...
parents = self.__class__.__bases__
if parents:
for i in range(len(parents)):
apply(parents.__name__.__init__,\ (self,)+args, kwargs)


Uhm ... I am quite sure this could be done better, but it is not
completely clear what you are trying to do. The parents.__name__.__init__
is clearly bogus, and the loop over "i" makes me say "ickkk!".
Moreover, it is completely useless since even fixing it will generate
a recursion error (__init__ of one instance would call __init__ of
the *same* class). In principle, you would need "super", but in
this case you are probably better with "dir": dir automatically retrieves the
attributes from the parents. Here is an example:

ATTRS = {
'one': ['attr1', 'attr2'],
'two': ['attr3', 'attr4'],
'three':['attr5', 'attr6'],
'four': ['attr7', 'attr8']}


class one:
def __init__(self, *args, **kwargs):
for item in dir(self):
# a way to pass attributes from parents to self by hand
if item.startswith("__"):
pass # skip private and special names
else:
setattr(self,item,getattr(self,item)) #put in self.__dict__
for attr in ATTRS[self.__class__.__name__]:
self.__dict__[attr] = ''
for item in kwargs.keys():
if self.__dict__.has_key( item ):
self.__dict__[item] = kwargs[item]
else: #useless but may help readability
pass
class two(one):
def foo(self):
pass

class three(one):
def bar(self):
pass

class four(two, three):
def wiskey_bar(self):
pass

i1=one()
i2=two()
i3=three()
i4=four()

print dir(i1)

print dir(i2)

print dir(i3)

print dir(i4)

This is probably not what you want: by I suggest you consider
changing the attributes of the class with setattr(self.__class__,name,value)
if you want inheritance to work. The issue is that the dictionary of one
instance is local to the instance and it is not inherited, so you want to
change the dictionary of the class.
Does this helps?

Michele Simionato
 
?

=?ISO-8859-1?Q?paul_k=F6lle?=

Michele said:
This is probably not what you want:
It's not about importing attributes rather than having only one
definition of __init__ in one "metaclass" wich should behave differently
dependent on the name of the subclass (see my answer to Peter). I
found out I have to read lots of stuff to catch up with concepts of
new-style classes and metaclass design.

The solution using super() like Peter suggested is almost perfect
except the name of the class is still hardcoded inside of __init__.

one(object):
def __init__(self, *args, **kwargs):
super(one, self).__init__()
^^^^
r = self.s.get_obj(ldap.schema.ObjectClass, self.__class__.__name__)
...process r...

implies to write another __init__ for every subclass, since "one" wouldn
't match the subclass's name right? Instead of "one" I'd like to have
something that get's the current class at instantiation time. Is that
possible ?

thanks a lot for all your time and help.
Paul
 
M

Michele Simionato

paul kölle said:
The solution using super() like Peter suggested is almost perfect
except the name of the class is still hardcoded inside of __init__.

one(object):
def __init__(self, *args, **kwargs):
super(one, self).__init__()
^^^^
r = self.s.get_obj(ldap.schema.ObjectClass, self.__class__.__name__)
...process r...

implies to write another __init__ for every subclass, since "one" wouldn
't match the subclass's name right? Instead of "one" I'd like to have
something that get's the current class at instantiation time. Is that
possible ?

Yes, but it is quite non-trivial to get it right with the current
language.
I consider it to be a wart of super. See Guido's "autosuper" metaclass
and this post of mine for a solution:

http://groups.google.it/groups?hl=i...ca+con+Google&meta=group%3Dcomp.lang.python.*

Warning: it is not for the faint of heart ;)

Michele Simionato
 

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

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top