在python中的内建函数和辅助工具进行调试

简介: 【6月更文挑战第18天】本文介绍Python的调试,涉及对代码的字节码和语法结构进行深入分析。通过这些工具,开发者能更好地理解和调试代码执行流程。

简介

python代码对象存储了代码的字节码和变量信息,通过如下工具进行代码分析。
ast模块解析为抽象语法树(AST)。
tokenize模块用于词法分析,将源代码转换为标记。
compile函数将AST编译为代码对象,
dis模块则可视化解码这些对象,展示字节码。
pdb是内置调试器,提供断点、单步调试等功能。
globals()locals()存储变量的全局和局部值。
breakpoint()是Python 3.7引入的便捷调试入口。

1 调试:内建函数

代码对象是python程序的字节码版本,不仅包含从您的python代码生成的确切指令还存储了该段代码使用的变量和常量。

代码对象从AST(抽象语法树)生成,它们本身由在代码字符串运行的解析器生成的。

查看抽象语法树,首先使用 ast模块从我们的代码生成一个AST,

    >>> import ast
    >>> code = """x = [1,2];print(x);"""
    >>> tree = ast.parse(code)
    >>> print(ast.dump(tree, indent=2))
    Module(
      body=[
        Assign(   // 声明
          targets=[   // 分配给目标x
            Name(id='x', ctx=Store())],
          value=List(  // list具有2个常数 1 和a的值2
            elts=[
              Constant(value=1),
              Constant(value=2)],
            ctx=Load())),
        Expr(  // Expr语句,Call函数调用
          value=Call(
            func=Name(id='print', ctx=Load()),  // 调用函数print 
            args=[
              Name(id='x', ctx=Load())],   // 被调用函数print的值为 x
            keywords=[]))],
      type_ignores=[])
    >>>

2 内建函数:标记器

在AST被解析出之前发生的一个步骤 Lexing 词法分析
根据python语法将源代码转换为标记,

您可查看python如何标记您的文件,使用tokenize

查看python 如何解析代码为 token, “令牌流”被解析为 AST。

有以下代码

       # paramsclient.py
    import os

    if __name__ == '__main__':
        print(os.path)

python -m tokenize paramsclient.py

    0,0-0,0:            ENCODING       'utf-8'
    1,0-1,6:            NAME           'import'
    1,7-1,9:            NAME           'os'
    1,9-1,11:           NEWLINE        '\r\n'
    2,0-2,2:            NL             '\r\n'
    3,0-3,2:            NL             '\r\n'
    4,0-4,2:            NAME           'if'
    4,3-4,11:           NAME           '__name__'
    4,12-4,14:          OP             '=='
    4,15-4,25:          STRING         "'__main__'"
    4,25-4,26:          OP             ':'
    4,26-4,28:          NEWLINE        '\r\n'
    5,0-5,4:            INDENT         '    '
    5,4-5,9:            NAME           'print'
    5,9-5,10:           OP             '('
    5,10-5,12:          NAME           'os'
    5,12-5,13:          OP             '.'
    5,13-5,17:          NAME           'path'
    5,17-5,18:          OP             ')'
    5,18-5,19:          NEWLINE        ''
    6,0-6,0:            DEDENT         ''
    6,0-6,0:            ENDMARKER      ''

tokenize将我们的文件转为它的裸标记,比如变量名,括号字符串,数组。

它还跟踪每个标记的行号和位置,例如,这有助于指出错误小心的确切位置。
这个“令牌流”被解析为 AST,解析AST树后 使用 内置函数将其编译为代码对象。

compile 在代码对象上运行exec,像以前一样运行它

    >>> code_Obj = compile(tree, "mf.py", 'exec')
>>> exec(code_Obj)

code_obj 有一些属性

>>> dir(code_Obj)

            ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne_
    _', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename',
     'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_posonlyargcount', 'co_stacksize', 'co_varnames', 'replace']


>>> code_Obj.co_filename
    'mf.py'

>>> code_Obj.co_names
    ('x', 'print')   // 变量 x 和 print

>>> code_Obj.co_consts
    (1, 2, None)

这些具有直接在python虚拟机中运行所需的所有信息,以便生成该输出。

3 dis 与 pdb

dis模块可用于以人类可理解的方式可视化代码对象内容
帮助了解python幕后所做工作。

它接收字节码,常量,和变量信息

>>> import dis
>>> dis.dis(code)
  1           0 LOAD_CONST               0 (1)
              2 LOAD_CONST               1 (2)
              4 BUILD_LIST               2
              6 STORE_NAME               0 (x)
              8 LOAD_NAME                1 (print)
             10 LOAD_NAME                0 (x)
             12 CALL_FUNCTION            1
             14 POP_TOP
             16 LOAD_CONST               2 (None)
             18 RETURN_VALUE

dis.dis 表示一行,创建 10个字节码,加载print并 x 到 堆栈上,并在堆栈 1上调用带有参数的函数。 然后它通过do摆脱调用返回值。

POP_TOP因为我们没有使用或存储来自 print(x) 的最后两行None从执行的末尾返回,它什么也没有做。

当存储为操作码时,如 POP_TOP名,这些字节码中的每一个都是2个字节长LOAD_CONST,

这就是操作码左侧的数字彼此相距2 的原因,它还表明整个代码有20个字节长。 事实上,使用len可查看

    >>> code_Obj.co_code
    b'd\x00d\x01g\x02Z\x00e\x01e\x00\x83\x01\x01\x00d\x02S\x00'
    >>> len(code_Obj.co_code)
    20

eval与exec非常相似,除了它只接收表达式,不是语句或一组语句,如exec并且与exec不同,它返回一个值,所述表达式的结果

    result = eval("1 + 1")
    result
        2

或者使用详细的路线eval,需要您告诉ast.parse并且compule您期望评估此代码的价值,而不是以python文件运行

>>> expr = ast.parse('1+1', mode='eval')
>>> code_obj = compile(expr, '<code>', 'eval')
>>> eval(code_obj)
2

4 调试时 globals 和 locals 存储所有内容的两个对象

虽然生成的代码对象存储了一段代码中定义的逻辑和常量,但它们不存储一件事是正在使用的变量的实际值。

关于语言工作原理 有几个原因

    def double(number):
        return number * 2

这个函数代码对象将存储常量 2,以及变量名 number,但它显然不能包含实际值的number,

因为在函数实际运行之前不会给它,

python将所有内容存储在与每个本地范围关联的字典中。这意味着每段代码都有自己定义的 “本地范围”

可用locals() 在该代码内部访问,其中包含与每个变量名称对应的值。

实例:

    >>> value = 5
    >>> def double(number):
    ...     return number * 2
    ...
    >>> double(value)
    10
    >>> locals()
    {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_froz
    en_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <
    module 'builtins' (built-in)>, 'ast': <module 'ast' from '\lib\\ast.py'>, 'co
    de': 'x = [1,2];print(x);', 'tree': <ast.Module object at 0x0000027AF729F8B0>, 'code_Obj':
     <code object <module> at 0x0000027AF72A2920, file "mf.py", line 1>, 'x': [1, 2], 'dis': <
    module 'dis' from '\lib\\dis.py'>, 'expr': <ast.Expression object at 0x000002
    7AF72DC910>, 'code_obj': <code object <module> at 0x0000027AF72F6030, file "<code>", line
    1>, 'value': 5, 'double': <function double at 0x0000027AF72E3310>}
    >>> globals()
    {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_froz...'}

此时全局与本地是相同的。

    >>> globals() == locals()
    True

globals非常相似,只是globals总是指向模块范围(也称全局范围)

  • 内置调试

    breakpoint是一个被添加到 Python 3.7 中的内置函数,作为进入调试会话的一种更简单的方法。
    本质上,它只是set_trace()从pdb模块调用,该模块是 Python 内置的调试器模块。

让您pdb可以随时停止代码的执行,检查变量的值,如果您愿意,可以运行一些代码,

然后您甚至可以做一些花哨的事情,比如一次运行一行代码,或者检查状态解释器内部的堆栈帧

python /tests/paramsclient.py

> \paramsclient.py(47)alternate_case()
-> array = bytearray(string.encode())  # 把字符添加到一个 byte数组
(Pdb) 
  • 查询断点设置位置
        (Pdb) list .
         42          :param string:
         43          :return:
         44          """
         45      
         46          breakpoint()
         47  ->        array = bytearray(string.encode())  # 把字符添加到一个 byte数组
         48          for index, byte in enumerate(array):
         49              if not (( 65 <= byte <= 90) or (97 <= byte <= 126)):
         50                  continue
         51              if index %2 == 0:
         52                  array[index] = byte | 32
        (Pdb) 
  • 查询附近更多代码

         (Pdb) ll
    
  • 查询执行输入的地方

         (Pdb) where
          \paramsclient.py(68)<module>()
         -> print(alternate_case("Hello,World!"))
         > \paramsclient.py(47)alternate_case()
         > 
    
  • 把字符添加到一个 byte数组

         -> array = bytearray(string.encode())  
         (Pdb) 
    
  • 向上进入调用者帧的层级

         (Pdb) up
         > \paramsclient.py(68)<module>()
         -> print(alternate_case("Hello,World!"))
         (Pdb) up
         *** Oldest frame
         (Pdb) up
         *** Oldest frame
    
  • 向下进入调用者帧的层级

         (Pdb) down
         > \paramsclient.py(47)alternate_case()
    
  • 把字符添加到一个 byte数组

         -> array = bytearray(string.encode())  
         (Pdb) down
         *** Newest frame
    
    • 在pdb调试器中打印

         使用内建函数打印
         (Pdb) locals()
                 {'string': 'Hello,World!'}
         (Pdb) globals()
      

      {'name': 'main', 'doc': None, 'package': None, 'loader': <_frozen_importlib_external.SourceFileLoader object at 0x00000198E3176CD0>, ...}

         (Pdb) 
      
  • 查询环境中加载的对象

         (Pdb) string
                 'Hello,World!'
    
  • p 相当于使用 pring

          (Pdb) p string
                  'Hello,World!'
    
  • pp 相当于使用 ppring

      (Pdb) pp xl
      {'a': 1, 'b': 2, 'c': 3}
    
  • 转跳到将要执行的一行

      n
      (Pdb) n
      >  \paramsclient.py(48)alternate_case()
      -> for index, byte in enumerate(array):
      (Pdb) 
    
  • 单步调试python代码 s

      (Pdb) s
      > 
      > \paramsclient.py(49)alternate_case()
      -> if not (( 65 <= byte <= 90) or (97 <= byte <= 126)):
      (Pdb) s
      > \paramsclient.py(51)alternate_case()
      -> if index %2 == 0:
      (Pdb) s
      > \paramsclient.py(52)alternate_case()
      -> array[index] = byte | 32
      (Pdb) 
    
  • 显示python执行器

     (Pdb) p sys.executable
     '\python.exe'
     (Pdb) 
    
  • r 是return 的简写,将执行到函数的return位置

     (Pdb) r
     --Return--
     > \paramsclient.py(55)alternate_case()->'hElLo,wOrLd!'
     -> return array.decode()
     (Pdb) r
     hElLo,wOrLd!
     --Return--
     > \paramsclient.py(69)<module>()->None
     -> print(alternate_case("Hello,World!"))
     (Pdb) 
    
  • 查看当前位置

          (Pdb) list
           64          #         print(i)
           65          #
           66          # print("regular print still works normal.")
           67          xl = {"a":1, "b":2, "c":3}
           68      
           69  ->        print(alternate_case("Hello,World!"))
           70      
          [EOF]
    
  • 运行到下一个return的位置

          (Pdb) r
          --Call--
          > \lib\logging\__init__.py(2126)shutdown()
          -> def shutdown(handlerList=_handlerList):
    
  • 设置运行时断点 和 条件断点

5 小结

本文解释几个常见的调式方法,并且它们如何与实际使用结合。

目录
相关文章
|
2月前
|
存储 JavaScript Java
(Python基础)新时代语言!一起学习Python吧!(四):dict字典和set类型;切片类型、列表生成式;map和reduce迭代器;filter过滤函数、sorted排序函数;lambda函数
dict字典 Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。 我们可以通过声明JS对象一样的方式声明dict
205 1
|
2月前
|
算法 Java Docker
(Python基础)新时代语言!一起学习Python吧!(三):IF条件判断和match匹配;Python中的循环:for...in、while循环;循环操作关键字;Python函数使用方法
IF 条件判断 使用if语句,对条件进行判断 true则执行代码块缩进语句 false则不执行代码块缩进语句,如果有else 或 elif 则进入相应的规则中执行
303 1
|
2月前
|
Java 数据处理 索引
(numpy)Python做数据处理必备框架!(二):ndarray切片的使用与运算;常见的ndarray函数:平方根、正余弦、自然对数、指数、幂等运算;统计函数:方差、均值、极差;比较函数...
ndarray切片 索引从0开始 索引/切片类型 描述/用法 基本索引 通过整数索引直接访问元素。 行/列切片 使用冒号:切片语法选择行或列的子集 连续切片 从起始索引到结束索引按步长切片 使用slice函数 通过slice(start,stop,strp)定义切片规则 布尔索引 通过布尔条件筛选满足条件的元素。支持逻辑运算符 &、|。
183 0
|
3月前
|
设计模式 缓存 监控
Python装饰器:优雅增强函数功能
Python装饰器:优雅增强函数功能
280 101
|
3月前
|
缓存 测试技术 Python
Python装饰器:优雅地增强函数功能
Python装饰器:优雅地增强函数功能
228 99
|
3月前
|
存储 缓存 测试技术
Python装饰器:优雅地增强函数功能
Python装饰器:优雅地增强函数功能
202 98
|
3月前
|
缓存 Python
Python中的装饰器:优雅地增强函数功能
Python中的装饰器:优雅地增强函数功能
|
3月前
|
存储 缓存 测试技术
理解Python装饰器:简化代码的强大工具
理解Python装饰器:简化代码的强大工具
|
4月前
|
Python
Python 函数定义
Python 函数定义
564 155
|
3月前
|
机器学习/深度学习 编解码 Python
Python图片上采样工具 - RealESRGANer
Real-ESRGAN基于深度学习实现图像超分辨率放大,有效改善传统PIL缩放的模糊问题。支持多种模型版本,推荐使用魔搭社区提供的预训练模型,适用于将小图高质量放大至大图,放大倍率越低效果越佳。
284 3

热门文章

最新文章

推荐镜像

更多