Fondo rápido: tenemos una gran fuente de base escrita en Python. Es un compilador para un lenguaje específico de dominio, e internamente todo se representa como gráficos dirigidos. Estos dígrafos se crean a partir de conjuntos, por lo que usamos el tipo de conjunto incorporado en Python.¿Cómo busco un Heisenbug en algún código de Python?
El problema es que originalmente no nos dimos cuenta de que Python utiliza activamente la falta de garantía de pedido en un objeto conjunto para usar una implementación no determinista más rápida. Por lo tanto, cuando itera sobre los objetos en un conjunto (como hacemos frecuentemente), el orden en el que se devuelve es débilmente aleatorio. No cambia en cada ejecución, pero cambia con frecuencia. Por lo que he visto depurar nuestro código, parece como si un hash del código fuente actuara como una semilla para el generador de números aleatorios. Por lo tanto, al cambiar el código, incluso en una ruta que no se ejecuta, el iterador de conjunto cambia el orden en que se generan los elementos.
Nuestra estrategia básica de depuración consiste en volcar las copias en el lugar donde creemos que está el error refinándolas basado en la salida hasta que encontremos el error. No es muy elegante, pero para la mayoría de las cosas funciona en gran medida. El problema básico es que agregar/cambiar cualquier declaración de impresión desencadena un comportamiento diferente y una ruta de ejecución muy diferente. No podemos permitirnos imprimir/registrar todo, ya que ralentiza el tiempo de ejecución del compilador de aproximadamente 20 segundos (manejable) a aproximadamente una hora (no tan manejable).
¿Cómo diagnosticarías un problema que solo ocurre con poca frecuencia y desaparece cuando comienzas a buscarlo?
Editar una aclaración: Varias respuestas sugieren maneras de fijar el orden de los conjuntos. Como dice torkildr a continuación "El problema aquí no es que los conjuntos se comporten de manera extraña, el problema es que su programa se comporta como si no fuera así". Este es exactamente el problema, pero la solución no es usar conjuntos deterministas. Esto simplemente enmascararía el comportamiento. El problema es encontrar por qué nuestro programa se comporta de esta manera y corregir ese comportamiento. Los algoritmos que usamos deben funcionar en los gráficos representados como conjuntos no ordenados. Ellos no. Necesito averiguar dónde están estos insectos y por qué ocurren.
Problema resuelto: Resulta que en la implementación de Python que estoy usando (2.6 en OS-X) si la relación entre los __eq__
__hash__
y métodos de los objetos que se almacena en el conjunto no es una ordenación bastante válida, entonces el sistema exhibe el comportamiento débilmente aleatorio descrito. Debe haber algún código en la implementación C de set.add() que use algo del módulo aleatorio para construir la representación. Esto provoca una dependencia en el grupo de entropía del sistema que cambia el orden en las escrituras del disco.
No hay respuestas directas, pero la lectura de la pregunta de seguimiento de kriss provocó que la perspicacia resolviera este problema para que obtuviera el voto.
He echado un vistazo rápido a los documentos pdb y no veo cómo hacer esto. Los documentos implican que modifica el código para insertar un punto de interrupción llamando a pdb .... ¿Lo he leído mal o simplemente no lo entiendo? – Amoss
Has leído bien, pero parece que en tu caso solo tendrás que llamar a pdb desde otro módulo (o incluso desde python interactive) y luego importar y ejecutar tu módulo. Lo que no está claro es si el comportamiento establecido depende del código fuente o del estado de la memoria. Por cierto, también puedes usar dictionnaries en lugar de set. Para ellos, el orden está garantizado no aleatorio (nd. Estoy bastante sorprendido de que haya algo aleatorio en los conjuntos, pero la documentación de Python en verdad no dice nada sobre eso y parece no garantizar que el orden será estable para dos iteraciones consecutivas en conjuntos inalterados) – kriss
Si tiene un módulo ejecutable, puede usar 'python -m pdb mymodule' y PDB se cargará primero, ofreciéndole la oportunidad de instalar puntos de interrupción. La mayoría de las personas modificará el código porque es más simple. –