web-dev-qa-db-fra.com

Conversion de l'instruction "yield from" en code Python 2.7

J'avais un code ci-dessous dans Python 3.2 et je voulais l'exécuter dans Python 2.7. Je l'ai converti (j'ai mis le code de missing_elements dans les deux versions) mais je ne sais pas si c'est la façon la plus efficace de le faire. Fondamentalement, ce qui se passe s'il y a deux yield from appels comme ci-dessous dans la moitié supérieure et la moitié inférieure dans missing_element une fonction? Les entrées des deux moitiés (supérieure et inférieure) sont-elles ajoutées l'une à l'autre dans une liste afin que la fonction de récursivité parent avec le yield from appeler et utiliser les deux moitiés ensemble?

def missing_elements(L, start, end):  # Python 3.2
    if end - start <= 1: 
        if L[end] - L[start] > 1:
            yield from range(L[start] + 1, L[end])
        return

index = start + (end - start) // 2

# is the lower half consecutive?
consecutive_low =  L[index] == L[start] + (index - start)
if not consecutive_low:
    yield from missing_elements(L, start, index)

# is the upper part consecutive?
consecutive_high =  L[index] == L[end] - (end - index)
if not consecutive_high:
    yield from missing_elements(L, index, end)

def main():
    L = [10, 11, 13, 14, 15, 16, 17, 18, 20]
    print(list(missing_elements(L, 0, len(L)-1)))
    L = range(10, 21)
    print(list(missing_elements(L, 0, len(L)-1)))

def missing_elements(L, start, end):  # Python 2.7
    return_list = []                
    if end - start <= 1: 
        if L[end] - L[start] > 1:
            return range(L[start] + 1, L[end])

    index = start + (end - start) // 2

    # is the lower half consecutive?
    consecutive_low =  L[index] == L[start] + (index - start)
    if not consecutive_low:
        return_list.append(missing_elements(L, start, index))

    # is the upper part consecutive?
    consecutive_high =  L[index] == L[end] - (end - index)
    if not consecutive_high:
        return_list.append(missing_elements(L, index, end))
    return return_list
63
vkaul11

Si vous n'utilisez pas les résultats de vos rendements, * vous pouvez toujours tourner ceci:

yield from foo

… En cela:

for bar in foo:
    yield bar

Il peut y avoir un coût de performance **, mais il n'y a jamais de différence sémantique.


Les entrées des deux moitiés (supérieure et inférieure) sont-elles ajoutées l'une à l'autre dans une liste afin que la fonction de récursivité parentale avec le rendement de l'appel et utilise les deux moitiés ensemble?

Non! L'intérêt des itérateurs et des générateurs est que vous ne construisez pas de listes réelles et que vous ne les ajoutez pas ensemble.

Mais l'effet est similaire: vous ne cédez que de l'un, puis vous en cédez un autre.

Si vous considérez la moitié supérieure et la moitié inférieure comme des "listes paresseuses", alors oui, vous pouvez considérer cela comme une "annexe paresseuse" qui crée une "liste paresseuse" plus grande. Et si vous appelez list sur le résultat de la fonction parent, vous obtiendrez un list réel qui est équivaut à ajouter ensemble les deux listes que vous auriez obtenues si vous aviez fait yield list(…) au lieu de yield from ….

Mais je pense qu'il est plus facile de penser à l'inverse: ce qu'il fait est exactement la même chose que les boucles for.

Si vous enregistrez les deux itérateurs dans des variables et bouclez sur itertools.chain(upper, lower), ce serait la même chose que de boucler sur le premier puis de boucler sur le second, non? Aucune différence ici. En fait, vous pouvez implémenter chain simplement:

for arg in *args:
    yield from arg

* Pas les valeurs que le générateur donne à son appelant, la valeur des expressions de rendement elles-mêmes, dans le générateur (qui proviennent de l'appelant en utilisant la méthode send), comme décrit dans PEP 342 =. Vous ne les utilisez pas dans vos exemples. Et je suis prêt à parier que vous n'êtes pas dans votre vrai code. Mais le code de style coroutine utilise souvent la valeur d'une expression yield from - voir PEP 3156 pour des exemples. Ce code dépend généralement d'autres fonctionnalités des générateurs Python 3.3 - en particulier, le nouveau StopIteration.value Du même PEP 38 qui a introduit yield from - Il faudra donc le réécrire. Sinon, vous pouvez utiliser le PEP qui vous montre également l'équivalent complet et horrible et vous pouvez bien sûr réduire les parties qui ne vous intéressent pas. Et si vous ne le faites pas '' t utiliser la valeur de l'expression, elle réduit les deux lignes ci-dessus.

** Pas énorme, et vous ne pouvez rien y faire si ce n'est d'utiliser Python 3.3 ou restructurer complètement votre code. C'est exactement le même cas que de traduire des compréhensions de liste en Python 1,5 boucles, ou tout autre cas lorsqu'il y a une nouvelle optimisation dans la version XY et que vous devez utiliser une version plus ancienne.

80
abarnert

Remplacez-les par des boucles for:

yield from range(L[start] + 1, L[end])

==>

for i in range(L[start] + 1, L[end]):
    yield i

La même chose sur les éléments:

yield from missing_elements(L, index, end)

==>

for el in missing_elements(L, index, end):
    yield el
4
ovgolovin

Je viens de rencontrer ce problème et mon utilisation était un peu plus difficile car j'avais besoin de valeur de retour de yield from:

result = yield from other_gen()

Cela ne peut pas être représenté comme une simple boucle for mais peut être reproduit avec ceci:

_iter = iter(other_gen())
try:
    while True: #broken by StopIteration
        yield next(_iter)
except StopIteration as e:
    if e.args:
        result = e.args[0]
    else:
        result = None

J'espère que cela aidera les gens qui rencontrent le même problème. :)

4

Qu'en est-il de l'utilisation de la définition de pep-38 afin de construire une version de syntaxe Python 2:

La déclaration:

RESULT = yield from EXPR

est sémantiquement équivalent à:

_i = iter(EXPR)
try:
    _y = next(_i)
except StopIteration as _e:
    _r = _e.value
else:
    while 1:
        try:
            _s = yield _y
        except GeneratorExit as _e:
            try:
                _m = _i.close
            except AttributeError:
                pass
            else:
                _m()
            raise _e
        except BaseException as _e:
            _x = sys.exc_info()
            try:
                _m = _i.throw
            except AttributeError:
                raise _e
            else:
                try:
                    _y = _m(*_x)
                except StopIteration as _e:
                    _r = _e.value
                    break
        else:
            try:
                if _s is None:
                    _y = next(_i)
                else:
                    _y = _i.send(_s)
            except StopIteration as _e:
                _r = _e.value
                break
RESULT = _r

Dans un générateur, l'instruction:

return value

est sémantiquement équivalent à

raise StopIteration(value)

sauf que, comme actuellement, l'exception ne peut pas être interceptée par les clauses except dans le générateur de retour.

L'exception StopIteration se comporte comme si elle était définie ainsi:

class StopIteration(Exception):

    def __init__(self, *args):
        if len(args) > 0:
            self.value = args[0]
        else:
            self.value = None
        Exception.__init__(self, *args)
2
Cliff Hill

Je pense avoir trouvé un moyen d'émuler Python 3.x yield from construire en Python 2.x. Ce n'est pas efficace et c'est un peu hacky, mais le voici:

import types

def inline_generators(fn):
    def inline(value):
        if isinstance(value, InlineGenerator):
            for x in value.wrapped:
                for y in inline(x):
                    yield y
        else:
            yield value
    def wrapped(*args, **kwargs):
        result = fn(*args, **kwargs)
        if isinstance(result, types.GeneratorType):
            result = inline(_from(result))
        return result
    return wrapped

class InlineGenerator(object):
    def __init__(self, wrapped):
        self.wrapped = wrapped

def _from(value):
    assert isinstance(value, types.GeneratorType)
    return InlineGenerator(value)

tilisation:

@inline_generators
def outer(x):
    def inner_inner(x):
        for x in range(1, x + 1):
            yield x
    def inner(x):
        for x in range(1, x + 1):
            yield _from(inner_inner(x))
    for x in range(1, x + 1):
        yield _from(inner(x))

for x in outer(3):
    print x,

Produit une sortie:

1 1 1 2 1 1 2 1 2 3

Peut-être que quelqu'un trouve cela utile.

Problèmes connus: Manque de prise en charge pour send () et divers cas d'angle décrits dans PEP 380. Ceux-ci pourraient être ajoutés et je modifierai mon entrée une fois que je la ferai fonctionner.

2
julkiewicz

J'ai trouvé que l'utilisation de contextes de ressources (en utilisant le module python-resources ) était un mécanisme élégant pour implémenter des sous-générateurs dans Python 2.7. Idéalement, j'avais déjà utilisé les contextes de ressources de toute façon.

Si dans Python 3.3 vous auriez:

@resources.register_func
def get_a_thing(type_of_thing):
    if type_of_thing is "A":
        yield from complicated_logic_for_handling_a()
    else:
        yield from complicated_logic_for_handling_b()

def complicated_logic_for_handling_a():
    a = expensive_setup_for_a()
    yield a
    expensive_tear_down_for_a()

def complicated_logic_for_handling_b():
    b = expensive_setup_for_b()
    yield b
    expensive_tear_down_for_b()

Dans Python 2.7 vous auriez:

@resources.register_func
def get_a_thing(type_of_thing):
    if type_of_thing is "A":
        with resources.complicated_logic_for_handling_a_ctx() as a:
            yield a
    else:
        with resources.complicated_logic_for_handling_b_ctx() as b:
            yield b

@resources.register_func
def complicated_logic_for_handling_a():
    a = expensive_setup_for_a()
    yield a
    expensive_tear_down_for_a()

@resources.register_func
def complicated_logic_for_handling_b():
    b = expensive_setup_for_b()
    yield b
    expensive_tear_down_for_b()

Notez que les opérations de logique compliquée nécessitent uniquement l'enregistrement en tant que ressource.

0
alkalinity