lenguaje - python tutorial
Conversión de la declaración de "rendimiento de" a Python 2.7 (6)
Tenía un código a continuación en Python 3.2 y quería ejecutarlo en Python 2.7. Lo convertí (he puesto el código de missing_elements
en ambas versiones) pero no estoy seguro de si esa es la forma más eficiente de hacerlo. Básicamente, ¿qué sucede si hay dos yield from
llamadas como abajo en la mitad superior y la mitad inferior en la función missing_element
? ¿Las entradas de las dos mitades (superior e inferior) se adjuntan entre sí en una lista para que la recursión principal funcione con el yield from
llamada y use ambas mitades juntas?
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
¿Qué hay de usar la definición de pep-380 para construir una versión de sintaxis de Python 2?
La declaración:
RESULT = yield from EXPR
es semánticamente equivalente a:
_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
En un generador, la declaración:
return value
es semánticamente equivalente a
raise StopIteration(value)
excepto que, como en la actualidad, la excepción no puede ser detectada por cláusulas de except
dentro del generador de retorno.
La excepción StopIteration se comporta como si se definiera así:
class StopIteration(Exception):
def __init__(self, *args):
if len(args) > 0:
self.value = args[0]
else:
self.value = None
Exception.__init__(self, *args)
Acabo de encontrar este problema y mi uso fue un poco más difícil ya que necesitaba el valor de retorno del yield from
:
result = yield from other_gen()
Esto no se puede representar como un simple bucle for
pero se puede reproducir con esto:
_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
Esperemos que esto ayude a las personas que se encuentran con el mismo problema. :)
Creo que encontré una manera de emular el yield from
Python 3.x yield from
construcción en Python 2.x. No es eficiente y es un poco hacky, pero aquí está:
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)
Uso:
@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,
Produce salida:
1 1 1 2 1 1 2 1 2 3
Tal vez alguien encuentra esto útil.
Problemas conocidos: falta compatibilidad con send () y varios casos de esquinas descritos en PEP 380. Se podrían agregar y editaré mi entrada una vez que lo haga funcionar.
Descubrí que usar contextos de recursos (usar el módulo de python-resources ) es un mecanismo elegante para implementar subgeneradores en Python 2.7. Convenientemente ya había estado usando los contextos de recursos de todos modos.
Si en Python 3.3 tendrías:
@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()
En Python 2.7 tendrías:
@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()
Observe cómo las operaciones de lógica complicada solo requieren el registro como un recurso.
Reemplazarlos con bucles for:
yield from range(L[start] + 1, L[end])
==>
for i in range(L[start] + 1, L[end]):
yield i
Lo mismo sobre los elementos:
yield from missing_elements(L, index, end)
==>
for el in missing_elements(L, index, end):
yield el
Si no usa los resultados de sus rendimientos, * siempre puede convertir esto:
yield from foo
… dentro de esto:
for bar in foo:
yield bar
Puede haber un costo de rendimiento, ** pero nunca hay una diferencia semántica.
¿Las entradas de las dos mitades (superior e inferior) se adjuntan entre sí en una lista para que la recursión principal funcione con el rendimiento de la llamada y use ambas mitades juntas?
¡No! El objetivo principal de los iteradores y generadores es que no se crean listas reales y no se agregan.
Pero el efecto es similar: solo cedes de uno, luego cedes de otro.
Si piensa en la mitad superior y en la inferior como "listas perezosas", entonces sí, puede pensar en esto como un "anexo perezoso" que crea una "lista perezosa" más grande. Y si llama a la list
en el resultado de la función principal, por supuesto que obtendrá una list
real que es equivalente a adjuntar las dos listas que habría obtenido si hubiera hecho la yield list(…)
lugar del yield from …
Pero creo que es más fácil pensarlo al revés: lo que hace es exactamente lo mismo que los bucles for
.
Si guardó los dos iteradores en variables y realizó un bucle en itertools.chain(upper, lower)
, sería lo mismo que un bucle en el primero y luego un bucle en el segundo, ¿verdad? No hay diferencia aquí. De hecho, podría implementar la chain
como simplemente:
for arg in *args:
yield from arg
* No son los valores que el generador cede a su llamador, el valor de las expresiones de rendimiento en sí mismas, dentro del generador (que proviene del llamante que utiliza el método de send
), como se describe en PEP 342 . No estás usando estos en tus ejemplos. Y estoy dispuesto a apostar que no estás en tu código real. Pero el código de estilo coroutine a menudo usa el valor de un yield from
expresión: vea PEP 3156 para ver ejemplos. Dicho código generalmente depende de otras características de los generadores de Python 3.3, en particular, el nuevo valor StopIteration.value
del mismo PEP 380 que introdujo el yield from
, por lo que tendrá que ser reescrito. Pero si no, puede usar el PEP. También le muestra el horroroso equivalente completo y, por supuesto, puede reducir las partes que no le interesan. Y si no usa el valor de la expresión, se reduce a las dos líneas de arriba.
** No es enorme, y no hay nada que puedas hacer al respecto, aparte de usar Python 3.3 o reestructurar completamente tu código. Es exactamente el mismo caso que la traducción de listas de comprensión a los bucles de Python 1.5, o cualquier otro caso cuando hay una nueva optimización en la versión XY y necesita usar una versión anterior.