views:

1003

answers:

3

I get the following error when instantiating a Django form with a the constructor overriden:

__init__() got multiple values for keyword argument 'collection_type'

The __init__() function (shown below) is exactly as written this but with # code replaced with my logic. Asside from that I am essentially overriding the form's (which is a ModelForm) constructor.

def __init__(self, collection_type, user=None, parent=None, *args, **kwargs):
    # code
    super(self.__class__, self).__init__(*args, **kwargs)

The call that creates the error is shown here:

form = CreateCollectionForm(
    request.POST, 
    collection_type=collection_type, 
    parent=parent, 
    user=request.user
)

I cannot see any reason why the error is popping up.

EDIT: Here is the full code for the constructor

def __init__(self, collection_type, user=None, parent=None, *args, **kwargs):
 self.collection_type = collection_type
 if self.collection_type == 'library':
  self.user = user
 elif self.collection_type == 'bookshelf' or self.collection_type == 'series':
  self.parent = parent
 else:
  raise AssertionError, 'collection_type must be "library", "bookshelf" or "series"'
 super(self.__class__, self).__init__(*args, **kwargs)

EDIT: Stacktrace

Environment:

Request Method: POST
Request URL: http://localhost:8000/forms/create_bookshelf/hello
Django Version: 1.1.1
Python Version: 2.6.1
Installed Applications:
['django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'libraries',
 'users',
 'books',
 'django.contrib.admin',
 'googlehooks',
 'registration']
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware')


Traceback:
File "/Library/Python/2.6/site-packages/django/core/handlers/base.py" in get_response
  92.                 response = callback(request, *callback_args, **callback_kwargs)
File "/Library/Python/2.6/site-packages/django/contrib/auth/decorators.py" in __call__
  78.             return self.view_func(request, *args, **kwargs)
File "/Users/marcus/Sites/marcuswhybrow.net/autolib/libraries/forms.py" in     create_collection
  13.     form = CreateCollectionForm(request.POST,     collection_type=collection_type, user=request.user)

Exception Type: TypeError at /forms/create_bookshelf/hello
Exception Value: __init__() got multiple values for keyword argument 'collection_type'
+2  A: 

It's pretty simple: you pass request.POST and only afterward you put argument for collection_type. Onto what request.POST will be put? There is not place for that. Watch this:

In [8]: class A:
   ...:     def __init__(self, a, *args):
   ...:         print a, args
   ...:         
   ...:         

In [9]: A(None, a=None)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/gruszczy/Programy/logbuilder/<ipython console> in <module>()

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

Move request.POST somewhere else in the call, but remember, that named arguments come after the ones, that aren't.

gruszczy
surely all other arguments *are* named, thus request.POST must be passed in as the first argument?
Marcus Whybrow
@Marcus Whybrow: Yes, request.POST must be passed as the first argument, but the first argument declared for __init__ is collection_type (self doesn't count)
Ian Clelland
+7  A: 

You're passing the collection_type argument in as a keyword argument, because you specifically say collection_type=collection_type in your call to the form constructor. So Python includes it within the kwargs dictionary - but because you have also declared it as a positional argument in that function's definition, it attempts to pass it twice, hence the error.

However, what you're trying to do will never work. You can't have user=None, parent=None before the *args dictionary, as those are already kwargs, and args must always come before kwargs. The way to fix it is to drop the explicit definition of collection_type, user and parent, and extract them from kwargs within the function:

def __init__(self, *args, **kwargs):
    collection_type = kwargs.pop('collection_type', None)
    user = kwargs.pop('user', None)
    parent = kwargs.pop('parent', None)
Daniel Roseman
wow, that worked perfectly. Thanks very much!
Marcus Whybrow
Python will never include an argument in kwargs if it is declared as a formal parameter. Also, is is absolutely valid to have parameters with default values before the args tuple. Something else is going on here.
Ian Clelland
Yes I agree with you, although this simpler approach is a better approach in general, so I am content. A complicated implementation is always bound to throw hard to detect bugs into the mix.
Marcus Whybrow
A: 

Daniel Roseman's solution is to handle a mixture of *args and **kwargs better, but gruszczy's explanation is the correct one:

You have defined CreateCollectionForm.__init__ with this signature:

def __init__(self, collection_type, user=None, parent=None, *args, **kwargs)

And you are then calling it like this:

form = CreateCollectionForm(
    request.POST, 
    collection_type=collection_type, 
    parent=parent, 
    user=request.user
)

self is assigned implicitly during the call. After that, Python sees only one positional argument: request.POST, which is assigned as collection_type, the first parameter. Then, the keyword arguments are processed, and when Python sees another keyword argument names collection_type, then it has to throw a TypeError.

Daniel's solution is a good one, by removing all named parameters, it is much easier to handle things like this, and to pass them up through super() to higher-level constructors. Alternately, you need to make the post dictionary the first formal parameter to your __init__ method, and pass it up to the superclass.

Ian Clelland
I see, to be honest I did not want to touch the request.POST due to my knowledge of all the contributing factors not being top-notch. Thanks for clarifying gruszczy's answer, although I feel I should leave Daniel Roseman's as the primary answer, as it provides a direct solution to my specific problem, would that be the correct protocol?
Marcus Whybrow
I think that it would be. If you are using super() in a constructor, that means that you recognize that your constructor could be called as part of a chain of calls, and it is generally best to only use *args and **kwargs.
Ian Clelland