2009-12-21 4 views
20

je reçois l'erreur suivante lors de l'instanciation un formulaire Django avec un constructeur overriden:erreur Django: obtenu plusieurs valeurs pour argument mot-clé

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

La fonction __init__() (ci-dessous) est exactement comme écrit cela, mais avec # code remplacé par ma logique. À partir de là, je substitue essentiellement le constructeur du formulaire (qui est un ModelForm).

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

L'appel qui crée l'erreur est montré ici:

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

Je ne vois aucune raison pour laquelle l'erreur est popping up.

EDIT: Voici le code complet pour le constructeur

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' 
+0

... donc vous êtes sûr que ce n'est pas la section #code que vous avez omise? Plus précisément ce que vous faites avec collection_type? – EMiller

+0

J'ai ajouté le code complet des constructeurs. –

+0

Quel est le type de l'erreur? Pourriez-vous poster l'ensemble de la pile? – gruszczy

Répondre

42

Vous passez l'argument collection_type en tant qu'argument mot-clé, car vous indiquez spécifiquement collection_type=collection_type dans votre appel au constructeur de formulaire. Donc Python l'inclut dans le dictionnaire kwargs - mais comme vous l'avez également déclaré comme argument positionnel dans la définition de cette fonction, il tente de le doubler, d'où l'erreur.

Cependant, ce que vous essayez de faire ne fonctionnera jamais. Vous ne pouvez pas avoir user=None, parent=Noneavant le dictionnaire *args, car ceux-ci sont déjà kwargs, et les arguments doivent toujours venir avant kwargs. La façon de le corriger est de laisser tomber la définition explicite de collection_type, utilisateur et parent, et les extraire de kwargs dans la fonction:

def __init__(self, *args, **kwargs): 
    collection_type = kwargs.pop('collection_type', None) 
    user = kwargs.pop('user', None) 
    parent = kwargs.pop('parent', None) 
+0

wow, cela a fonctionné parfaitement. Merci beaucoup! –

+3

Python n'inclura jamais un argument dans kwargs s'il est déclaré en tant que paramètre formel. En outre, il est absolument valide d'avoir des paramètres avec des valeurs par défaut avant le tuple args. Quelque chose d'autre se passe ici. –

+0

Oui Je suis d'accord avec vous, même si cette approche plus simple est une meilleure approche en général, alors je suis content. Une implémentation compliquée est toujours liée à la difficulté de détecter les bogues dans le mixage. –

9

Il est assez simple: vous passez request.POST et que vous mettez ensuite l'argument pour collection_type. Sur quelle demande.POST sera mis? Il n'y a pas de place pour ça. Regardez ceci:

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' 

request.POST Déplacer ailleurs dans l'appel, mais rappelez-vous, que les arguments nommés viennent après ceux, qui ne sont pas.

+0

sûrement tous les autres arguments * sont * nommés, donc request.POST doit être passé en tant que premier argument? –

+0

@Marcus Whybrow: Oui, request.POST doit être passé comme premier argument, mais le premier argument déclaré pour __init__ est collection_type (self ne compte pas) –

7

solution de Daniel Roseman est de gérer un mélange de *args et **kwargs mieux, mais des années gruszczy explication est correcte:

vous avez défini CreateCollectionForm.__init__ avec cette signature:

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

Et vous êtes alors appeler comme ceci:

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

self est implicitement affecté pendant l'appel. Après cela, Python ne voit qu'un seul argument de position: request.POST, qui est affecté comme collection_type, le premier paramètre. Ensuite, les arguments des mots-clés sont traités, et quand Python voit un autre noms d'argument de mot-clé collection_type, alors il doit lancer une erreur de TypeError.

La solution de Daniel est bonne, en supprimant tous les paramètres nommés, il est beaucoup plus facile de gérer de telles choses, et de les passer par super() aux constructeurs de plus haut niveau. Alternativement, vous devez faire du dictionnaire post le premier paramètre formel de votre méthode __init__, et le passer à la superclasse.

+0

Je vois, pour être honnête, je ne voulais pas toucher à la demande.POST en raison de ma connaissance de tous les facteurs contributifs n'étant pas de premier ordre. Merci d'avoir clarifié la réponse de Gruszczy, bien que je pense que je devrais laisser Daniel Roseman comme réponse principale, car elle fournit une solution directe à mon problème spécifique, serait-ce le bon protocole? –

+0

Je pense que ce serait. Si vous utilisez super() dans un constructeur, cela signifie que vous reconnaissez que votre constructeur peut être appelé dans le cadre d'une chaîne d'appels, et il est généralement préférable de n'utiliser que * args et ** kwargs. –

Questions connexes