PDB:在python中的内建函数和辅助工具用法
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):
-
设置运行时断点 和 条件断点
小结
本文解释几个常见的调式方法,并且它们如何与实际使用结合。
- 点赞
- 收藏
- 关注作者
评论(0)