Getting an __init__'s parameter list

7 views
Skip to first unread message

Richard Lewis

unread,
Nov 12, 2009, 11:04:20 AM11/12/09
to Cambridge and East Anglian Python Users Group
Hi there,

I have a funny little setup where, in a class's constructor, I need to
create another instance of that class (or some class derived from it)
and store that instance as a property of the object being created. The
most obvious solution (I think) would be just to call self.__class__
(self, a, b, c) but this won't work if the parameter list of a derived
class's __init__ method is different to the base class.

So the solution I'm attempting involves getting the parameter name
list of self.__class__.__init__ using inspect.getargspec and then
building a dictionary mapping the values from locals() to those names
and passing it to a self.__class__(self, **the_dict) call to construct
the 'copied' (sort of) object:

import inspect

class Base:
def __init__(self, a, b, c):
# create a instance of the same class which is being
# constructed and store it as a property of this object
virtual_init_arg_names = inspect.getargspec
(self.__class__.__init__)[0]
virtual_init_args = dict([(arg_name, locals()[arg_name]) for
arg_name in virtual_init_arg_names if arg_name != 'self'])
print virtual_init_args
self.like_me = self.__class__(self, **virtual_init_args)

class Derived1(Base):
def __init__(self, a, b):
Base.__init__(self, a, b, c='auto')

class Derived2(Base):
def __init__(self, a):
Base.__init__(self, a, b='auto', c='auto')

b = Base('x', 'y', 'z')
d1 = Derived1('i', 'j')
d2 = Derived2('n')


However, the call to self.__class__(self, **virtual_init_args) above
raises the following exception:

TypeError: __init__() got multiple values for keyword argument 'a'

and I can't work out why.

Can anyone either a) tell me what this error is about, or b) think of
a better way of doing what I'm trying to do.

Best,
Richard

David Guaraglia

unread,
Nov 12, 2009, 12:02:39 PM11/12/09
to cam...@googlegroups.com
To quote Chris Rock talking about Michael Jackson "<racial epithet> is you crazy???".

Why aren't you using *args and **kwargs? You could declare __init__ like:

def __init__(self, *args, **kwargs)

and then call the constructor like:

self.__class__(*args, **kwargs)

I fail to see how you'd avoid infinite recursion (or at least as much recursion as Python will allow), but I suppose you are using some condition to check that.

Cheers,
David

P.S. I think what you might want to use is "super(self.__class__, self).__init__()" in the subclasses instead of using the base class directly.


--

You received this message because you are subscribed to the Google Groups "Cambridge and East Anglian Python Users Group" group.
To post to this group, send email to cam...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/campug?hl=.



Tom Lynn

unread,
Nov 12, 2009, 3:48:28 PM11/12/09
to Cambridge and East Anglian Python Users Group
Richard Lewis wrote:
> Hi there,
>
> I have a funny little setup where, in a class's constructor, I need to
> create another instance of that class (or some class derived from it)
> and store that instance as a property of the object being created. The
> most obvious solution (I think) would be just to call self.__class__
> (self, a, b, c) but this won't work if the parameter list of a derived
> class's __init__ method is different to the base class.

So before you create a Foo, you have to create a Foo? That sounds a
lot like dodgy design, but...

import re

class Foo(object):
def __init__(self, factory=None, depth=None):
if factory is None:
factory = self.__class__
if depth is None:
depth = 1
if depth < 3:
self.other = factory(depth=depth+1)
else:
self.other = EndFoo()

class EndFoo(Foo):
def __init__(self, depth=None):
self.other = self

class Bar(Foo):
def __init__(self, name, depth=None):
def factory(depth):
new_name = '%s%d' % (re.sub(r'\d+\Z', '', name), depth)
return Bar(new_name, depth=depth)
super(Bar, self).__init__(factory=factory, depth=depth)
self.name = name

bar = Bar('bar')
assert bar.name=='bar'
assert bar.other.name=='bar2'
assert bar.other.other.name=='bar3'
assert isinstance(bar.other.other.other, EndFoo)

Tom

David Guaraglia

unread,
Nov 12, 2009, 5:15:11 PM11/12/09
to cam...@googlegroups.com
BTW, re-reading my previous e-mail I sound really rude and cocky. Sorry for that, was meant to be funny, but it seems omitting emoticons makes a *huge* difference!

Again, sorry for that :)

Cheers,
David

Toby White

unread,
Nov 12, 2009, 6:32:19 PM11/12/09
to cam...@googlegroups.com
The error message is telling you the truth: you are providing multiple
values for the keyword argument 'a'.

You're calling :

self.__class__(self, **virtual_init_args)

which will generate a new instance of self.__class__, and call
__init__ on it as a bound method; with self already implicitly
referenced. What you're doing is equivalent to invoking:

Base(self, a=x, b=y, c=z)

but the signature of the Base.__init__ is
Base(a, b, c)

So you're assigning "self" to a (the first argument of the bound
method) and then also providing "a" in virtual_init_args.

----

Removing the self-as-first argument does what you're trying to do -
but immediately drops you into an infinite loop. This is not
surprising, because now you've got cls.__init__ calling itself
directly, with no termination condition on the recursion.

----

I can't really tell you how to fix it without knowing more about what
you're actually trying to do.

I'd guess you maybe want to be looking at overriding __new__ somehow
as well, but without further details I don't know.

Toby

Richard Lewis

unread,
Nov 13, 2009, 5:20:52 AM11/13/09
to Cambridge and East Anglian Python Users Group
OK, thanks for the replies.

(My example was, of course, abstracted from the real problem I'm
dealing with. I have a database which allows the clients a data entry
process with collaboration and workflow, editing each record can take
several days. So the database has "pending" versions of each table. I
was in the process of refactoring my ORM code a bit to make the Python
interface to the pending version of the table a property of the main
table itself. Because there's nothing technically different about the
pending versions of tables (they're only semantically different), I
can use my Table class for them. So I ended up with a situation where
I wanted the Table constructor to create another instance of the Table
class to store its respective pending table.)

Anyway, I've reneged on that particular bit of refactoring and gone
back to storing the pending tables as siblings of the main tables,
both being properties of a containing object.

Apologies for the slightly wacky problem.

Best,
Richard

David Guaraglia

unread,
Nov 13, 2009, 6:22:44 AM11/13/09
to cam...@googlegroups.com
Hi Richard,

Well, maybe you can still use that approach, but I'd keep the construction of the sibling Table object *outside* of the constructor, by either using the Factory Method pattern or using a complete different class altogether to handle the construction of the objects.

Cheers,
David


Best,
Richard

Reply all
Reply to author
Forward
0 new messages