sys模块常用介绍 - 系统相关的参数和函数


sys模块提供了一些变量和函数,主要与解释器相关。这些变量可能被解释器使用,也可能由解释器提供;这些函数与解释器有很强的关系。本文总结其中常用的变量与函数,其他可参考 sys — 系统相关的参数和函数。本文演示案例的解释器版本为3.9

常量

  • sys.builtin_module_names,该解释器中内置模块名称的元组,即包含所有的被编译进Python解释器的模块

  • sys.copyright,与该解释器有关的版权声明

  • sys.exec_prefix,提供特定域的目录前缀字符串,用于查找特定机器的python库,该目录中安装了与平台相关的Python文件

    • 默认是 ‘/usr/local’
    • 该目录前缀可以在构建时使用 configure 脚本的 --exec-prefix 参数进行设置
    • 所有配置文件(如 pyconfig.h 头文件)都安装在目录 exec_prefix/lib/pythonX.Y/config 中
    • 共享库模块安装在 exec_prefix/lib/pythonX.Y/lib-dynload 中
    • 如果在一个虚拟环境中,该值将在 site.py 中被修改,指向虚拟环境。Python 安装位置仍然可以用 base_exec_prefix 来获取
  • sys.executable,解释器的二进制可执行文件的绝对路径,仅在部分系统中此值有意义。如果无法获取其可执行文件的真实路径,则将为空字符串或None

  • sys.float_info,带有关于float实现所需信息的具名元组。是关于精度和内部表示的底层信息,这些值与标准头文件 float.h 中为 C 语言定义的各种浮点常量对应,具体参考sys.float_info

  • sys.float_repr_style,反映 repr() 函数在浮点数上行为的字符串。 Python 3.1 及更高版本中,该字符串是 ‘short’,那么对于(非无穷的)浮点数 x,repr(x) 将会生成一个短字符串,满足 float(repr(x)) == x 的特性;否则 float_repr_style 的值将是 ‘legacy’,此时 repr(x) 的行为方式将与 Python 3.1 之前的版本相同

  • sys.hash_info,带有哈希算法信息的具名元组,给出数字类型的哈希的实现参数。具体参考sys.hash_info

  • sys.implementation,包含当前运行的 Python 解释器实现信息的对象

  • sys.int_info,包含Python 内部整数表示形式信息的具名元组

  • sys.maxsize,容器的最大支持长度,表示 Py_ssize_t 类型的变量可以取到的最大值。在 32 位平台上通常为 2**31 - 1,在 64 位平台上通常为 2**63 - 1

  • sys.platform,平台标识符。例如,该标识符可用于将特定平台的组件追加到 sys.path 中

  • sys.prefix,用于查找python库的前缀,即python的安装位置

  • sys.thread_info,带有关于线程(thread)实现所需信息的具名元组

  • sys.version,包含 Python 解释器版本号加编译版本号以及所用编译器等额外信息的字符串,此字符串会在交互式解释器启动时显示。注意,不应从该字符串中提取版本信息,而应使用 version_info 以及 platform 模块所提供的函数

  • sys.version_info,具名元组形式显示的版本信息

    l = [sys.builtin_module_names, sys.copyright, sys.exec_prefix, sys.executable, sys.float_info, sys.float_repr_style,
         sys.hash_info, sys.implementation,sys.int_info,sys.maxsize,sys.platform,sys.prefix, sys.thread_info,sys.version,sys.version_info]
    for i in l:
         print(i)
         print('*'*10)
    
    ('_abc', '_ast', '_codecs', '_collections', '_functools', '_imp', '_io', '_locale', '_operator', '_peg_parser', '_signal', '_sre', '_stat', '_string', '_symtable', '_thread', '_tracemalloc', '_warnings', '_weakref', 'atexit', 'builtins', 'errno', 'faulthandler', 'gc', 'itertools', 'marshal', 'posix', 'pwd', 'sys', 'time', 'xxsubtype')
    **********
    Copyright (c) 2001-2020 Python Software Foundation.
    All Rights Reserved.
    
    Copyright (c) 2000 BeOpen.com.
    All Rights Reserved.
    
    Copyright (c) 1995-2001 Corporation for National Research Initiatives.
    All Rights Reserved.
    
    Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
    All Rights Reserved.
    **********
    /opt/miniconda3/envs/py39
    **********
    /opt/miniconda3/envs/py39/bin/python3
    **********
    sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
    **********
    short
    **********
    sys.hash_info(width=64, modulus=2305843009213693951, inf=314159, nan=0, imag=1000003, algorithm='siphash24', hash_bits=64, seed_bits=128, cutoff=0)
    **********
    namespace(name='cpython', cache_tag='cpython-39', version=sys.version_info(major=3, minor=9, micro=0, releaselevel='final', serial=0), hexversion=50921712, _multiarch='darwin')
    **********
    sys.int_info(bits_per_digit=30, sizeof_digit=4)
    **********
    9223372036854775807
    **********
    darwin
    **********
    /opt/miniconda3/envs/py39
    **********
    sys.thread_info(name='pthread', lock='mutex+cond', version=None)
    **********
    3.9.0 (default, Nov 15 2020, 06:25:35) 
    [Clang 10.0.0 ]
    **********
    sys.version_info(major=3, minor=9, micro=0, releaselevel='final', serial=0)
    **********
    

变量

sys.argv

  • 是什么:一个列表,包含了被传递给 Python 脚本的命令行参数,即解释器读入的所有参数。注意,如果没有脚本名被传递给解释器,argv[0]为空字符串
  • argv第一个元素是什么,包括以下情况
    • 用文件名作为参数执行脚本文件。argv[0]为文件名
      (base) 192:~ yxt$ cat fruits.py 
      import sys
      
      print("this fruit is apple")
      print(sys.argv)
      (base) 192:~ yxt$ python fruits.py 
      this fruit is apple
      ['fruits.py']
      (base) 192:~ yxt$
      
    • 通过-c command执行命令, 可用换行符分隔多条语句。argv[0]被设置为"-c"
      (base) 192:~ yxt$ python -c "print('hello')"
      hello
      (base) 192:~ yxt$ python -c "import sys;print(sys.argv)"
      ['-c']
      (base) 192:~ yxt$
      
    • 通过-m module-name执行模块(即在sys.path中搜索指定模块,并以 __main__ 模块执行其内容)。argv[0]为完整路径名
      (base) 192:~ yxt$ cat fruits.py 
      import sys
      print(sys.argv)
      
      if __name__ == "__main__":
          print("this is fruits module")
      (base) 192:~ yxt$ python -m fruits
      ['/Users/yxt/fruits.py']
      this is fruits module
      (base) 192:~ yxt$ 
      

sys.modules

  • 是什么:一个字典,将模块名称映射到已加载的模块。可以操作该字典来强制重新加载模块,但替换的字典不一定会按预期工作
    >>> import sys
    >>> sys.modules
    {'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>, ...}
    

sys.path

  • 是什么:一个字符串列表,用于指定模块的搜索路径。初始化自环境变量PYTHONPATH,加上一个与依赖于安装的默认路径
  • 启动程序时将会初始化该列表,列表第一项path[0]是包含调用python解释器脚本的目录。注意,脚本目录在PYTHONPATH作为条目插入之前插入
    (base) 192:~ yxt$ env | grep "PYTHONPATH"
    (base) 192:~ yxt$ vim .bash_profile 
    (base) 192:~ yxt$ source .bash_profile
    (base) 192:~ yxt$ env | grep "PYTHONPATH"
    PYTHONPATH=/Users/yxt/Desktop
    (base) 192:~ yxt$ cat fruits.py 
    import sys
    
    print(sys.path)
    sys.path.append("/Users/yxt/Desktop/play")
    print(sys.path)
    (base) 192:~ yxt$ python fruits.py 
    ['/Users/yxt', '/Users/yxt/Desktop', '/opt/miniconda3/lib/python39.zip', '/opt/miniconda3/lib/python3.9', '/opt/miniconda3/lib/python3.9/lib-dynload', '/opt/miniconda3/lib/python3.9/site-packages']
    ['/Users/yxt', '/Users/yxt/Desktop', '/opt/miniconda3/lib/python39.zip', '/opt/miniconda3/lib/python3.9', '/opt/miniconda3/lib/python3.9/lib-dynload', '/opt/miniconda3/lib/python3.9/site-packages', '/Users/yxt/Desktop/play']
    (base) 192:~ yxt$
    
  • 若脚本目录不可用(例如解释器被交互方式调用,或从标准输入中读取脚本),则path[0]为空字符串,这会使python首先搜索当前目录的模块。例如,python交互模式下path[0]为空字符串
    >>> import sys
    >>> sys.path
    ['', '/Users/yxt/Desktop', '/opt/miniconda3/lib/python39.zip', '/opt/miniconda3/lib/python3.9', '/opt/miniconda3/lib/python3.9/lib-dynload', '/opt/miniconda3/lib/python3.9/site-packages']
    

sys.stdout

  • 是什么:stdout是解释器用于标准输出的文件对象。该文件流是常规的文本文件,与open函数返回的对象一致,其参数中的字符编码取决于各个平台
    import sys
    f = open(file='file.txt', mode='a')
    print(f, type(f))
    print(sys.stdout, type(sys.stdout))
    f.close()
    
    <_io.TextIOWrapper name='file.txt' mode='a' encoding='UTF-8'> <class '_io.TextIOWrapper'>
    <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'> <class '_io.TextIOWrapper'>
    
  • 如何理解stdout:详参 如何理解python中的sys.stdout和sys.stderr
    • 在C程序中,一般有三个默认打开的“文件”:stdin、stdout和stderr。在C中输入和输出时,默认情况下它们来自stdin和stdout。但也可以在代码需要文件的地方使用它们,或者将它们重新分配为新文件。
    • Python试图“模仿”C的这种行为,当调用print()时,输出文本将被写入sys.stdout;当做input()时,输入来自sys.stdin;异常被写入sys.stderr。
  • 用途:用于print()输出、input()的提示符、expression语句输出
    • print()传入参数file:如果file不存在或为 None,则默认为sys.stdout。即将print()的文本写入sys.stdout,实现解释器的标准输出
      def print(self, *args, sep=' ', end='\n', file=None): # known special case of print
          """
          print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
          
          Prints the values to a stream, or to sys.stdout by default.
          Optional keyword arguments:
          file:  a file-like object (stream); defaults to the current sys.stdout.
          sep:   string inserted between values, default a space.
          end:   string appended after the last value, default a newline.
          flush: whether to forcibly flush the stream.
          """
          pass
      
    • input()提示符:如果input()存在实参(prompt string),则将其写入标准输出(即将提示符写入sys.stdout),末尾不带换行符
      def input(*args, **kwargs): # real signature unknown
          """
          Read a string from standard input.  The trailing newline is stripped.
          
          The prompt string, if given, is printed to standard output without a
          trailing newline before reading input.
          
          If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
          On *nix systems, readline is used if available.
          """
          pass
      
    • expression语句的输出:表达式是可以求出某个值的语法单元,即一个表达式就是表达元素例如字面值、名称、属性访问、运算符或函数调用的汇总,它们最终都会返回一个值。 Python并非所有语言构件都是表达式,还存在不能被用作表达式的语句,例如 while、赋值等。详参 expression
      v = 2 * 3 / 0.5  # 表达式
      print(v)
      
  • sys.stdout与sys.__stdout__的关系
    • 程序开始时,__stdin__、__stderr__、__stdout__这些对象存有 stdin、stderr 和 stdout 的初始值。这些对象在程序结束前都可以使用,且在需要向实际的标准流打印内容时很有用,无论 sys.std* 对象是否已重定向。如果实际文件已经被覆盖成一个损坏的对象了,那它也可用于将实际文件还原成能正常工作的文件对象
    • 在Python中可以重新分配stdout这些变量,将代码输出重定向到stdout以外的文件对象,类似于shell的重定向概念
      sys.stdout = open(file='file.txt', mode='a')  # 重定向stdout到文件对象
      print('sys')  # 输出到文件对象
      sys.stdout.close()
      sys.stdout = sys.__stdout__  # stdout重定向到将初始值
      

sys.stdin

  • 是什么:stdin是解释器用于标准输入的文件对象
    import sys
    s = sys.stdin
    print(s, type(s))
    
    <_io.TextIOWrapper name='<stdin>' mode='r' encoding='utf-8'> <class '_io.TextIOWrapper'>
    
  • 用途:用于所有交互式输入(包括对 input() 的调用)。input()功能即按照规则从标准输入中读取字符串
    def input(*args, **kwargs): # real signature unknown
        """
        Read a string from standard input.  The trailing newline is stripped.
        
        The prompt string, if given, is printed to standard output without a
        trailing newline before reading input.
        
        If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
        On *nix systems, readline is used if available.
        """
        pass
    
  • sys.stdin.readline()与input()的关系:sys.stdin.readline()与input()都是以换行为结束输入的标志。区别为前者会将标准输入全部读取包括尾部换行符’\n‘,后者会将尾部换行符删除
    import sys
    a = sys.stdin.readline()  # 实现标准输入
    print(a, type(a))  # 读取标准输入所有内容,包括换行符
    b = input('输入内容:')  # input函数实现标准输入,即input从标准输入读取内容
    print(b)  # 标准输入所有内容,但去除尾部换行符
    print(f'sys.stdin.readline()读取内容长度为{len(a)}, input读取内容长度为{len(b)}')
    
    ab  # 标准输入
    ab
     <class 'str'>
    输入内容:ab  # input()输入
    ab
    sys.stdin.readline()读取内容长度为3, input读取内容长度为2  # 字符串长度不同
    
  • 标准输入读取的三种方式:sys.stdin.read、sys.stdin.readline、sys.stdin.readlines
    • read可一次性读取多行内容,不读出换行符,换行符直接在字符串内起作用,mac中以command+D结束读取。注意,read为读取多行内容,所以最后输入为换行符,否则不会读取最后的内容
      import sys
      a = sys.stdin.read()
      # a = sys.stdin.read().splitlines()  # 返回列表,元素为每行内容且不含每行尾部换行符
      print(a, len(a))
      
      ab  # 标准输入
      cd^D  # 标准输入。没有换行,以command+D结束
      ab
       3  # 输出存在换行,但没有最后的内容
      
      ab  # 标准输入
      cd  # 标准输入。存在换行
      ^D  # 以command+D结束
      ab
      cd  # 读取最后一行内容成功
       6
      
    • readline只能读取单行内容,以换行符结束读取。不读出换行符,换行符直接在字符串内起作用
    • readlines会读取多行内容,返回结果为列表,元素为每行内容包括换行符,会读出换行符,mac中以command+D结束读取。注意readlines为读取多行内容,最后输入需为换行符
      import sys
      a = sys.stdin.readlines()
      print(a)
      
      ab  # 标准输入
      cd^D  # 标准输入。没有换行,直接以command+D结束
      ['ab\n']  # 列表,每行内容为元素,读出换行符
      
      ab  # 标准输入
      cd  # 标准输入。存在换行
      ^D  # 以command+D结束
      ['ab\n', 'cd\n']  # 列表,元素为每行内容(包括换行符),换行符会读出
      

sys.stderr

  • 是什么:stderr是解释器用于标准错误的文件对象
    import sys
    stderr = sys.stderr
    print(stderr, type(stderr))
    
    <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'> <class '_io.TextIOWrapper'>
    
  • 用途:解释器自身的提示符和它的错误消息都发往 stderr
    import sys
    sys.stderr = open(file='file.txt', mode='w')  # 将stderr重定向到另一个文件对象
    a = 3 / 0  # 使程序崩溃并将调试信息输出到stderr管道中
    sys.stderr.close()  # 关闭该文件对象
    sys.stderr = sys.__stderr__  # 将标准错文件对象重定向到初始标准错误文件对象
    

在这里插入图片描述

sys.last_type & sys.last_value & sys.last_traceback

  • 是什么:这三个变量并非总是有定义,仅当有异常未处理,且解释器打印了错误消息和堆栈回溯时,才会给它们赋值。变量含义与sys.exc_info() 返回值含义相同,type 是正在处理的异常类型(它是 BaseException 的子类);value 是异常实例(异常类型的实例);traceback 是一个 回溯对象,该对象封装了最初发生异常时的调用堆栈
  • 预期用途:允许交互中的用户导入调试器模块,进行事后调试,而不必重新运行导致错误的命令,通常使用 import pdb; pdb.pm() 进入程序发生异常导致崩溃后的事后调试器,跟踪异常程序最后的堆在信息
    • 演示的脚本test.py

      n = 'a'
      r = n + 2
      print(n, r)
      
    • 通过 -i 参数执行脚本,会在运行脚本后进行交互式检查,即打开一个交互式shell窗口

      python -i test.py
      
    • 运行脚本后打开的交互式窗口

      (py39) 192:标准库 yxt$ python -i sys模块.py 
      Traceback (most recent call last):
        File "/Users/yxt/Desktop/Exercises/标准库/sys模块.py", line 50, in <module>
          r = n + 2
      TypeError: can only concatenate str (not "int") to str
      >>> sys.last_type, sys.last_value, sys.last_traceback  # 异常未处理,已打印错误消息和堆栈回溯,此时 last_type & last_value & last_traceback 存在值
      (<class 'TypeError'>, TypeError('can only concatenate str (not "int") to str'), <traceback object at 0x7fe9a9542500>)
      >>> import pdb;pdb.pm()  # 进入事后调试器
      > /Users/yxt/Desktop/Exercises/标准库/sys模块.py(50)<module>()
      -> r = n + 2  # 导致程序崩溃的语句
      (Pdb) p n  # 打印变量n
      'a'
      (Pdb) q  # 退出pdb窗口
      >>> quit()  # 退出交互式窗口
      (py39) 192:标准库 yxt$ 
      

函数

sys._current_frames()

  • 是什么:该函数返回一个字典,存放着每个线程的标识符与(调用本函数时)该线程栈顶的帧(当前活动的帧)之间的映射
  • 用途举例:用于调试死锁,因为该函数不需要死锁线程的配合,只要这些线程的调用栈保持死锁,它们就是冻结的。当在调用本代码来检查栈顶的帧的那一刻,非死锁线程返回的帧可能与该线程当前活动的帧没有任何关系
    import sys, time
    
    if sys.version_info.major == 2:  # Python 2
        import thread
    else:  # Python 3
        import _thread as thread
    lock1 = thread.allocate_lock()  # 资源R1
    lock2 = thread.allocate_lock()  # 资源R2
    
    
    def thread_entry_A():  # 线程A的入口函数
        global lock1, lock2
        print("Thread A: Before lock1 Acquire")
        lock1.acquire()  # 得到资源R1
        print("Thread A: After lock1 Acquire")
        time.sleep(3)
        print("Thread A: Before lock2 Acquire")
        lock2.acquire()  # 申请资源R2,死锁在这里
        print("Thread A: After lock2 Acquire")
        lock1.release()  # 释放资源R1
        lock2.release()  # 释放资源R2
    
    
    def thread_entry_B():  # 线程B的入口函数
        global lock1, lock2
        print("Thread B: Before lock2 Acquire")
        lock2.acquire()  # 得到资源R2
        print("Thread B: After lock2 Acquire")
        time.sleep(3)
        print("Thread B: Before lock1 Acquire")
        lock1.acquire()  # 申请资源R1,死锁在这里
        print("Thread B: After lock1 Acquire")
        lock1.release()  # 释放资源R1
        lock2.release()  # 释放资源R2
    
    
    def start_threads():
        thread.start_new_thread(thread_entry_A, tuple())
        thread.start_new_thread(thread_entry_B, tuple())
        time.sleep(5)
        for i, frame in sys._current_frames().items():
            print(f'线程标识符 {i}, 栈顶帧中的文件名 {frame.f_code.co_filename}、函数名 {frame.f_code.co_name}、行号 {frame.f_lineno}')
        print("Main Thread Quit")  # 主线程退出,进程也退出
    
    
    if __name__ == '__main__':
        start_threads()
    
    Thread A: Before lock1 Acquire
    Thread A: After lock1 Acquire
    Thread B: Before lock2 Acquire
    Thread B: After lock2 Acquire
    Thread B: Before lock1 AcquireThread A: Before lock2 Acquire
    
    线程标识符 123145400258560, 栈顶帧中的文件名 /Users/yxt/Desktop/Exercises/标准库/test.py、函数名 thread_entry_B、行号 31
    线程标识符 123145383469056, 栈顶帧中的文件名 /Users/yxt/Desktop/Exercises/标准库/test.py、函数名 thread_entry_A、行号 18
    线程标识符 4643649024, 栈顶帧中的文件名 /Users/yxt/Desktop/Exercises/标准库/test.py、函数名 start_threads、行号 42
    Main Thread Quit
    

sys._getframe([depth])

  • 是什么:该函数返回来自调用栈的一个帧对象,depth 的默认值是 0,返回调用栈顶部的帧
  • 传入参数:如果传入可选整数 depth,则返回从栈顶往下相应调用层数的帧对象。如果该数比调用栈更深,则抛出 ValueError
  • 注意: 这个函数应该只在内部为了一些特定的目的使用。不保证它在所有 Python 实现中都存在
  • 用途举例:可用于定位追踪问题,确定程序的调用链,打印程序执行过程中的每一个函数调用,包括函数名称、所在文件等
    • 通过sys._getframe()获取当前调用栈的所有帧对象,可以得到详细的调用信息。但只能做到从调用链最深处获得调用信息,即获取sys._getframe()执行前的函数调用信息,不能得到之后的函数调用信息
      import sys
      
      
      def func1():
          print('ok')
      
      
      def func2():
          frame = sys._getframe()
          print(f'调用栈顶部帧对象:{frame}, 文件名:{frame.f_code.co_filename}, 函数名:{frame.f_code.co_name}, 行号:{frame.f_lineno}')
          frame = sys._getframe(1)  # 从栈顶向下的深度
          print(
              f'堆栈栈栈顶向下第二个帧对象:{frame}, 文件名:{frame.f_code.co_filename}, 函数:{frame.f_code.co_name}, 行号:{frame.f_lineno}')
          func1()
      
      
      def func3():
          func2()
      
      
      def func4():
          func3()
      
      
      func4()
      
      调用栈顶部帧对象:<frame at 0x7f85b8216cf0, file '/Users/yxt/Desktop/Exercises/标准库/test.py', line 10, code func2>, 文件名:/Users/yxt/Desktop/Exercises/标准库/test.py, 函数名:func2, 行号:10
      堆栈栈栈顶向下第二个帧对象:<frame at 0x7f85b80d9580, file '/Users/yxt/Desktop/Exercises/标准库/test.py', line 18, code func3>, 文件名:/Users/yxt/Desktop/Exercises/标准库/test.py, 函数:func3, 行号:18
      ok
      
    • 通过sys.setprofile(profilefunc)设置性能分析函数,即可在调用某函数和从某函数返回时,执行性能分析函数,得到函数的全部调用链及信息

sys.breakpointhook()

  • 是什么:本钩子函数由内建函数 breakpoint() 调用,默认情况下,它将进入 pdb 调试器,但可以将其改为任何其他函数,以选择使用哪个调试器
  • 默认情况下调用breakpoint() :sys.breakpointhook() 调用 pdb.set_trace() 且没有参数
    • 查询环境变量PYTHONBREAKPOINT,若为"0"则breakpointhook() 立即返回,表示在断点无操作
      import os
      
      os.environ['PYTHONBREAKPOINT'] = '0'
      print('run')
      breakpoint()  # 该断点处无操作
      print('finish')
      
    • 若PYTHONBREAKPOINT为未设置或为空字符串,则调用pdb.set_trace()
      import os
      
      os.environ['PYTHONBREAKPOINT'] = ''
      print('run')
      breakpoint()  # 进入pdb.set_trace()调试器
      print('finish')
      
  • PYTHONBREAKPOINT指定运行函数:指定函数以package.subpackage.module.function方式导入,当breakpoint()运行时将*args 和 **kws 传入breakpointhook(),且无论function()返回什么,sys.breakpointhook() 都将返回到內建函数 breakpoint()
    • 如果在导入 PYTHONBREAKPOINT 指定的可调用对象时出错,则将报告一个 RuntimeWarning 并忽略断点
    • 如果以编程方式覆盖sys.breakpointhook(),则不会查询PYTHONBREAKPOINT
      # debugFunc.py文件
      import sys, time
      
      
      def func(*arg, **kwargs):
          while True:
              time.sleep(0.25)
              s = input('请输入:\n')
              if s not in ['a', 'A', 'k', 'K', 'q', 'Q']:
                  print('请输入"a"查看位置参数、"k"来查看关键字参数、"q"退出调试器', file=sys.stderr)
              elif s in ['a', 'A']:
                  print(arg)
              elif s in ['k', 'K']:
                  print(kwargs)
              elif s in ['q', 'Q']:
                  break
      
      import os
      
      
      def f():
          os.environ['PYTHONBREAKPOINT'] = '标准库.debugFunc.func'  # 调用debugFunc模块的func函数
          # os.environ['PYTHONBREAKPOINT'] = '标准库.debugFunc.func2'  # func2不存在,发生报错并忽略断点
          breakpoint('1', '2', '3', v='6')
          print('end')
      
      
      f()
      
      请输入:
      l
      请输入"a"查看位置参数、"k"来查看关键字参数、"q"退出调试器
      请输入:
      a
      ('1', '2', '3')
      请输入:
      k
      {'v': '6'}
      请输入:
      q
      end
      

sys.displayhook(value)

  • 是什么:使用repr函数处理value,将处理结果写入标准输出,并保存到builtins._ 。以下为实现函数功能的伪代码:
    def displayhook(value):
        if value is None:
            return
        # Set '_' to None to avoid recursion
        builtins._ = None
        text = repr(value)  # 将value转为规范字符串表现形式
        try:
            sys.stdout.write(text)  # 写入标准输入
        except UnicodeEncodeError:  # Unicode编码错误,可能是sys.stdout.errors错误处理方案为'strict',所以遇到编码错误时抛出该异常
            bytes = text.encode(sys.stdout.encoding, 'backslashreplace')  # 将字符串编码,错误处理方案设置为'backslashreplace',即使用反斜杠(\)开始的转义字符名称序列来替换未能转换的字符
            if hasattr(sys.stdout, 'buffer'):  # 若有底层二进制'buffer'对象,则在标准流写入二进制数据
                sys.stdout.buffer.write(bytes)
            else:  # 将编码数据解码,并写入标准流
                text = bytes.decode(sys.stdout.encoding, 'strict')
                sys.stdout.write(text)
        sys.stdout.write("\n")
        builtins._ = value
    
  • 用途:在交互式会话中运行表达式产生结果后,将在结果上调用该函数。若要自定义处理结果,也可将将display指向一个单参数函数
    >>> import sys
    >>> def displayhook2(value):
        	    text = '规范字符串:' + repr(value)
        	    sys.stdout.write(text)
        	    sys.stdout.write("\n")
    ... 
    >>> sys.displayhook = displayhook2
    >>> 2 + 3
    规范字符串:5
    

sys.excepthook(type, value, traceback)

  • 是什么:一个处理任何未捕获异常(除SystemExit之外的)的函数,用于将所给的回溯与异常输出到stderr

    def excepthook(*args, **kwargs): # real signature unknown
        """ Handle an exception by displaying it with a traceback on sys.stderr. """
        pass
    
  • 传入参数:type是正在处理的异常类(它是 BaseException 的子类),value 是异常实例(异常类型的实例),traceback 是一个回溯对象,该对象封装了最初发生异常时的调用堆栈

    import sys
    
    
    def p():
        return 1 / 0
    
    
    def my_excepthook(ttype, value, traceback):
        print("异常类型:{}, {}".format(ttype, type(ttype)))
        print("异常实例:{}, {}".format(value, type(value)))
        print("回溯对象:{}, {}, {}".format(traceback, type(traceback), dir(traceback)))
    
    
    sys.excepthook = my_excepthook  # 自定义函数处理顶级异常
    p()
    
    异常类型:<class 'ZeroDivisionError'>, <class 'type'>
    异常实例:division by zero, <class 'ZeroDivisionError'>
    回溯对象:<traceback object at 0x7fc85558f200>, <class 'traceback'>, ['tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next']
    
  • 函数调用:当程序抛出一个未被捕获的异常时,解释器会调用excepthook函数,将其获得的回溯与异常信息输出到stderr。在交互式会话中,这会在控制权返回到提示符之前发生;在 Python 程序中,这会在程序退出之前发生

  • 自定义该类顶级异常处理过程:可以将接受3个参数的函数赋给 sys.excepthook。自定义函数处理traceback对象输出异常信息可行但比较麻烦,输出信息可读性较差;可使用traceback模块内置的辅助函数来提取异常信息。以下为异常在一系列嵌套较深的函数调用中引发,函数在不同模块

    # module2.py
    def m():
        return 1 / 0
    
    
    def n():
        m()
    
    import sys
    from module2 import n
    
    
    def p():
        n()
    
    
    def myExcepthook(ttype, value, traceback):
        print("异常类型:{}".format(ttype))
        print("异常实例:{}".format(value))
        i = 1
        while traceback:  # 链表结构
            print("第{}层堆栈信息".format(i))
            print('异常追踪行数:{}'.format(traceback.tb_lineno))
            tracebackCode = traceback.tb_frame.f_code  # 异常信息位置
            print("文件名:{}".format(tracebackCode.co_filename))
            print("函数或者模块名:{}".format(tracebackCode.co_name))
            traceback = traceback.tb_next  # 下一个回溯对象
            i += 1
    
    
    sys.excepthook = myExcepthook
    p()
    
    异常类型:<class 'ZeroDivisionError'>
    异常实例:division by zero
    第1层堆栈信息
    异常追踪行数:24
    文件名:/Users/yxt/Desktop/Exercises/标准库/sys模块.py
    函数或者模块名:<module>
    第2层堆栈信息
    异常追踪行数:6
    文件名:/Users/yxt/Desktop/Exercises/标准库/sys模块.py
    函数或者模块名:p
    第3层堆栈信息
    异常追踪行数:6
    文件名:/Users/yxt/Desktop/Exercises/标准库/module2.py
    函数或者模块名:n
    第4层堆栈信息
    异常追踪行数:2
    文件名:/Users/yxt/Desktop/Exercises/标准库/module2.py
    函数或者模块名:m
    

sys.exc_info()

  • 是什么:该函数返回当前正在处理的异常的信息。返回的信息仅限于当前线程和当前堆栈帧;若当前堆栈帧没有正在处理的异常,则从下级被调用的堆栈帧或上级调用者等位置获取,直到找到正在处理异常(即执行except子句)的堆栈帧;若整个堆栈都没有正在处理的异常,则返回包含三个 None 值的元组。用法为在except子句中调用该函数,可返回当前正在处理的异常信息
    • 堆栈:是一块连续的内存,堆栈中的内存单元在函数生命周期内可以使用,当函数返回时内存单元会被释放(释放后其它函数就可以用)
    • 堆栈帧:在堆栈中对当前正在运行的函数分配的区域,包括传入的参数、返回地址(函数结束后必须跳转到该地址,即主调函数的断点处)、函数所用的内部存储单元(函数存储在堆栈上的局部变量),详参:什么是堆栈帧
  • 返回值:type 是正在处理的异常类型(它是 BaseException 的子类),value 是异常实例(异常类型的实例),traceback 是一个 回溯对象,该对象封装了最初发生异常时的调用堆栈
  • 几种获取异常信息的方式
    • try…except…语句,捕获异常类型与描述
      import sys
      
      f = lambda x: x / 0
      try:
          f(5)
      except Exception as e:
          print(e, type(e))
      
      division by zero <class 'ZeroDivisionError'>
      
    • 在except子句中,sys.exc_info()获取异常信息,处理异常回溯对象
      import sys
      
      f = lambda x: x / 0
      try:
          f(5)
      except Exception as e:
          ttype, tvalue, ttraceback = sys.exc_info()
          print(ttype, tvalue, ttraceback)
          while ttraceback:
              print('异常追踪行数:{}'.format(ttraceback.tb_lineno))
              tracebackCode = ttraceback.tb_frame.f_code
              print("文件名:{}".format(tracebackCode.co_filename))
              print("函数或者模块名:{}".format(tracebackCode.co_name))
              ttraceback = ttraceback.tb_next
      
      <class 'ZeroDivisionError'> division by zero <traceback object at 0x7f7f07bbbd00>
      文件名:/Users/yxt/Desktop/Exercises/标准库/sys模块.py, 函数或者模块名:<module>, 异常追踪行数:5
      文件名:/Users/yxt/Desktop/Exercises/标准库/sys模块.py, 函数或者模块名:<lambda>, 异常追踪行数:3
      
    • 在except子句中,sys.exc_info()获取异常信息,traceback模块处理异常回溯对象
      import sys, traceback
      
      f = lambda x: x / 0
      try:
          f(5)
      except Exception as e:
          ttype, tvalue, ttraceback = sys.exc_info()
          print(ttype, tvalue, ttraceback)
          traceback.print_tb(ttraceback)
      
      <class 'ZeroDivisionError'> division by zero <traceback object at 0x7fb3bc3b2640>
        File "/Users/yxt/Desktop/Exercises/标准库/sys模块.py", line 5, in <module>
          f(5)
        File "/Users/yxt/Desktop/Exercises/标准库/sys模块.py", line 3, in <lambda>
          f = lambda x: x / 0
      
    • 通过traceback模块获取异常信息
      import traceback
      
      f = lambda x: x / 0
      try:
          f(5)
      except Exception as e:
          traceback.print_exc()
      
      Traceback (most recent call last):
        File "/Users/yxt/Desktop/Exercises/标准库/sys模块.py", line 5, in <module>
          f(5)
        File "/Users/yxt/Desktop/Exercises/标准库/sys模块.py", line 3, in <lambda>
          f = lambda x: x / 0
      ZeroDivisionError: division by zero
      

sys.exit([arg])

  • 是什么:抛出一个SystemExit异常,发出退出解释器的信号。可用于退出程序、暂时终止程序等情况
  • 传入参数:可选参数 arg 可以是表示退出状态的整数(默认为 0),也可以是其他类型对象
    • 传入参数是整数,则 shell 等将 0 视为“成功终止”,非零值(1-127)视为“异常终止”。Unix 程序通常用 2 表示命令行语法错误,用 1 表示所有其他类型的错误。
    • 传入参数是其他类型对象,如果传入 None 等同于传入 0,如果传入其他对象则将其打印至 stderr,且退出代码为 1。注意,sys.exit(“some error message”) 可以在发生错误时快速退出程序
      import sys
      
      # 不传入、传入0、传入None,都等同于传入0,代表 成功终止
      sys.exit()
      sys.exit(0)
      sys.exit(None)
      
      # 整数类型非0(1-127),代表 异常终止
      sys.exit(1)
      
      # 会将传入参数打印至标准错误,且退出代码为 1
      sys.exit('some error message')
      
  • SystemExit异常:由 sys.exit() 函数引发,继承自 BaseException 而不是 Exception以确保不会被处理 Exception 的代码意外捕获,这允许此异常正确地向上传播并导致解释器退出。对 sys.exit() 的调用会被转换为一个异常以便能执行清理处理程序 (try 语句的 finally 子句)
    import sys
    
    try:
        print('run')
        sys.exit('some error message')
    except Exception as e:  # 不能捕获SystemExit异常,在执行清理处理程序后(finally 子句),退出解释器
        print(e)
        print('exception class')
    finally:
        print('end')
    print('out')  # 不会执行该行
    
    run
    end
    some error message
    
    • SystemExit异常未被处理,Python 解释器就将退出,不会打印任何栈回溯信息
    • 捕获SystemExit异常,执行一些清理操作,并继续执行后面程序
      import sys
      
      try:
          print('run')
          sys.exit('some error message')
      except SystemExit as e:  # 捕获SystemExit异常,执行清理处理程序后,正常运行后面程序
          print(e)
          print('SystemExit:基类为BaseException')
      except Exception as e:
          print(e)
          print('exception class')
      finally:
          print('end')
      print('out')
      
      run
      some error message
      SystemExit:基类为BaseException
      end
      out
      
  • 3.6版本更改:在 Python 解释器捕获 SystemExit 后,如果在清理中发生错误(如清除标准流中的缓冲数据时出错),则退出状态码将变为 120

sys.getallocatedblocks()

  • 是什么:返回解释器当前已分配的内存块数,无论它们大小如何。如果当前 Python 构建或实现无法合理地计算此信息,允许 getallocatedblocks() 返回 0
  • 用途:本函数主要用于跟踪和调试内存泄漏。因为解释器有内部缓存,所以不同调用之间结果会变化。可能需要调用 _clear_type_cache() 和 gc.collect() 使结果更容易预测

sys.getrefcount(object)

  • 是什么:返回 object 的引用计数。返回的计数通常比预期的多一,因为它包括了作为 getrefcount() 参数的这一次(临时)引用。
  • 引用计数:记录该对象当前被引用的次数,每当新的引用指向该对象时,它的引用计数加1,每当该对象的引用失效时计数减1,一旦对象的引用计数为0,该对象立即被回收。引用计数加1存在以下情况
    • 对象被创建。注意,Python启动解释器时会创建一个小整数池,在-5~256之间的整数对象会被自动加载到内存中等待调用,因此该范围的整数对象引用计数会加1
    • 对象被引用
    • 对象作为参数传递到函数中
    • 对象作为元素存储到容器中

sys.getsizeof(object[, default])

  • 是什么:用于返回对象的大小(以字节为单位)的函数。该函数只计算直接分配给对象的内存消耗,不计算它所引用的对象的内存消耗。当对象不提供计算大小的方法时,如果传入过 default 则返回它,否则抛出 TypeError 异常
  • 传入参数:object,该对象可以是任何类型
    • 所有内建对象返回的结果都是正确的,但对于第三方扩展不一定正确,因为这与具体实现有关
    • 如果对象由垃圾回收器管理,则 getsizeof() 将调用对象的 __sizeof__ 方法,并在上层添加额外的垃圾回收器
  • 如何计算容器及其所有物的大小:参考 COMPUTE MEMORY FOOTPRINT OF AN OBJECT AND ITS CONTENTS
    • 处理方法:递归调用getsizeof()来计算容器及其所有物的大小。通过处理器将容器元素、对象属性转为生成器,使用map函数调用getsizeof()来处理生成器生成的每个元素
    • 自定义对象计算方式:计算对象及其属性值的大小。当__slots__在类中定义时,__dict__在实例中不会存在,根据类的__slots__定义的所有属性来获取实例的所有属性。当__slots__不存在时,通常存在__dict__,根据实例的__dict__来获取实例的所有属性。当对象没有__dict__、__dict__意味着没有属性,sys.getsizeof()返回实际的正确值
    • __slot__简介:允许显示声明数据成员,并拒绝创建__dict__和__weakref__(除非在__slots__中明确声明或在父节点中可用),可理解为阻止类动态创建属性。
      • 当实例化对象达到数百万个,使用__slots__能大幅节省内存,显著提高属性查找速度。因为Python在各个实例中__dict__ 的字典里存储实例属性,为了使用底层的散列表提升访问速度,字典会消耗大量内存,但__slots__会使其在元组中存储实例属性
      from __future__ import print_function
      from sys import getsizeof, stderr
      from itertools import chain
      from collections import deque
      
      try:
          from reprlib import repr
      except ImportError:
          pass
      
      
      def total_size(o, handlers={}, verbose=False):
          dict_handler = lambda d: chain.from_iterable(d.items())
          all_handlers = {tuple: iter,
                          list: iter,
                          deque: iter,
                          dict: dict_handler,
                          set: iter,
                          frozenset: iter,
                          }
          all_handlers.update(handlers)  # user handlers take precedence
          seen = set()  # track which object id's have already been seen
          default_size = getsizeof(0)  # estimate sizeof object without __sizeof__
      
          def sizeof(o):
              if id(o) in seen:  # do not double count the same object
                  return 0
              seen.add(id(o))
              s = getsizeof(o, default_size)
      
              if verbose:
                  print(s, type(o), repr(o), file=stderr)
      
              for typ, handler in all_handlers.items():
                  if isinstance(o, typ):
                      s += sum(map(sizeof, handler(o)))  # 处理每个容器的元素、对象的属性
                      break
              return s
      
          return sizeof(o)
      
      
      class userClass(object):
          def __init__(self):
              self.integer = 1
              self.integer2 = 20
              self.array = [['sd', 'ds'], 32]
      
      
      def myHandler(obj):  # 将对象所有属性变为迭代器并返回
          if not hasattr(obj.__class__, '__slots__'):  # 当对象不包含 __slots__时,通常意味着包含__dict__,但一些特殊的内置类两者都没有(表示sys.getsizeof()事实上返回了正确的值)
              if hasattr(obj, '__dict__'):
                  return iter([i for i in obj.__dict__.values()])
              return []
          return iter([getattr(obj, i) for i in obj.__class__.__slots__ if hasattr(obj, i)])  # 根据类中定义的__slots__,获取实例中存在属性
      
      
      myClass = userClass()
      handlers = {userClass: myHandler}
      d = dict(a=1, b=2, c=3, d=[4, 5, 6, 7], e='a string of chars', k=myClass)
      print(total_size(d, handlers=handlers, verbose=True))
      

sys.intern(string)

  • 是什么:该函数将 string 插入 “interned” (驻留)字符串表,返回被插入的字符串(string 本身或副本)。驻留字符串对提高字典查找的性能很有用,如果字典中的键已驻留,且所查找的键也已驻留,则键(after hashing)的比较可以用指针代替字符串来比较(比较内存地址而非内容)。Python 程序使用到的名称会被自动驻留,且用于保存模块、类或实例属性的字典的键也已驻留,参考python sys.intern是做什么的
  • 注意:驻留字符串不是永久存在的,对 intern() 返回值的引用必须保留下来,才能发挥驻留字符串的优势
    >>> import sys
    >>> a = sys.intern('why do pangolins dream of quiche')
    >>> b = sys.intern('why do pangolins dream of quiche')  # 不会重新创建,返回interned表中插入的字符串
    >>> b is a
    True
    >>> c = 'why do pangolins dream of quiche'
    >>> c is a
    False
    >>> 
    

sys.setprofile(profilefunc) & sys.getprofile()

  • 是什么
    • setprofile(profilefunc)是一个函数,用于设置系统的性能分析函数
    • getprofile()是一个函数,返回由 setprofile(profilefunc) 设置的性能分析函数
  • 性能分析函数
    • 是什么:在 Py​​thon 中能够实现一个 Python 源代码性能分析器。性能分析函数是特定于单个线程的,由于性能分析器无法得知线程之间的上下文切换,因此在存在多个线程的情况下使用它是没有意义的。因为函数的返回值不会被用到,所以可以简单地返回 None。性能分析函数中的错误将导致其自身被解除设置。
    • 调用方式:类似于系统的跟踪函数(参阅 settrace() ),但性能分析函数是通过不同的事件调用的,仅在调用某函数和从某函数返回时才会调用性能分析函数,但即使某函数发生异常也会算作返回事件
    • 传入参数:frame为当前的堆栈帧。event 是一个字符串,包括’call’、‘return’、‘c_call’、‘c_return’ 或 ‘c_exception’,arg 取决于event。以下为event事件含义
      • call 表示调用了某个函数(或进入了其他的代码块)。性能分析函数将被调用,arg 为 None
      • return 表示某个函数(或别的代码块)即将返回。性能分析函数将被调用,arg 是即将返回的值,如果此次返回事件是由于抛出异常,arg 为 None
      • c_call 表示即将调用某个 C 函数,它可能是扩展函数或是内建函数。arg 是 C 函数对象
      • c_return 表示返回了某个 C 函数。arg 是 C 函数对象
      • c_exception 表示某个 C 函数抛出了异常。arg 是 C 函数对象
    • 用途举例:由于调用某函数和从某函数返回时才会调用性能分析函数,因此可以获取程序中的函数调用过程,例如获取某段源码中的函数调用过程,参考神奇的python追踪术—sys.setprofile
      import sys
      
      CALL_EVENT_LIST = []
      
      
      class FileFilter():
          """
          tracefunc会处理每一次函数的调用和退出,包括第三方库和python内置库里的函数,这会导致搜集的信息过多。
          对当前调用栈的函数所在文件名称,通过此类函数筛选指定区域的代码
          """
      
          @staticmethod
          def filter(filename):
              return True
      
          @staticmethod
          def old_filter(filename):
              return True
      
      
      def tracefunc(frame, event, arg):
          if event == "call":
              tracefunc.stack_level += 1
      
              unique_id = frame.f_code.co_filename + str(frame.f_lineno)
              if unique_id in tracefunc.memorized:
                  return
      
              # Part of filename MUST be in white list.
              if 'self' in frame.f_locals:
                  class_name = frame.f_locals['self'].__class__.__name__
                  func_name = class_name + '.' + frame.f_code.co_name
              else:
                  func_name = frame.f_code.co_name
      
              func_name = '{indent}{name}'.format(
                  indent=tracefunc.stack_level * 2 * '-', name=func_name)
      
              if not FileFilter.filter(frame.f_code.co_filename):
                  return
      
              frame_back = frame.f_back  # 获取调用函数时的信息
              txt = '{: <40} # {}, {}, {}, {}'.format(
                  func_name, frame.f_code.co_filename, frame.f_lineno, frame_back.f_code.co_filename, frame_back.f_lineno)
      
              CALL_EVENT_LIST.append(txt)
      
              tracefunc.memorized.add(unique_id)
      
          elif event == "return":
              tracefunc.stack_level -= 1
      
      
      def start(filter_func=None):  # 设置过滤器、性能分析函数
          if filter_func:
              FileFilter.filter = filter_func
      
          tracefunc.memorized = set()  # 同一行代码多次调用时,只记录第一次调用
          tracefunc.stack_level = 0  # 标注函数调用层数
          CALL_EVENT_LIST.clear()
          sys.setprofile(tracefunc)  # 设置性能函数
      
      
      def output():  # 输出函数调用过程
          for text in CALL_EVENT_LIST:
              print(text)
      
          CALL_EVENT_LIST.clear()
      
      
      def stop():
          def do_nothing(frame, event, arg):
              pass
      
          FileFilter.filter = FileFilter.old_filter  # 过滤器清空
          sys.setprofile(do_nothing)  # 关闭性能分析函数
      
      
      def func1():
          print('ok')
      
      
      def func2():
          func1()
      
      
      def func3():
          func2()
      
      
      def func4():
          func3()
      
      
      def file_filter(filename):
          if filename.find('calc') != -1:
              return True
          return False
      
      
      start(filter_func=lambda x: x.find(__file__) != -1)  # lambda函数为过滤函数
      func4()
      stop()
      output()
      
      ok
      func4                                    # /Users/yxt/Desktop/Exercises/标准库/sys模块.py, 91, /Users/yxt/Desktop/Exercises/标准库/sys模块.py, 102
      --func3                                  # /Users/yxt/Desktop/Exercises/标准库/sys模块.py, 87, /Users/yxt/Desktop/Exercises/标准库/sys模块.py, 92
      ----func2                                # /Users/yxt/Desktop/Exercises/标准库/sys模块.py, 83, /Users/yxt/Desktop/Exercises/标准库/sys模块.py, 88
      ------func1                              # /Users/yxt/Desktop/Exercises/标准库/sys模块.py, 79, /Users/yxt/Desktop/Exercises/标准库/sys模块.py, 84
      stop                                     # /Users/yxt/Desktop/Exercises/标准库/sys模块.py, 71, /Users/yxt/Desktop/Exercises/标准库/sys模块.py, 103
      

sys.setrecursionlimit(limit) & sys.getrecursionlimit()

  • 是什么
    • setrecursionlimit(limit)将 Python 解释器堆栈的最大深度设置为 limit
    • getrecursionlimit()返回当前的递归限制值,即 Python 解释器堆栈的最大深度。该限制可以防止无限递归导致的 C 堆栈溢出和 Python 崩溃
  • 注意事项:不同平台所允许的最高限值不同。当用户有需要深度递归的程序且平台支持更高的限值,可能就需要调高限值。进行该操作需要谨慎,因为过高的限值可能会导致崩溃。如果新的限值低于当前的递归深度,将抛出 RecursionError 异常
    >>> import sys
    >>> sys.getrecursionlimit()
    1000
    

sys.settrace(tracefunc) & sys.gettrace()

  • 是什么

    • sys.settrace(tracefunc)用于设置系统的跟踪函数,使得用户在 Python 中就可以实现 Python 源代码调试器。该函数是特定于单个线程的,若要让调试器支持多线程,可使用 threading.settrace()
    • sys.gettrace()会返回由 settrace() 设置的跟踪函数。该函数仅用于实现调试器,性能分析器,打包工具等。它的行为是实现平台的一部分,而不是语言定义的一部分,因此并非在所有 Python 实现中都可用
  • 传入参数:frame 是当前的堆栈帧,event 是一个字符串(事件名),包括’call’、‘line’、‘return’、‘exception’ 或 ‘opcode’,arg 取决于事件类型

    event含义arg返回值注意
    call表示调用了某个函数(或进入了其他的代码块),全局跟踪函数将被调用None返回值将指定局部跟踪函数
    line表示解释器即将执行新一行代码或重新执行循环条件,局部跟踪函数将被调用None返回值将指定新的局部跟踪函数工作原理详参见 Objects/lnotab_notes.txt。在该堆栈帧禁用每行触发事件,frame.f_trace_lines=False
    return表示某个函数(或别的代码块)即将返回,局部跟踪函数将被调用arg 是即将返回的值跟踪函数的返回值将被忽略若返回事件是由于抛出异常,则arg为None
    exception表示发生了某个异常,局部跟踪函数将被调用arg 是一个 (exception, value, traceback) 元组返回值将指定新的局部跟踪函数由于异常是在链式调用中传播的,所以每一级都会产生一个 ‘exception’ 事件
    opcode表示解释器即将执行一个新的操作码,局部跟踪函数将被调用None返回值将指定新的局部跟踪函数每操作码触发事件默认情况下都不发出:必须在堆栈帧上将 f_trace_opcodes 显式地设置为 True 来请求这些事件
  • 全局跟踪函数与局部跟踪函数:当发生call事件时,会调用全局跟踪函数(即设置的跟踪函数),其返回值将指定局部跟踪函数。当发生其他事件时,会调用局部跟踪函数,其返回值应为局部跟踪函数自身的引用(或对另一个函数的引用)。如果跟踪函数返回None,代表不需要跟踪当前的作用范围或停止跟踪起作用范围。如果跟踪函数出错,则该跟踪函数将被取消设置,类似于调用 settrace(None)

    import sys
    
    
    # 全局跟踪函数(call事件),本地跟踪函数也是该函数(其他事件)
    def my_tracer(frame, event, arg=None):
        s = 'global:' if event == 'call' else 'local:'
        s += f"堆栈帧地址 {id(frame)}, 传入参数 {arg},事件 {event} 发生在 {frame.f_code.co_name}{frame.f_lineno}行"
        print(s)
        return my_tracer
    
    
    def f2():
        return "GFG"
    
    
    def f1():
        return f2()
    
    
    sys.settrace(my_tracer)
    f1()
    
    global:堆栈帧地址 140662066957776, 传入参数 None,事件 call 发生在 f1 第16行
    local:堆栈帧地址 140662066957776, 传入参数 None,事件 line 发生在 f1 第17行
    global:堆栈帧地址 140662067273280, 传入参数 None,事件 call 发生在 f2 第12行
    local:堆栈帧地址 140662067273280, 传入参数 None,事件 line 发生在 f2 第13行
    local:堆栈帧地址 140662067273280, 传入参数 GFG,事件 return 发生在 f2 第13行
    local:堆栈帧地址 140662066957776, 传入参数 GFG,事件 return 发生在 f1 第17行
    
  • 显式设置局部跟踪函数:在堆栈帧上通过frame.f_trace = tracefunc 来设置跟踪函数,而不是用现有跟踪函数的返回值去间接设置它。为了使上述设置起效,当前帧上的跟踪函数必须激活,使用 settrace() 来设置全局跟踪函数才能启用运行时跟踪机制。关于代码对象和帧对象的更多信息请参考 标准类型层级结构

    import sys
    
    
    def local_trace_function(frame, event, arg):  # 局部跟踪函数
        print(f"local:堆栈帧地址 {id(frame)}, 传入参数 {arg},事件 {event} 发生在 {frame.f_code.co_name}{frame.f_lineno}行")
    
    
    def global_trace_function(frame, event, arg):  # 全局跟踪函数
        print(f"global:堆栈帧地址 {id(frame)}, 传入参数 {arg},事件 {event} 发生在 {frame.f_code.co_name}{frame.f_lineno}行")
        frame.f_trace = local_trace_function  # 显式设置局部跟踪函数到堆栈帧对象
    
    
    def f2():
        return "GFG"
    
    
    def f1():
        return f2()
    
    
    sys.settrace(global_trace_function)
    f1()
    
    global:堆栈帧地址 140574221454800, 传入参数 None,事件 call 发生在 f1 第17行
    local:堆栈帧地址 140574221454800, 传入参数 None,事件 line 发生在 f1 第18行
    global:堆栈帧地址 140574203098960, 传入参数 None,事件 call 发生在 f2 第13行
    local:堆栈帧地址 140574203098960, 传入参数 None,事件 line 发生在 f2 第14行
    local:堆栈帧地址 140574203098960, 传入参数 GFG,事件 return 发生在 f2 第14行
    local:堆栈帧地址 140574221454800, 传入参数 GFG,事件 return 发生在 f1 第18行
    
  • 多线程情况下使用threading.settrace(func)设置跟踪函数

    import time
    import threading
    
    
    def my_tracer(frame, event, arg=None):
        s = f"Passing the trace function and current thread is {str(threading.current_thread().name)}, event is {event}, function is {frame.f_code.co_name}"
        print(s, flush=True)
        # return my_tracer  # 跟踪当前的作用范围(设置局部跟踪函数)
    
    
    def thread_1(i):
        time.sleep(2)
        print("Value by Thread-1:", i)
    
    
    def thread_2(i):
        print("Value by Thread-2:", i)
    
    
    def thread_3(i):
        time.sleep(1)
        print("Value by Thread-3:", i)
    
    
    threading.settrace(my_tracer)
    thread1 = threading.Thread(target=thread_1, args=(1,))
    thread2 = threading.Thread(target=thread_2, args=(2,))
    thread3 = threading.Thread(target=thread_3, args=(3,))
    
    thread1.start()
    thread2.start()
    thread3.start()
    
    Passing the trace function and current thread is Thread-1, event is call, function is run  # 线程1,调用函数为run
    Passing the trace function and current thread is Thread-1, event is call, function is thread_1  # 线程1,调用函数为thread_1
    Passing the trace function and current thread is Thread-2, event is call, function is run
    Passing the trace function and current thread is Thread-2, event is call, function is thread_2
    Value by Thread-2: 2  # 由于线程2没有IO阻塞,因此直接打印
    Passing the trace function and current thread is Thread-3, event is call, function is run
    Passing the trace function and current thread is Thread-3, event is call, function is thread_3
    Value by Thread-3: 3
    Value by Thread-1: 1
    
  • 注意:settrace() 函数仅用于实现调试器,性能分析器,打包工具等。它的行为是实现平台的一部分,而不是语言定义的一部分,因此并非在所有 Python 实现中都可用

  • 3.7版本更改:添加了 ‘opcode’ 事件类型;为帧对象添加了 f_trace_lines 和 f_trace_opcodes 属性

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值