2017-04-26 4 views
2

Après je réussir peu par écrit un module minimalistic d'extension Python3.6 en C++ (see here) Je prévois de fournir un module Python qui fait la même chose que la fonction Python suivante iterUniqueCombos():Comment écrire du code C (d'un module Python) capable de renvoyer un objet Python itérateur?

def iterUniqueCombos(lstOfSortableItems, sizeOfCombo): 
    lstOfSortedItems = sorted(lstOfSortableItems) 
    sizeOfList = len(lstOfSortedItems) 

    lstComboCandidate = [] 

    def idxNextUnique(idxItemOfList): 
     idxNextUniqueCandidate = idxItemOfList + 1 

     while (
       idxNextUniqueCandidate < sizeOfList 
        and 
       lstOfSortedItems[idxNextUniqueCandidate] == lstOfSortedItems[idxItemOfList] 
     ): # while 
      idxNextUniqueCandidate += 1 

     idxNextUnique = idxNextUniqueCandidate 

     return idxNextUnique 

    def combinate(idxItemOfList): 
     if len(lstComboCandidate) == sizeOfCombo: 
      yield tuple(lstComboCandidate) 
     elif sizeOfList - idxItemOfList >= sizeOfCombo - len(lstComboCandidate): 
      lstComboCandidate.append(lstOfSortedItems[idxItemOfList]) 
      yield from combinate(idxItemOfList + 1) 
      lstComboCandidate.pop() 
      yield from combinate(idxNextUnique(idxItemOfList)) 

    yield from combinate(0) 

J'ai quelques base compréhension de la programmation Python et C++, mais absolument aucune idée de la façon de "traduire" Pythons céder dans le code C++ d'un module d'extension Python. Donc, ma question est:

How to write C++ code (of a Python module) able to return a Python iterator object?

Toutes les astuces pour me lancer sont les bienvenues.

MISE À JOUR (état 07.05.2017):

deux le commentaire: rendement n'a pas C++ équivalent. Je commencerais par implémenter le protocole itérateur manuellement en Python, pour sortir du rendement et du rendement de l'état d'esprit. - user2357112 Avr 26 à 1:16 et l'indice dans la réponse par dannyla réponse à cette question revient à demander 'Comment implémenter un itérateur sans utiliser yield' mais dans une extension C++ au lieu du pur Python. mettre mes efforts de programmation dans la mauvaise direction de réinventer la roue en réécrivant le code des algorithmes afin d'éliminer yield et en écrivant le code C d'un module d'extension Python à partir de zéro (ce qui a entraîné pleut Segmentation Fault erreurs).

The state-of-the-art of my current knowledge on the subject of the question is that using Cython it is possible to translate the above Python code (which is using yield) directly into C code of a Python extension module.

Ceci est non seulement possible en utilisant le code Python comme il est (sans qu'il soit nécessaire de tout réécrire), mais en plus que la vitesse du module d'extension créée par Cython de l'algorithme en utilisant yield fonctionne à au moins deux fois plus vite que le module d'extension créé à partir d'une classe itérative utilisant __iter__ et __next__ algorithme réécrit (ce dernier est valide si aucun code d'optimisation de vitesse spécifique à Cython n'est ajouté au script Python).

+1

Je commencerai par [implémenter le protocole itérateur manuellement] (https: //www.python.org/dev/peps/pep-0234 /) en Python, pour sortir de l'état d'esprit 'yield' et' yield from'. – user2357112

Répondre

1

Ceci est plus une réponse à votre édition de question qu'une réponse complète - Je suis d'accord avec l'essentiel de la réponse de Danny, que vous devez implémenter dans une classe une méthode __next__/next (en fonction de la version de Python). Dans votre édition, vous affirmez que cela doit être possible parce que Cython peut le faire.Je pensais que ça valait la peine de voir exactement comment Cython le fait.

Commencez par un exemple de base (choisi parce qu'il a quelques différentes déclarations yield et une boucle):

def basic_iter(n): 
    a = 0 
    b = 5 
    yield a 
    a+=3 
    yield b 
    b+=2 

    for i in range(n): 
     yield a+b+n 
     a = b+1 
     b*=2 
    yield 50 

La première chose Cython fait est de définir une classe __pyx_CoroutineObject C avec une méthode __Pyx_Generator_Next qui implémente __next__/next. Quelques attributs pertinents du __pyx_CoroutineObject:

  • body - un pointeur de fonction C qui implémente la logique que vous avez défini.
  • resume_label - un entier utilisé pour rappeler à quel point vous avez dans la fonction définie par body
  • closure - une coutume créée classe C qui stocke toutes les variables utilisées dans body.

D'une manière légèrement rond-point, __Pyx_Generator_Next appelle l'attribut body, qui est la traduction du code Python que vous avez défini.

Voyons maintenant comment fonctionne la fonction affectée à body - dans le cas de mon exemple appelé __pyx_gb_5iters_2generator. La première chose qu'il fait est d'utiliser resume_label pour passer à la déclaration yield droite:

switch (__pyx_generator->resume_label) { 
    case 0: goto __pyx_L3_first_run; 
    case 1: goto __pyx_L4_resume_from_yield; 
    case 2: goto __pyx_L5_resume_from_yield; 
    case 3: goto __pyx_L8_resume_from_yield; 
    case 4: goto __pyx_L9_resume_from_yield; 
    default: /* CPython raises the right error here */ 
    __Pyx_RefNannyFinishContext(); 
    return NULL; 
    } 

Les affectations de variables se font à travers la structure closure (localement appelée à __pyx_cur_scope:

/*  a = 0    # <<<<<<<<<<<<<< */ 
__pyx_cur_scope->__pyx_v_a = __pyx_int_0 

yield définit la resume_label et renvoie (avec resume_label vous permettant de revenir directement la prochaine fois):

__pyx_generator->resume_label = 1; 
return __pyx_r; 

La boucle est légèrement plus compliquée mais fondamentalement la même chose - elle utilise goto pour sauter dans la boucle C (ce qui est légal).

Enfin, une fois qu'il a atteint la fin, il déclenche une erreur StopIteration:

PyErr_SetNone(PyExc_StopIteration); 

En résumé, Cython fait exactement ce que vous avez conseillé de le faire: il définit une classe avec __next__ ou next méthode et utilise cette classe pour garder une trace de l'état. Parce qu'il est automatisé, il est assez bon à suivre le comptage des références et évite ainsi les erreurs Segmentation Fault que vous avez rencontrées. L'utilisation de goto pour revenir au point d'exécution précédent est efficace, mais nécessite des précautions.

Je peux voir pourquoi réécrire votre fonction de générateur C en termes d'une seule fonction __next__/next est peu attrayante et Cython offre clairement un moyen simple de le faire sans écrire C vous, mais il n'utilise pas de techniques spéciales faire la traduction en plus de ce qu'on vous a déjà dit. `Yield` n'a pas d'équivalent C++

+0

Vous écrivez: 'Cython offre clairement une façon simple de le faire sans écrire C vous-même, mais il n'utilise pas de techniques spéciales pour faire la traduction en plus de ce que vous avez déjà dit.» Et en même temps (si j'ai bien compris votre réponse), expliquez la technique spéciale que Cython utilise pour traduire Python 'yield' en un équivalent C approprié au-dessus de ce qu'on m'a dit: en utilisant un commutateur pour les instructions' goto' où l'état de l'itération est maintenu dans une structure spéciale de «fermeture». – Claudio

+1

Vous le comprenez bien. Je pense que mon point de vue était que vous ne pouviez pas écrire vous-même. Je suppose que si vous avez donné le problème d'écrire une fonction qui peut être appelée plusieurs fois pour fonctionner comme un générateur Python vers un programmeur C, ils trouveraient probablement quelque chose de similaire (peut-être éviter 'goto' ...). Quoi qu'il en soit, j'ai surtout pensé que ça valait la peine de regarder comment Cython le fait. Il montrera soit un mécanisme utile à copier, ou vous convaincra que vous préférez ne pas l'écrire vous-même et vous devriez juste utiliser Cython (les deux sont des résultats ok) – DavidW

+0

Dans ce contexte, il serait certainement intéressant de savoir comment le L'interpréteur Python le fait dans son propre code C. Je ne serais pas surpris si Cython ne fait que copier comment Python le fait lui-même. – Claudio

1

Un itérateur dans le python est une forme particulière d'un générateur et est mis en oeuvre par une des méthodes contenant classe __iter__ et next__iter__ rendements self et next rendements de chaque valeur, à son tour, ce qui soulève StopIteration sur la fin de l'itération - see PEP.

Pour fournir un équivalent C++, le code C++ doit implémenter ces mêmes fonctions python pour se conformer au protocole. Le type d'extension résultant est un itérateur.

En d'autres termes, la réponse à cette question est la même chose que demander « Comment puis-je mettre un itérateur sans utiliser yield » mais dans un C++ au lieu de l'extension Python pur. Il existe plusieurs réponses à ce problème sur le débordement de pile.

NB - next est __next__ sur Python 3.

+0

La réponse répond directement à la question de savoir comment implémenter un itérateur de manière beaucoup plus détaillée que le commentaire disant 'implémenter le protocole décrit dans PEP ici'. Par conséquent, je pense que c'est plus clair et plus approprié. Si votre question n'est plus applicable ou si vous pensez qu'elle a été couverte ailleurs, pensez à la verrouiller/la mettre à jour en indiquant comme telle. – danny

+0

Voir la dernière ligne en réponse. – danny

+0

Je suis d'accord avec la conclusion des critiques. La réponse est valide telle qu'elle est et votre réponse ne contient aucune autre information pertinente qui n'est pas déjà présente dans la réponse ci-dessus. – danny