Comparando dos generadores en Python

Me pregunto sobre el uso de == al comparar dos generadores

Por ejemplo:

 x = ['1','2','3','4','5'] gen_1 = (int(ele) for ele in x) gen_2 = (int(ele) for ele in x) 

gen_1 y gen_2 son iguales para todos los propósitos prácticos y, sin embargo, cuando los comparo:

 >>> gen_1 == gen_2 False 

Mi conjetura aquí es que == aquí se trata como is normalmente, y dado que gen_1 y gen_2 están ubicados en diferentes lugares en la memoria:

 >>> gen_1 <generator object  at 0x01E8BAA8> >>> gen_2 <generator object  at 0x01EEE4B8> 

su comparación se evalúa a False . Estoy en lo cierto en esta conjetura? Y cualquier otra idea es bienvenida.

Y por cierto, sé cómo comparar dos generadores:

 >>> all(a == b for a,b in zip(gen_1, gen_2)) True 

o incluso

 >>> list(gen_1) == list(gen_2) True 

Pero si hay una mejor manera, me encantaría saberlo.

Tiene razón con su conjetura: la alternativa de comparación de tipos que no definen == es la comparación basada en la identidad del objeto.

Una mejor manera de comparar los valores que generan sería

 from itertools import izip_longest, tee sentinel = object() all(a == b for a, b in izip_longest(gen_1, gen_2, fillvalue=sentinel)) 

Esto puede provocar un cortocircuito sin tener que mirar necesariamente todos los valores. Como lo señala larsmans en los comentarios, no podemos usar izip() aquí, ya que podría dar resultados erróneos si los generadores producen un número diferente de elementos: izip() se detendrá en el iterador más corto. Utilizamos una instancia de object recién creada como valor de relleno para izip_longest() , ya object instancias de object también se comparan por identidad de objeto, por lo que sentinel está garantizada para comparar desigual con todo lo demás.

Tenga en cuenta que no hay forma de comparar los generadores sin cambiar su estado. Puede almacenar los artículos que se consumieron si los necesita más adelante:

 gen_1, gen_1_teed = tee(gen_1) gen_2, gen_2_teed = tee(gen_2) all(a == b for a, b in izip_longest(gen_1, gen_2, fillvalue=sentinel)) 

Esto dejará el estado de gen_1 y gen_2 esencialmente sin cambios. Todos los valores consumidos por all() se almacenan dentro del objeto tee .

En ese punto, puede preguntarse si realmente vale la pena usar generadores perezosos para la aplicación en cuestión, podría ser mejor simplemente convertirlos en listas y trabajar con las listas en su lugar.

Debido a que los generadores generan sus valores a pedido, no hay manera de “compararlos” sin consumirlos realmente. Y si sus generadores generan una secuencia infinita de valores, tal prueba de igualdad que usted proponga sería inútil.

== es de hecho lo mismo que en dos generadores, porque esa es la única verificación que se puede hacer sin cambiar su estado y, por lo tanto, perder elementos.

 list(gen_1) == list(gen_2) 

es la forma confiable y general de comparar dos generadores finitos (pero obviamente consume ambos); su solución basada en zip falla cuando no generan un número igual de elementos:

 >>> list(zip([1,2,3,4], [1,2,3])) [(1, 1), (2, 2), (3, 3)] >>> all(a == b for a, b in zip([1,2,3,4], [1,2,3])) True 

La solución basada en la list todavía falla cuando cualquiera de los generadores genera un número infinito de elementos. Puede idear una solución alternativa para eso, pero cuando ambos generadores son infinitos, solo puede idear un semi-algoritmo para la no igualdad.

Para realizar una comparación por elementos de dos generadores con listas y otros contenedores, Python tendría que consumirlos por completo (bueno, el más corto, de todos modos). Creo que es bueno que debas hacer esto explícitamente, especialmente porque uno u otro puede ser infinito.