¿Alguien puede explicar este extraño error iterando sobre un conjunto?

Tuve un bucle de la forma for thing in a_set: Estaba funcionando incorrectamente porque, ocasionalmente e inconsistentemente, sacaría la misma cosa del conjunto dos veces. (Esto no hace que el progtwig se bloquee. Simplemente recibe la respuesta incorrecta). No pude determinar nada que fuera determinista sobre el comportamiento incorrecto; pero mis bashs de depurarlo dejaron muy claro que la extrañeza ocurría a veces. En los casos en los que lo observé más de cerca, había 3 elementos en el conjunto (antes y después) y el bucle se ejecutó 4 veces, una vez con una repetición de uno de los elementos. Los artículos eran referencias a objetos de una clase que había creado (tratados más como una estructura C). El mal comportamiento desapareció cuando cambié la instrucción for thing in list(a_set): por for thing in list(a_set):

Estoy en una pérdida total para explicar el comportamiento incorrecto. Estoy muy seguro de que nada en el cuerpo del bucle puede hacer que lo que está haciendo ocurra dos veces o cambiar el valor de la variable cosa. Estoy bastante seguro de que lo que está sucediendo en el bucle no podría tratar de afectar la composición del conjunto. Además, incluso si pudiera, creo que eso causaría un RuntimeError . Estoy en una pérdida total para llegar a hipótesis sobre lo que podría estar causando esto. La falta de repetitividad que ejecuta el mismo código consecutivamente es especialmente misteriosa. Mis bashs de recrear el síntoma en un escenario más simple han fallado. Sin embargo, me sentiría tonto por dejar la list() invocación allí solo para resolver un problema que no puedo explicar. La hipótesis de cualquier otra persona sería bienvenida. Necesito ideas sobre qué tipo de cosas debería tratar de eliminar al depurarlas.

Actualización: creo que esta pregunta se puso incorrectamente en espera debido a una afirmación de que estaba fuera de tema. La falta de reproducibilidad fue el problema en este caso, y sospeché que había un cierto matiz del lenguaje que faltaba. De hecho, resulta que ese es el caso, y la respuesta de MSeifert me llevó a lo que lo estaba causando. Sin embargo, no fue tan simple como lo especuló, como observo en un comentario sobre su respuesta.

También confundí el problema diciendo que los objetos del conjunto eran mutables. Ellos no son. Son referencias a objetos cuyos atributos son modificables. (Eso podría haberse deducido de lo que escribí, pero estaba usando incorrectamente la palabra “mutable” en un sentido general y no en el sentido técnico de Python). Lo que está en hash es la dirección del objeto, independientemente de los valores de su atributos Si esas referencias de objetos fueran mutables, Python nunca me hubiera permitido colocarlas en un conjunto en primer lugar.

Si el error desapareció al agregar la list(a_set) es muy probable que haya cambiado el conjunto durante la iteración. En general, esto lanza un RuntimeError pero en caso de que agregue tantos elementos como lo elimine no se activará:

 a = {1,2,3} for item in a: print(item) a.add(item+3) # add one item a.remove(item) # remove one item 

imprime los números del 1 al 31 (la cantidad es en realidad un detalle de implementación, por lo que puede ver cantidades diferentes) y antes y después del bucle, así como al comienzo de cada iteración, el set contiene 3 elementos.

Sin embargo, si agrego una llamada de list , crea una copia (como lista) del conjunto original y solo itera sobre los elementos que estaban presentes en el conjunto original:

 a = {1,2,3} for item in list(a): print(item) a.add(item+3) a.remove(item) print(a) 

huellas dactilares:

 1 2 3 set([4, 5, 6]) # totally changed! 

En los comentarios, notó que las clases que tiene en el conjunto son mutables, por lo que aunque piense que elimina y agrega el mismo elemento, puede que ya no sea el mismo elemento (desde el punto de vista del set ). En general, no debe colocar clases mutables en un set o como claves en un dict porque debe tener mucho cuidado de que la mutabilidad no pueda afectar el resultado de los métodos __hash__ o __eq__ .

Solo un ejemplo que itera sobre un número aparentemente “aleatorio” de elementos establecidos:

 class Fun(object): def __init__(self, value): self.value = value def __repr__(self): return '{self.__class__.__name__}({self.value})'.format(self=self) def __eq__(self, other): return self.value == other.value a = {Fun(1),Fun(2),Fun(3)} for item in a: print(item) a.add(Fun(item.value+3)) a.remove(item) 

realmente mostrará un “aleatorio” (no realmente aleatorio, solo depende de los hashes de las instancias y, en este caso, el hash depende de la id del objeto de clase que cambia cada vez que ejecuto el código) número de objetos Fun cada vez que ejecutar el fragmento