Issue #9268: Add annotation option to pickletools.dis

This commit is contained in:
Alexander Belopolsky 2010-07-17 15:51:21 +00:00
parent f4e7a35d64
commit 929d384058
3 changed files with 54 additions and 12 deletions

View File

@ -13,14 +13,18 @@ ordinary users of the :mod:`pickle` module probably won't find the
:mod:`pickletools` module relevant.
.. function:: dis(pickle, out=None, memo=None, indentlevel=4)
.. function:: dis(pickle, out=None, memo=None, indentlevel=4, annotate=0)
Outputs a symbolic disassembly of the pickle to the file-like object *out*,
defaulting to ``sys.stdout``. *pickle* can be a string or a file-like object.
*memo* can be a Python dictionary that will be used as the pickle's memo; it can
be used to perform disassemblies across multiple pickles created by the same
pickler. Successive levels, indicated by ``MARK`` opcodes in the stream, are
indented by *indentlevel* spaces.
Outputs a symbolic disassembly of the pickle to the file-like
object *out*, defaulting to ``sys.stdout``. *pickle* can be a
string or a file-like object. *memo* can be a Python dictionary
that will be used as the pickle's memo; it can be used to perform
disassemblies across multiple pickles created by the same
pickler. Successive levels, indicated by ``MARK`` opcodes in the
stream, are indented by *indentlevel* spaces. If a nonzero value
is given to *annotate*, each opcode in the output is annotated with
a short description. The value of *annotate* is used as a hint for
the column where annotation should start.
.. function:: genops(pickle)

View File

@ -1907,7 +1907,7 @@ def optimize(p):
##############################################################################
# A symbolic pickle disassembler.
def dis(pickle, out=None, memo=None, indentlevel=4):
def dis(pickle, out=None, memo=None, indentlevel=4, annotate=0):
"""Produce a symbolic disassembly of a pickle.
'pickle' is a file-like object, or string, containing a (at least one)
@ -1923,9 +1923,15 @@ def dis(pickle, out=None, memo=None, indentlevel=4):
to proceed across multiple pickles that were all created by the same
pickler with the same memo. Ordinarily you don't need to worry about this.
Optional arg indentlevel is the number of blanks by which to indent
Optional arg 'indentlevel' is the number of blanks by which to indent
a new MARK level. It defaults to 4.
Optional arg 'annotate' if nonzero instructs dis() to add short
description of the opcode on each line of disassembled output.
The value given to 'annotate' must be an integer and is used as a
hint for the column where annotation should start. The default
value is 0, meaning no annotations.
In addition to printing the disassembly, some sanity checks are made:
+ All embedded opcode arguments "make sense".
@ -1953,6 +1959,7 @@ def dis(pickle, out=None, memo=None, indentlevel=4):
markstack = [] # bytecode positions of MARK opcodes
indentchunk = ' ' * indentlevel
errormsg = None
annocol = annotate # columnt hint for annotations
for opcode, arg, pos in genops(pickle):
if pos is not None:
print("%5d:" % pos, end=' ', file=out)
@ -2020,6 +2027,13 @@ def dis(pickle, out=None, memo=None, indentlevel=4):
line += ' ' + repr(arg)
if markmsg:
line += ' ' + markmsg
if annotate:
line += ' ' * (annocol - len(line))
# make a mild effort to align annotations
annocol = len(line)
if annocol > 50:
annocol = annotate
line += ' ' + opcode.doc.split('\n', 1)[0]
print(line, file=out)
if errormsg:
@ -2293,6 +2307,22 @@ highest protocol among opcodes = 2
12: h BINGET 1
14: . STOP
highest protocol among opcodes = 2
Try protocol 3 with annotations:
>>> dis(pickle.dumps(T, 3), annotate=1)
0: \x80 PROTO 3 Protocol version indicator.
2: ] EMPTY_LIST Push an empty list.
3: q BINPUT 0 Store the stack top into the memo. The stack is not popped.
5: h BINGET 0 Read an object from the memo and push it on the stack.
7: \x85 TUPLE1 Build a one-tuple out of the topmost item on the stack.
8: q BINPUT 1 Store the stack top into the memo. The stack is not popped.
10: a APPEND Append an object to a list.
11: 0 POP Discard the top stack item, shrinking the stack by one item.
12: h BINGET 1 Read an object from the memo and push it on the stack.
14: . STOP Stop the unpickling machine.
highest protocol among opcodes = 2
"""
_memo_test = r"""
@ -2348,6 +2378,9 @@ if __name__ == "__main__":
parser.add_argument(
'-l', '--indentlevel', default=4, type=int,
help='the number of blanks by which to indent a new MARK level')
parser.add_argument(
'-a', '--annotate', action='store_true',
help='annotate each line with a short opcode description')
parser.add_argument(
'-p', '--preamble', default="==> {name} <==",
help='if more than one pickle file is specified, print this before'
@ -2362,14 +2395,15 @@ if __name__ == "__main__":
if args.test:
_test()
else:
annotate = 30 if args.annotate else 0
if not args.pickle_file:
parser.print_help()
elif len(args.pickle_file) == 1:
dis(args.pickle_file[0], args.output,
indentlevel=args.indentlevel)
dis(args.pickle_file[0], args.output, None,
args.indentlevel, annotate)
else:
memo = {} if args.memo else None
for f in args.pickle_file:
preamble = args.preamble.format(name=f.name)
args.output.write(preamble + '\n')
dis(f, args.output, memo, args.indentlevel)
dis(f, args.output, memo, args.indentlevel, annotate)

View File

@ -473,6 +473,10 @@ C-API
Library
-------
- Issue #9268: ``pickletools.dis()`` now has an optional *annotate*
argument which controls printing of opcode descriptions in ``dis()``
output.
- Issue #1555570: email no longer inserts extra blank lines when a \r\n
combo crosses an 8192 byte boundary.