Encontré un caso en el que el uso de map() no es equivalente a la comprensión de una lista. Ocurre cuando se usa a next como primer argumento.
Por ejemplo:
l1 = [1, 2] l2 = ['hello', 'world'] iterators = [iter(l1), iter(l2)] # list comprehension values1 = [next(it) for it in iterators] # values1 = [1, "hello"] values2 = [next(it) for it in iterators] # values2 = [2, "world"] values3 = [next(it) for it in iterators] # raise StopIteration l1 = [1, 2] l2 = ['hello', 'world'] iterators = [iter(l1), iter(l2)] # map values1 = list(map(next, iterators)) # values1 = [1, "hello"] values2 = list(map(next, iterators)) # values2 = [2, "world"] values3 = list(map(next, iterators)) # values3 = [] # doesn't raise StopIterationCualquier otra excepción ocurre como debería. Ejemplo:
def divide_by_zero(value: int): return value // 0 l = [1, 2, 3] values = list(map(divide_by_zero, l)) # raises ZeroDivisionError as expected values = [divide_by_zero(value) for value in l] # raises ZeroDivisionError as expected, tooParece muy extraño. Funciona igual con Python 3.9 y Python 3.11.
Parece que map() funciona así:
def map(func, iterator): try: while True: item = next(iterator) yield func(item) except StopIteration: passpero esperaba que funcionara así:
def map(func, iterator): while True: try: item = next(iterator) except StopIteration: break yield func(item)¿Es un error?
Intente llamar next en el map :
>>> >>> m = map(next, iterators) >>> next(m) 1 >>> next(m) 'hello' >>> next(m) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration Es la list que ve StopIteration y la usa para detener la construcción de la lista a partir de lo que produce el map .
La comprensión de la lista, por otro lado, está construyendo la lista iterando sobre iterators , no un iterador particular en esa lista. Es decir, next(it) se usa para producir un valor para la lista, no para determinar si hemos llegado al final de los iterators .
Parece que ha encontrado uno de los raros casos en los que permitir que StopIteration aparezca como una excepción provoca un comportamiento incorrecto. No es un error, es solo una consecuencia desafortunada de la decisión de diseño de Python de usar una excepción para señalar el final de un iterador; es una trampa, como los argumentos predeterminados mutables , excepto que aparece con mucha menos frecuencia.
Como señala la respuesta de @chepner, el problema es que la list está capturando StopIteration y, por lo tanto, pensando que el map está agotado, cuando lo que realmente sucedió es que la función de next de llamada generó una excepción que desea tratar como una excepción real, es decir, una condición de falla .
Para evitar esto, en términos generales, no debe permitir que se llame a next en un contexto en el que StopIteration pueda surgir y quedar atrapado en el lugar equivocado. Si desea pasar next como una devolución de llamada, le sugiero que escriba un contenedor a next que convierta StopIteration en una excepción que no se malinterpretará como el final de algún otro iterador:
def safe_next(it): try: return next(it) except StopIteration: raise ValueError('Unexpected end of iterator')Ejemplo de uso:
>>> l1 = [1, 2] >>> l2 = ['hello', 'world'] >>> iterators = [iter(l1), iter(l2)] >>> list(map(safe_next, iterators)) [1, 'hello'] >>> list(map(safe_next, iterators)) [2, 'world'] >>> list(map(safe_next, iterators)) Traceback (most recent call last): File "<stdin>", line 3, in safe_next StopIteration During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in safe_next ValueError: Unexpected end of iterator Dicho esto, en este caso de uso probablemente deberías usar zip :
>>> pairs = zip(l1, l2) >>> next(pairs) (1, 'hello') >>> next(pairs) (2, 'world') >>> next(pairs) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration