2017-04-10 1 views
0
  1. Consultez le code suivant:Pourquoi le déballage d'une chaîne avec un argument non itérable déclenche-t-il cette erreur?

    from itertools import chain 
    list(chain(42)) 
    

    Je passe un non-itérables comme argument pour chain et peu de surprise, je reçois exactement cette erreur:

    TypeError: 'int' object is not iterable 
    

    (passage à list n'est nécessaire que parce que chain n'évalue pas ses arguments avant l'itération réelle.)

  2. Si j'utilise correctement chain, je peux déballer le résultat comme argument de la fonction:

    from itertools import chain 
    foo = lambda x: x 
    foo(*chain([42])) 
    

    Cela va sans erreurs.

  3. Considérons maintenant la combinaison des deux cas ci-dessus, à savoir, une chaîne avec un argument non itérables décompressé comme arguments de la fonction:

    from itertools import chain 
    foo = lambda x: x 
    foo(*chain(42)) 
    

    Comme prévu, cela ne fonctionne pas. En Python 3, cela renvoie la même erreur que le premier cas. Cependant, en Python 2.7.12, l'erreur est jeté:

    TypeError: <lambda>() argument after * must be an iterable, not itertools.chain 
    

    Cela n'a pas de sens pour moi. itertools.chain est clairement un type itérable: isinstance(chain(42),collections.Iterable) donne True. En outre, il n'a pas causé de problème dans le second exemple. Je m'attendrais à un message d'erreur similaire dans le cas 2 ou Python 3. Quelle est l'explication de ce message d'erreur?

+0

Il est également intéressant de noter que 'chain (42)' ne lance pas d'erreur dans Python2 tant que vous n'essayez pas d'itérer sur le résultat. – iafisher

+0

@iafisher: C'est pourquoi j'ai enveloppé 'list' autour d'elle. Voir aussi mon édition. – Wrzlprmft

+0

* 'itertools.chain' est un type itérable * <- seulement quand vous l'avez passé. Garbage in, garbage out. – wim

Répondre

0

Le comportement que vous voyez est une tentative de donner une idée plus claire message d'erreur sur ce qui a mal tourné avec l'appel de fonction.

La façon dont Python 2.7 détermine si un objet est itérable consiste simplement à l'itérer, puis à intercepter l'exception TypeError si nécessaire. Il n'est pas réellement implémenté dans le code Python, mais c'est toujours ce qui se passe dans la gestion de la syntaxe de l'appel de la fonction. Note: cela n'a rien à voir avec lambda, et un vieux def aurait illustré l'exemple aussi bien.

L'appel de fonction est traitée dans CPython 2.7 par this C code:

static PyObject * 
ext_do_call(PyObject *func, PyObject ***pp_stack, int flags, int na, int nk) 
{ 
    ... snip ... 

     t = PySequence_Tuple(stararg); 
     if (t == NULL) { 
      if (PyErr_ExceptionMatches(PyExc_TypeError) && 
        /* Don't mask TypeError raised from a generator */ 
        !PyGen_Check(stararg)) { 
       PyErr_Format(PyExc_TypeError, 
          "%.200s%.200s argument after * " 
          "must be an iterable, not %200s", 
          PyEval_GetFuncName(func), 
          PyEval_GetFuncDesc(func), 
          stararg->ob_type->tp_name); 
      } 
      goto ext_call_fail; 

    ... snip ... 
} 

J'ai tronqué le code par souci de concision pour montrer le bloc concerné: les starargs sont réitérés dans un tuple, et si cela échoue avec PyExc_TypeError puis une nouvelle erreur est soulevée avec le type et le message correspondant à ce que vous avez vu.

En Python 3, le code d'appel de fonction C a été nettoyé et simplifié de manière significative. En fait la fonction ext_do_call n'existe même plus, elle a probablement été supprimée lors de l'implémentation de PEP 3113. Maintenant, l'exception d'itération d'une chaîne brisée monte en bulles non gérées. Si vous voulez fouiller dans le code d'appel actuel, vous pouvez commencer à creuser Python/ceval.c::do_call_core.