PDB:在python中的内建函数和辅助工具用法

举报
码乐 发表于 2023/12/14 09:20:24 2023/12/14
【摘要】 1 调试:内建函数代码对象是python程序的字节码版本,不仅包含从您的python代码生成的确切指令还存储了该段代码使用的变量和常量。代码对象从AST(抽象语法树)生成,它们本身由在代码字符串运行的解析器生成的。查看抽象语法树,首先使用 ast模块从我们的代码生成一个AST, >>> import ast >>> code = """x = [1,2];print(x);""" >>> ...

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=[])
	>>>

1.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虚拟机中运行所需的所有信息,以便生成该输出。

1.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

2 调试时 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):
    
  • 设置运行时断点 和 条件断点

小结

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

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。