Dibujar un gráfico de llamadas

11

Estoy manteniendo una antigua base de código escrita en python. En particular, hay un código complejo que desde un módulo llama a otras funciones desde otros módulos que llaman a otras funciones, etc. No es OOP, solo funciones y módulos.
Intenté hacer un seguimiento de dónde comienza y termina el flujo cada vez que llamo a la función principal, pero siento que necesito dibujar esto porque me estoy perdiendo en las llamadas secundarias.

Lo que me preocupa es que cada función llama múltiples funciones externas dentro de su cuerpo para completar su tarea y devolver el valor a la persona que llama.

¿Cómo puedo dibujar esto? ¿Qué tipo de tabla / gráfico sería apropiado para documentar este tipo de comportamiento / código?

Por lo tanto, no creo que sea útil dibujar un diagrama UML, ni un diagrama de flujo. ¿Un gráfico de llamadas, tal vez?

Leonardo
fuente
doxygen: generará gráficos de llamadas / llamadas, no estoy seguro de cuánto soporte tiene para python. Sé que puedes documentar el código de Python para ello.
gbjbaanb
Intenté pycallgraph pero es demasiado complicado / demasiado profundo para usarlo. Esto se debe a la complejidad de mi código porque combina Python simple con Django y una llamada externa a la URL de la API. Es por eso que quería dibujarlo a mano solo teniendo en cuenta la parte relevante que necesito. El problema es que no sé qué tipo de gráfico usar para tener una comprensión completa del sistema
Leonardo
55
Si esto es solo para ayudarlo a comprenderlo, simplemente dibuje lo que sea natural. Siempre puede ordenarlo más adelante si se trata de documentación formal.
jonrsharpe

Respuestas:

9

Creo que lo que estás buscando aquí es un diagrama de secuencia . Estos le permiten visualizar el orden en que varios módulos se llaman entre sí mediante el uso de flechas.

Construir uno es simple:

  1. Dibuja tu clase inicial con una línea de puntos debajo de ella.
  2. Dibuje la siguiente clase / método en el seguimiento de la llamada con una línea de puntos debajo de ese
  3. Conecte las líneas con una flecha, colocada verticalmente debajo de la última flecha que dibujó
  4. Repita los pasos 2-3 para todas las llamadas en su rastreo

Ejemplo

Supongamos que tenemos el siguiente código para el que queremos crear un diagrama de secuencia:

def long_division(quotient, divisor):
    solution = ""
    remainder = quotient
    working = ""
    while len(remainder) > 0:
        working += remainder[0]
        remainder = remainder[1:]
        multiplier = find_largest_fit(working, divisor)
        solution += multiplier
        working = calculate_remainder(working, multiplier, divisor)
    print solution


def calculate_remainder(working, multiplier, divisor):
    cur_len = len(working)
    int_rem = int(working) - (int(multiplier) * int (divisor))
    return "%*d" % (cur_len, int_rem)


def find_largest_fit(quotient, divisor):
    if int(divisor) == 0:
        return "0"
    i = 0
    while i <= 10:
        if (int(divisor) * i) > int(quotient):
            return str(i - 1)
        else:
            i += 1


if __name__ == "__main__":
    long_division("645", "5")

Lo primero que dibujaremos es el punto de entrada ( main) que se conecta al método long_division. Tenga en cuenta que esto crea un cuadro en long_division, lo que significa el alcance de la llamada al método. Para este ejemplo simple, el cuadro será la altura completa de nuestro diagrama de secuencia debido al hecho de que esto es lo único que se ejecuta.

ingrese la descripción de la imagen aquí

Ahora llamamos find_largest_fitpara encontrar el múltiplo más grande que se ajuste a nuestro número de trabajo y nos lo devuelve. Dibujamos una línea de long_divisiona find_largest_fitcon otro cuadro para indicar el alcance de la llamada a la función. Observe cómo termina el cuadro cuando se devuelve el multiplicador; ¡Este es el final del alcance de las funciones!

ingrese la descripción de la imagen aquí

Repita varias veces para un número mayor y su gráfico debería verse así:

ingrese la descripción de la imagen aquí

Notas

Puede elegir si desea etiquetar las llamadas con los nombres de variables pasados ​​o sus valores si solo desea documentar un caso específico. También puede mostrar recursividad con una función que se llama a sí misma.

Además, puede mostrar a los usuarios aquí y solicitarles que muestren su entrada en el sistema con bastante facilidad. ¡Es un sistema bastante flexible que creo que encontrarás bastante útil!

Ampt
fuente
Gracias, conozco el diagrama de secuencia, pero me parece que es más adecuado para OOP. En mi caso, las cosas son un poco más complicadas, lo que significa que, por ejemplo, tengo alrededor de 20 funciones / ayudantes repartidas en varios módulos. ¿Cómo especificaría el módulo al que pertenece la función? Teniendo en cuenta que algunas funciones también cambian de nombre durante las importaciones ..
Leonardo
1
Yo diría que no importa cuántos módulos tengas, el ejemplo anterior tampoco está en absoluto. Simplemente nómbrelos para que pueda encontrarlos más tarde, Módulo A / función1, Módulo B / Función2, etc. Para 20 funciones será más grande, pero definitivamente no es imposible de entender. Otra idea que puede hacer es finalizar la línea de una función después de su último uso y colocar otra línea de funciones debajo de ella para ahorrar espacio horizontal en su diagrama.
Ampt
5

Creo que un gráfico de llamadas sería la visualización más adecuada. Si decide no hacerlo a mano, hay una pequeña herramienta pyanque se llama análisis estático en un archivo de Python y puede generar un gráfico de llamada visualizado a través de un archivo de punto graphviz (que se puede representar en una imagen). Ha habido un par de tenedores, pero el más completo parece ser https://github.com/davidfraser/pyan .

Solo necesita especificar todos los archivos que desea procesar cuando ejecuta el comando:

python ~/bin/pyan.py --dot a.py b.py c.py -n > pyan.dot; dot -Tpng -opyan.png pyan.dot

o

python ~/bin/pyan.py --dot $(find . -name '*.py') -n > pyan.dot; dot -Tpng -opyan.png pyan.dot

Puede hacer que el gráfico sea más limpio con la '-n' que elimina las líneas que muestran dónde se definió una función.

seren
fuente