What is the Python equivalent of `set -x` in shell?

后端 未结 3 2076
有刺的猬
有刺的猬 2020-12-04 19:16

Please suggest Python command which is equivalent of set -x in shell scripts.

Is there a way to print/log each source file line executed by Python?

3条回答
  •  孤城傲影
    2020-12-04 20:00

    I liked @jcomeau_ictx's answer very much, but it has a small flaw, which is why I extended on it a bit. The problem is that jcomeau_ictx's 'traceit' function only works correctly if all code to be traced is within the file that is called with python file.py (let's call it the host file). If you call any imported functions, you get a lot of line numbers without code. The reason for this is that line = linecache.getline(sys.argv[0], lineno) always tries to get the line of code from the host file (sys.argv[0]). This can easily be corrected, as the name of the file that actually contains the traced line of code can be found in frame.f_code.co_filename. This now may produce a lot of output, which is why one probably would want to have a bit more control.

    There is also another bit to notice. According to the sys.settrace() documentation:

    The trace function is invoked (with event set to 'call') whenever a new local scope is entered

    In other words, the code to be traced has to be inside a function.

    To keep everything tidy, I decided to put everything into an own file called setx.py. The code should be pretty self-explanatory. There is, however, one piece of code that is needed for Python 3 compatibility, which deals with the differences between Python 2 and 3 with respect to how modules are imported. This is explained here. The code should now also work both with Python 2 and 3.

    ##setx.py
    from __future__ import print_function
    import sys, linecache
    
    ##adapted from https://stackoverflow.com/a/33449763/2454357
    ##emulates bash's set -x and set +x
    
    ##for turning tracing on and off
    TRACING = False
    
    ##FILENAMES defines which files should be traced
    ##by default this will on only be the host file 
    FILENAMES = [sys.argv[0]]
    
    ##flag to ignore FILENAMES and alwas trace all files
    ##off by default
    FOLLOWALL = False
    
    def traceit(frame, event, arg):
        if event == "line":
            ##from https://stackoverflow.com/a/40945851/2454357
            while frame.f_code.co_filename.startswith('

    I then test the functionality with this code:

    ##setx_tester.py
    from __future__ import print_function
    import os
    import setx
    from collections import OrderedDict
    
    import file1
    from file1 import func1
    import file2
    from file2 import func2
    
    def inner_func():
        return 15
    
    def test_func():
    
        x=5
        print('the value of x is', x)
    
        ##testing function calling:
        print('-'*50)
        ##no further settings
        print(inner_func())
        print(func1())
        print(func2())
    
        print('-'*50)
        ##adding the file1.py to the filenames to be traced
        ##it appears that the full path to the file is needed:
        setx.FILENAMES.append(file1.__file__)
        print(inner_func())
        print(func1())
        print(func2())
    
        print('-'*50)
        ##restoring original:
        setx.FILENAMES.pop()
    
        ##setting that all files should be traced:
        setx.FOLLOWALL = True
        print(inner_func())
        print(func1())
        print(func2())
    
    ##turn tracing on:
    setx.TRACING = True
    outer_test = 42  ##<-- this line will not show up in the output
    test_func()
    

    The files file1.py and file2.py look like this:

    ##file1.py
    def func1():
        return 7**2
    

    and

    ##file2.py
    def func2():
        return 'abc'*3
    

    The output then looks like this:

    setx_tester.py, 16:     x=5
    setx_tester.py, 17:     print('the value of x is', x)
    the value of x is 5
    setx_tester.py, 20:     print('-'*50)
    --------------------------------------------------
    setx_tester.py, 22:     print(inner_func())
    setx_tester.py, 12:     return 15
    15
    setx_tester.py, 23:     print(func1())
    49
    setx_tester.py, 24:     print(func2())
    abcabcabc
    setx_tester.py, 26:     print('-'*50)
    --------------------------------------------------
    setx_tester.py, 29:     setx.FILENAMES.append(file1.__file__)
    setx_tester.py, 30:     print(inner_func())
    setx_tester.py, 12:     return 15
    15
    setx_tester.py, 31:     print(func1())
    **path to file**/file1.py, 2:     return 7**2
    49
    setx_tester.py, 32:     print(func2())
    abcabcabc
    setx_tester.py, 34:     print('-'*50)
    --------------------------------------------------
    setx_tester.py, 36:     setx.FILENAMES.pop()
    setx_tester.py, 39:     setx.FOLLOWALL = True
    setx_tester.py, 40:     print(inner_func())
    setx_tester.py, 12:     return 15
    15
    setx_tester.py, 41:     print(func1())
    **path to file**/file1.py, 2:     return 7**2
    49
    setx_tester.py, 42:     print(func2())
    **path to file**/file2.py, 2:     return 'abc'*3
    abcabcabc
    

提交回复
热议问题