|
| 1 | +#!/usr/bin/python3 -u |
| 2 | + |
| 3 | +# Python 3 version of encoder by David Pritchard, built upon work by Peter Wentworth |
| 4 | +# (diff with pg_encoder.py, which is for Python 2) |
| 5 | + |
| 6 | + |
| 7 | +# given an arbitrary piece of Python data, encode it in such a manner |
| 8 | +# that it can be later encoded into JSON. |
| 9 | +# http://json.org/ |
| 10 | +# |
| 11 | +# Format: |
| 12 | +# * None, int, float, str, bool - unchanged (long is removed in Python 3) |
| 13 | +# (json.dumps encodes these fine verbatim) |
| 14 | +# * list - ['LIST', unique_id, elt1, elt2, elt3, ..., eltN] |
| 15 | +# * tuple - ['TUPLE', unique_id, elt1, elt2, elt3, ..., eltN] |
| 16 | +# * set - ['SET', unique_id, elt1, elt2, elt3, ..., eltN] |
| 17 | +# * dict - ['DICT', unique_id, [key1, value1], [key2, value2], ..., [keyN, valueN]] |
| 18 | +# * instance - ['INSTANCE', class name, unique_id, [attr1, value1], [attr2, value2], ..., [attrN, valueN]] |
| 19 | +# * class - ['CLASS', class name, unique_id, [list of superclass names], [attr1, value1], [attr2, value2], ..., [attrN, valueN]] |
| 20 | +# * circular reference - ['CIRCULAR_REF', unique_id] |
| 21 | +# * other - [<type name>, unique_id, string representation of object] |
| 22 | +# |
| 23 | +# |
| 24 | +# the unique_id is derived from id(), which allows us to explicitly |
| 25 | +# capture aliasing of compound values |
| 26 | + |
| 27 | +# Key: real ID from id() |
| 28 | +# Value: a small integer for greater readability, set by cur_small_id |
| 29 | +real_to_small_IDs = {} |
| 30 | +cur_small_id = 1 |
| 31 | + |
| 32 | +import re, types |
| 33 | +#typeRE = re.compile("<type '(.*)'>") # not used in Python 3 |
| 34 | +classRE = re.compile("<class '(.*)'>") |
| 35 | +functionRE = re.compile("<function (\w*) (.*)>") # new case for Python 3 |
| 36 | + |
| 37 | +# When we find a <class x> and x is in this list, don't confuse the beginner by listing the inner details |
| 38 | +native_types = ['int', 'float', 'str', 'tuple', 'list', 'set', 'dict', 'bool', 'NoneType', 'bytes', 'type', 'object'] |
| 39 | + |
| 40 | +def encode(dat, ignore_id=False): |
| 41 | + |
| 42 | + def append_attributes(ret, new_compound_obj_ids, dict): |
| 43 | + """ Put attributes onto ret. """ |
| 44 | + # traverse the __dict__ to grab attributes |
| 45 | + # (filter out useless-seeming ones): |
| 46 | + |
| 47 | + user_attrs = sorted([e for e in dict.keys() |
| 48 | + if e not in {'__doc__', '__module__', '__return__', '__locals__', |
| 49 | + '__weakref__', '__dict__'} |
| 50 | + ]) |
| 51 | + for attr in user_attrs: |
| 52 | + foo = [encode_helper(attr, new_compound_obj_ids), |
| 53 | + encode_helper(dict[attr], new_compound_obj_ids)] |
| 54 | + ret.append(foo) |
| 55 | + |
| 56 | + def encode_helper(dat, compound_obj_ids): |
| 57 | + # primitive type |
| 58 | + if dat is None or type(dat) in (int, float, str, bool): |
| 59 | + return dat |
| 60 | + # compound type |
| 61 | + else: |
| 62 | + my_id = id(dat) |
| 63 | + |
| 64 | + global cur_small_id |
| 65 | + if my_id not in real_to_small_IDs: |
| 66 | + if ignore_id: |
| 67 | + real_to_small_IDs[my_id] = 99999 |
| 68 | + else: |
| 69 | + real_to_small_IDs[my_id] = cur_small_id |
| 70 | + cur_small_id += 1 |
| 71 | + |
| 72 | + if my_id in compound_obj_ids: |
| 73 | + return ['CIRCULAR_REF', real_to_small_IDs[my_id]] |
| 74 | + |
| 75 | + new_compound_obj_ids = compound_obj_ids.union([my_id]) |
| 76 | + |
| 77 | + typ = type(dat) |
| 78 | + obj_as_string = object.__repr__(dat) |
| 79 | + |
| 80 | + my_small_id = real_to_small_IDs[my_id] |
| 81 | + |
| 82 | + if typ == list: |
| 83 | + ret = ['LIST', my_small_id] |
| 84 | + for e in dat: ret.append(encode_helper(e, new_compound_obj_ids)) |
| 85 | + elif typ == tuple: |
| 86 | + ret = ['TUPLE', my_small_id] |
| 87 | + for e in dat: ret.append(encode_helper(e, new_compound_obj_ids)) |
| 88 | + elif typ == set: |
| 89 | + ret = ['SET', my_small_id] |
| 90 | + for e in dat: ret.append(encode_helper(e, new_compound_obj_ids)) |
| 91 | + elif typ == dict: |
| 92 | + ret = ['DICT', my_small_id] |
| 93 | + append_attributes(ret, new_compound_obj_ids, dat) |
| 94 | + |
| 95 | + elif typ == type: # its a class. What a mess they made of it! |
| 96 | + superclass_names = [e.__name__ for e in dat.__bases__] |
| 97 | + ret = ['CLASS', dat.__name__, my_small_id, superclass_names] |
| 98 | + if dat.__name__ not in native_types: |
| 99 | + if hasattr(dat, '__dict__'): |
| 100 | + append_attributes(ret, new_compound_obj_ids, dat.__dict__) |
| 101 | + |
| 102 | + elif repr(typ)[:6] == "<class" and obj_as_string.find('object') >= 0: # is it an instance? |
| 103 | + ret = ['INSTANCE', dat.__class__.__name__, my_small_id] |
| 104 | + if hasattr(dat, '__dict__'): |
| 105 | + append_attributes(ret, new_compound_obj_ids, dat.__dict__) |
| 106 | + |
| 107 | + else: |
| 108 | + typeStr = repr(typ) |
| 109 | + m = classRE.match(typeStr) |
| 110 | + assert m, typ |
| 111 | + ret = [m.group(1), my_small_id , obj_as_string] |
| 112 | + |
| 113 | + return ret |
| 114 | + |
| 115 | + return encode_helper(dat, set()) |
| 116 | + |
| 117 | + |
| 118 | +if __name__ == '__main__': |
| 119 | + |
| 120 | + def test(actual, expected=0): |
| 121 | + """ Compare the actual to the expected value, and print a suitable message. """ |
| 122 | + import sys |
| 123 | + linenum = sys._getframe(1).f_lineno # get the caller's line number. |
| 124 | + if (expected == actual): |
| 125 | + msg = "Test on line %s passed." % (linenum) |
| 126 | + else: |
| 127 | + msg = "Test on line %s failed. Expected '%s', but got '%s'." % (linenum, expected, actual) |
| 128 | + print(msg) |
| 129 | + |
| 130 | + class P(): |
| 131 | + p_attr1 = 123 |
| 132 | + def p_method(self, x): |
| 133 | + return 2*x |
| 134 | + |
| 135 | + class Q(P): |
| 136 | + pass |
| 137 | + |
| 138 | + p1 = P() |
| 139 | + q1 = Q() |
| 140 | + |
| 141 | + addr = 1 |
| 142 | + |
| 143 | + test(encode("hello"),"hello") |
| 144 | + test(encode(123),123) |
| 145 | + test(encode(123.45),123.45) |
| 146 | + test(encode(132432134423143132432134423143),132432134423143132432134423143) |
| 147 | + test(encode(False),False) |
| 148 | + test(encode(None),None) |
| 149 | + |
| 150 | + |
| 151 | + test(encode((1,2)), ['TUPLE', addr, 1, 2]) |
| 152 | + |
| 153 | + addr += 1 |
| 154 | + test(encode([1,2]), ['LIST', addr, 1, 2]) |
| 155 | + |
| 156 | + addr += 1 |
| 157 | + test(encode({1:'mon'}), ['DICT', addr, [1, 'mon']]) |
| 158 | + |
| 159 | + addr += 1 |
| 160 | + test(encode(test), ['function', addr, 'test']) |
| 161 | + |
| 162 | + addr += 1 |
| 163 | + test(encode(P), ['CLASS', 'P', addr, ['object'], ['p_attr1', 123], ['p_method', ['function', addr+1, 'p_method']]]) |
| 164 | + |
| 165 | + addr += 2 |
| 166 | + test(encode(Q), ['CLASS', 'Q', addr, ['P']]) |
| 167 | + |
| 168 | + addr += 1 |
| 169 | + test(encode(p1), ['INSTANCE', 'P', addr]) |
| 170 | + |
| 171 | + addr += 1 |
| 172 | + test(encode(q1), ['INSTANCE', 'Q', addr]) |
| 173 | + |
| 174 | + addr += 1 |
| 175 | + test(encode(min), ['builtin_function_or_method', addr, '<built-in function min>'] ) |
| 176 | + |
| 177 | + addr += 1 |
| 178 | + test(encode(range(1,3)), ['range', addr, 'range(1, 3)']) |
| 179 | + |
| 180 | + addr += 1 |
| 181 | + test(encode({1,2}), ['SET', addr, 1, 2]) |
| 182 | + |
| 183 | + addr += 1 |
| 184 | + p = [1,2,3] |
| 185 | + p.append(p) # make a circular reference |
| 186 | + |
| 187 | + test(encode(p), ['LIST', addr, 1, 2, 3, ['CIRCULAR_REF', addr]]) |
| 188 | + |
| 189 | +# Need some new tests for z = type(123) |
| 190 | + |
| 191 | + |
| 192 | + print(encode({"stdout": "", "func_name": "<module>", "globals": {"sum": 0, "friends": ["LIST", 1, "Joe", "Bill"], "length": 3, "f": "Joe"}, "stack_locals": [], "line": 7, "event": "step_line"})) |
0 commit comments