Trace Table for Python Programs

前端 未结 3 1277
我在风中等你
我在风中等你 2021-02-09 12:04

Is there a way to get the trace table for a Python program? Or for a program to run another program and get its trace table? I\'m a teacher trying to flawlessly verify the answe

相关标签:
3条回答
  • 2021-02-09 12:25

    Based on what ned-batchelder proposed, as a teacher, I've made a Tracer class that help on creating LaTeX outputed longtable showing the trace of a program with selective variables, bypassing input() for automating process (especially while called by a \bash macro from the powerfull bashful LaTeX package).

    tracer.py:

    import sys
    class Tracer():
        def __init__(self, varList=[], startLine=1, jeuEssai=[]):
            """
            Arguments :
            \tvarList\ttraced variable list (used as column header)
            \tstartLine\toffset numbering line from the beginning of the program
            \tjeuEssai\tinput values to be sent to the automated input bypass
            """
            self.traced_variables = varList
            self.traced_line_start = startLine
            self.input_values = jeuEssai
            self.input_cursor = int(0)
            self.traced_variables_new_values = dict( (k, '') for k in self.traced_variables)
    
            print("\\begin{longtable}{c*{%i}{>{\\ttfamily}c}}" % len(self.traced_variables), file=sys.stderr, flush=True)
            print("\t\\hline\\no ligne",end='', file=sys.stderr)
            for header in self.traced_variables:
                print(" &", header,end='', file=sys.stderr)
            print(" \\\\ \\hline", file=sys.stderr)
            sys.settrace(self.tracer_programme_latex)
    
    
        def tracer_programme_latex(self, frame, event, args):
            if frame.f_code.co_name not in ['input','print','close']:
                if event == "line":
                    output = str()
                    for var in self.traced_variables:
                        current_val = str(frame.f_locals.get(var, "-"))
                        if str(self.traced_variables_new_values.get(var, "-")) != current_val:
                            self.traced_variables_new_values[var] = current_val
                            current_val = "\hit{}" + current_val
                        output += " & "
                        output += current_val
                    output += " \\\\"
                    print("\t%s%s" % (str(frame.f_lineno - self.traced_line_start), output), file=sys.stderr, flush=True)
            return self.tracer_programme_latex
    
    
        def close(self):
            """Close the 'longtable' LaTeX environnement."""
            print("\\end{longtable}", file=sys.stderr, flush=True)
    
    
        def input(self, prompt=None):
            """
            bypass de la fonction 'input()' pour injecter
            les valeurs d'essais.
            Le jeu d'essai est fourni de manière cyclique. Cela peut
            causer des boucles infinies si vous ne fournissez pas une
            valeur permettant de réaliser l'arrêt des entrées (dans le
            cas bien-sûr où 'input()' est appelé dans une boucle).
            """
            self.input_cursor = (1 + self.input_cursor) % len(self.input_values)
            return self.input_values[self.input_cursor - 1]
    
    
        def print(self, *args):
            pass
    

    Next you can find an exemple,and the output generated:

    program.py:

    def factor():
        question = "Give a number: "
        number = float(input(question))
        product = 1
        while number != 0 :
            product *= number
            print("Product:", product)
            number = float(input(question))
    
    if __name__ == "__main__":
        import sys
        TRACING = len(sys.argv) == 2 and sys.argv[1] == 'trace'
        if TRACING:
            from tracer import Tracer
            t = Tracer(varList=['question','number','product'], startLine=2, jeuEssai=[7,6,5,-8,0])
            input = t.input
    
        factor()
        if TRACING:
            t.close()
    

    standard output: (while called by python3 program.py)

    Give a number: 7
    Product: 7.0
    Give a number: 6
    Product: 42.0
    Give a number: 5
    Product: 210.0
    Give a number: -8
    Product: -1680.0
    Give a number: 0
    

    output with Tracer: (while called by python3 program.py trace 1>/dev/null)

    \begin{longtable}{c*{3}{>{\ttfamily}c}}
        \hline\no ligne & question & number & product \\ \hline
        0 & \hit{}- & \hit{}- & \hit{}- \\
        1 & \hit{}Give a number:  & - & - \\
        2 & Give a number:  & \hit{}7.0 & - \\
        3 & Give a number:  & 7.0 & \hit{}1 \\
        4 & Give a number:  & 7.0 & 1 \\
        5 & Give a number:  & 7.0 & \hit{}7.0 \\
        6 & Give a number:  & 7.0 & 7.0 \\
        3 & Give a number:  & \hit{}6.0 & 7.0 \\
        4 & Give a number:  & 6.0 & 7.0 \\
        5 & Give a number:  & 6.0 & \hit{}42.0 \\
        6 & Give a number:  & 6.0 & 42.0 \\
        3 & Give a number:  & \hit{}5.0 & 42.0 \\
        4 & Give a number:  & 5.0 & 42.0 \\
        5 & Give a number:  & 5.0 & \hit{}210.0 \\
        6 & Give a number:  & 5.0 & 210.0 \\
        3 & Give a number:  & \hit{}-8.0 & 210.0 \\
        4 & Give a number:  & -8.0 & 210.0 \\
        5 & Give a number:  & -8.0 & \hit{}-1680.0 \\
        6 & Give a number:  & -8.0 & -1680.0 \\
        3 & Give a number:  & \hit{}0.0 & -1680.0 \\
    \end{longtable}
    

    The \hit{} macro is insert while the value has changed. It's up to you to define something relevant, like a coloring macro : \newcommand{\hit}{\color{red}}

    0 讨论(0)
  • 2021-02-09 12:28

    This isn't a use case that the current Python tracing tool support, but it should be possible to build. I don't know how you decide what columns to output. In your sample, a and b are the only local variables, but larger programs would have more interesting ecosystems of data.

    Updated: here's a simple proof of concept:

     1     import sys
     2
     3     def trace(frame, event, arg_unused):
     4         print event, frame.f_lineno, frame.f_locals
     5         return trace
     6
     7     sys.settrace(trace)
     8
     9     def foo():
    10         a = 1
    11         b = 2
    12
    13         a = a + b
    14
    15     foo()
    

    when run, the output is:

    call 9 {}
    line 10 {}
    line 11 {'a': 1}
    line 13 {'a': 1, 'b': 2}
    return 13 {'a': 3, 'b': 2}
    
    0 讨论(0)
  • 2021-02-09 12:35

    You could use the Python debugger, though I do not know how to have it step through on it's own, but it's likely do-able, then you could just parse the output.

    Here's a really crude example:

    adding.py

    a = 1
    b = 2
    
    a = a + b
    

    running it...

    PS >python -m pdb adding.py
    > adding.py(1)<module>()
    -> a = 1
    (Pdb) alias stepprint step;;print a;;print b
    (Pdb) stepprint
    > adding.py(2)<module>()
    -> b = 2
    1
    *** NameError: name 'b' is not defined
    (Pdb) stepprint
    > adding.py(4)<module>()
    -> a = a + b
    1
    2
    (Pdb) stepprint
    --Return--
    > adding.py(4)<module>()->None
    -> a = a + b
    3
    2
    (Pdb) stepprint
    --Return--
    > <string>(1)<module>()->None
    3
    2
    (Pdb) stepprint
    The program finished and will be restarted
    > adding.py(1)<module>()
    -> a = 1
    *** NameError: name 'a' is not defined
    *** NameError: name 'b' is not defined
    (Pdb) q
    
    PS >
    

    End (q) on the "The program finished" bit.

    0 讨论(0)
提交回复
热议问题