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
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.
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
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. :)
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)
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.
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.