1 """ pydevd_vars deals with variables:
2 resolution/conversion to XML.
5 from _pydevd_bundle.pydevd_constants import dict_contains, get_frame, get_thread_id
7 from _pydevd_bundle.pydevd_custom_frames import get_custom_frame
8 from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate, get_type, var_to_xml
9 from _pydev_imps._pydev_saved_modules import thread
12 from StringIO import StringIO
14 from io import StringIO
15 import sys # @Reimport
17 from _pydev_imps._pydev_saved_modules import threading
19 from _pydevd_bundle import pydevd_save_locals
20 from _pydev_bundle.pydev_imports import Exec, quote, execfile
21 from _pydevd_bundle.pydevd_utils import to_string
24 # -------------------------------------------------------------------------- defining true and false for earlier versions
31 setattr(__builtin__, 'True', 1)
32 setattr(__builtin__, 'False', 0)
35 # ------------------------------------------------------------------------------------------------------ class for errors
37 class VariableError(RuntimeError): pass
40 class FrameNotFoundError(RuntimeError): pass
43 def _iter_frames(initialFrame):
44 '''NO-YIELD VERSION: Iterates through all the frames starting at the specified frame (which will be the first returned item)'''
48 while initialFrame is not None:
49 frames.append(initialFrame)
50 initialFrame = initialFrame.f_back
55 def dump_frames(thread_id):
56 sys.stdout.write('dumping frames\n')
57 if thread_id != get_thread_id(threading.currentThread()):
58 raise VariableError("find_frame: must execute on same thread")
60 curFrame = get_frame()
61 for frame in _iter_frames(curFrame):
62 sys.stdout.write('%s\n' % pickle.dumps(frame))
65 # ===============================================================================
66 # AdditionalFramesContainer
67 # ===============================================================================
68 class AdditionalFramesContainer:
69 lock = thread.allocate_lock()
70 additional_frames = {} # dict of dicts
73 def add_additional_frame_by_id(thread_id, frames_by_id):
74 AdditionalFramesContainer.additional_frames[thread_id] = frames_by_id
77 addAdditionalFrameById = add_additional_frame_by_id # Backward compatibility
80 def remove_additional_frame_by_id(thread_id):
81 del AdditionalFramesContainer.additional_frames[thread_id]
84 removeAdditionalFrameById = remove_additional_frame_by_id # Backward compatibility
87 def has_additional_frames_by_id(thread_id):
88 return dict_contains(AdditionalFramesContainer.additional_frames, thread_id)
91 def get_additional_frames_by_id(thread_id):
92 return AdditionalFramesContainer.additional_frames.get(thread_id)
95 def find_frame(thread_id, frame_id):
96 """ returns a frame on the thread that has a given frame_id """
98 curr_thread_id = get_thread_id(threading.currentThread())
99 if thread_id != curr_thread_id:
101 return get_custom_frame(thread_id, frame_id) # I.e.: thread_id could be a stackless frame id + thread_id.
105 raise VariableError("find_frame: must execute on same thread (%s != %s)" % (thread_id, curr_thread_id))
107 lookingFor = int(frame_id)
109 if AdditionalFramesContainer.additional_frames:
110 if dict_contains(AdditionalFramesContainer.additional_frames, thread_id):
111 frame = AdditionalFramesContainer.additional_frames[thread_id].get(lookingFor)
113 if frame is not None:
116 curFrame = get_frame()
118 return curFrame # any frame is specified with "*"
122 for frame in _iter_frames(curFrame):
123 if lookingFor == id(frame):
130 # Important: python can hold a reference to the frame from the current context
131 # if an exception is raised, so, if we don't explicitly add those deletes
132 # we might have those variables living much more than we'd want to.
134 # I.e.: sys.exc_info holding reference to frame that raises exception (so, other places
135 # need to call sys.exc_clear())
138 if frameFound is None:
142 for frame in _iter_frames(get_frame()):
144 msgFrames += str(id(frame))
150 errMsg = '''find_frame: frame not found.
151 Looking for thread_id:%s, frame_id:%s
152 Current thread_id:%s, available frames:
154 ''' % (thread_id, lookingFor, curr_thread_id, msgFrames)
156 sys.stderr.write(errMsg)
162 traceback.print_exc()
166 def getVariable(thread_id, frame_id, scope, attrs):
168 returns the value of a variable
170 :scope: can be BY_ID, EXPRESSION, GLOBAL, LOCAL, FRAME
172 BY_ID means we'll traverse the list of all objects alive to get the object.
174 :attrs: after reaching the proper scope, we have to get the attributes until we find
175 the proper location (i.e.: obj\tattr1\tattr2)
177 :note: when BY_ID is used, the frame_id is considered the id of the object to find and
178 not the frame (as we don't care about the frame in this case).
181 if thread_id != get_thread_id(threading.currentThread()):
182 raise VariableError("getVariable: must execute on same thread")
186 objects = gc.get_objects()
188 pass # Not all python variants have it.
190 frame_id = int(frame_id)
192 if id(var) == frame_id:
193 if attrs is not None:
194 attrList = attrs.split('\t')
196 _type, _typeName, resolver = get_type(var)
197 var = resolver.resolve(var, k)
201 # If it didn't return previously, we coudn't find it by id (i.e.: alrceady garbage collected).
202 sys.stderr.write('Unable to find object with id: %s\n' % (frame_id,))
205 frame = find_frame(thread_id, frame_id)
209 if attrs is not None:
210 attrList = attrs.split('\t')
214 for attr in attrList:
215 attr.replace("@_@TAB_CHAR@_@", '\t')
217 if scope == 'EXPRESSION':
218 for count in xrange(len(attrList)):
220 # An Expression can be in any scope (globals/locals), therefore it needs to evaluated as an expression
221 var = evaluate_expression(thread_id, frame_id, attrList[count], False)
223 _type, _typeName, resolver = get_type(var)
224 var = resolver.resolve(var, attrList[count])
226 if scope == "GLOBAL":
227 var = frame.f_globals
228 del attrList[0] # globals are special, and they get a single dummy unused attribute
230 # in a frame access both locals and globals as Python does
232 var.update(frame.f_globals)
233 var.update(frame.f_locals)
236 _type, _typeName, resolver = get_type(var)
237 var = resolver.resolve(var, k)
242 def resolve_compound_variable(thread_id, frame_id, scope, attrs):
243 """ returns the value of the compound variable as a dictionary"""
245 var = getVariable(thread_id, frame_id, scope, attrs)
248 _type, _typeName, resolver = get_type(var)
249 return resolver.get_dictionary(var)
251 sys.stderr.write('Error evaluating: thread_id: %s\nframe_id: %s\nscope: %s\nattrs: %s\n' % (
252 thread_id, frame_id, scope, attrs,))
253 traceback.print_exc()
256 def resolve_var(var, attrs):
257 attrList = attrs.split('\t')
260 type, _typeName, resolver = get_type(var)
262 var = resolver.resolve(var, k)
265 type, _typeName, resolver = get_type(var)
266 return resolver.get_dictionary(var)
268 traceback.print_exc()
271 def custom_operation(thread_id, frame_id, scope, attrs, style, code_or_file, operation_fn_name):
273 We'll execute the code_or_file and then search in the namespace the operation_fn_name to execute with the given var.
275 code_or_file: either some code (i.e.: from pprint import pprint) or a file to be executed.
276 operation_fn_name: the name of the operation to execute after the exec (i.e.: pprint)
278 expressionValue = getVariable(thread_id, frame_id, scope, attrs)
281 namespace = {'__name__': '<custom_operation>'}
282 if style == "EXECFILE":
283 namespace['__file__'] = code_or_file
284 execfile(code_or_file, namespace, namespace)
285 else: # style == EXEC
286 namespace['__file__'] = '<customOperationCode>'
287 Exec(code_or_file, namespace, namespace)
289 return str(namespace[operation_fn_name](expressionValue))
291 traceback.print_exc()
294 def eval_in_context(expression, globals, locals):
297 result = eval(expression, globals, locals)
300 traceback.print_exc(file=s)
301 result = s.getvalue()
305 etype, value, tb = sys.exc_info()
308 etype = value = tb = None
312 result = ExceptionOnEvaluate(result)
314 # Ok, we have the initial error message, but let's see if we're dealing with a name mangling error...
316 if '__' in expression:
317 # Try to handle '__' name mangling...
318 split = expression.split('.')
319 curr = locals.get(split[0])
320 for entry in split[1:]:
321 if entry.startswith('__') and not hasattr(curr, entry):
322 entry = '_%s%s' % (curr.__class__.__name__, entry)
323 curr = getattr(curr, entry)
331 def evaluate_expression(thread_id, frame_id, expression, doExec):
332 '''returns the result of the evaluated expression
333 @param doExec: determines if we should do an exec or an eval
335 frame = find_frame(thread_id, frame_id)
339 # Not using frame.f_globals because of https://sourceforge.net/tracker2/?func=detail&aid=2541355&group_id=85796&atid=577329
340 # (Names not resolved in generator expression in method)
341 # See message: http://mail.python.org/pipermail/python-list/2009-January/526522.html
343 updated_globals.update(frame.f_globals)
344 updated_globals.update(frame.f_locals) # locals later because it has precedence over the actual globals
347 expression = str(expression.replace('@LINE@', '\n'))
351 # try to make it an eval (if it is an eval we can print it, otherwise we'll exec it and
352 # it will have whatever the user actually did)
353 compiled = compile(expression, '<string>', 'eval')
355 Exec(expression, updated_globals, frame.f_locals)
356 pydevd_save_locals.save_locals(frame)
358 result = eval(compiled, updated_globals, frame.f_locals)
359 if result is not None: # Only print if it's not None (as python does)
360 sys.stdout.write('%s\n' % (result,))
364 return eval_in_context(expression, updated_globals, frame.f_locals)
366 # Should not be kept alive if an exception happens and this frame is kept in the stack.
371 def change_attr_expression(thread_id, frame_id, attr, expression, dbg, value=None):
372 '''Changes some attribute in a given frame.
374 frame = find_frame(thread_id, frame_id)
379 expression = expression.replace('@LINE@', '\n')
381 if dbg.plugin and not value:
382 result = dbg.plugin.change_variable(frame, attr, expression)
386 if attr[:7] == "Globals":
388 if attr in frame.f_globals:
390 value = eval(expression, frame.f_globals, frame.f_locals)
391 frame.f_globals[attr] = value
392 return frame.f_globals[attr]
394 if pydevd_save_locals.is_save_locals_available():
396 value = eval(expression, frame.f_globals, frame.f_locals)
397 frame.f_locals[attr] = value
398 pydevd_save_locals.save_locals(frame)
399 return frame.f_locals[attr]
401 # default way (only works for changing it in the topmost frame)
403 value = eval(expression, frame.f_globals, frame.f_locals)
405 Exec('%s=%s' % (attr, expression), frame.f_globals, frame.f_locals)
410 traceback.print_exc()
413 MAXIMUM_ARRAY_SIZE = 100
414 MAX_SLICE_SIZE = 1000
417 def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format):
418 _, type_name, _ = get_type(array)
419 if type_name == 'ndarray':
420 array, metaxml, r, c, f = array_to_meta_xml(array, name, format)
423 if rows == -1 and cols == -1:
426 xml += array_to_xml(array, roffset, coffset, rows, cols, format)
427 elif type_name == 'DataFrame':
428 xml = dataframe_to_xml(array, name, roffset, coffset, rows, cols, format)
430 raise VariableError("Do not know how to convert type %s to table" % (type_name))
432 return "<xml>%s</xml>" % xml
435 def array_to_xml(array, roffset, coffset, rows, cols, format):
437 rows = min(rows, MAXIMUM_ARRAY_SIZE)
438 cols = min(cols, MAXIMUM_ARRAY_SIZE)
440 # there is no obvious rule for slicing (at least 5 choices)
441 if len(array) == 1 and (rows > 1 or cols > 1):
443 if array.size > len(array):
444 array = array[roffset:, coffset:]
445 rows = min(rows, len(array))
446 cols = min(cols, len(array[0]))
449 elif array.size == len(array):
450 if roffset == 0 and rows == 1:
451 array = array[coffset:]
452 cols = min(cols, len(array))
453 elif coffset == 0 and cols == 1:
454 array = array[roffset:]
455 rows = min(rows, len(array))
457 xml += "<arraydata rows=\"%s\" cols=\"%s\"/>" % (rows, cols)
458 for row in range(rows):
459 xml += "<row index=\"%s\"/>" % to_string(row)
460 for col in range(cols):
462 if rows == 1 or cols == 1:
463 if rows == 1 and cols == 1:
471 if "ndarray" in str(type(value)):
474 value = array[row][col]
475 value = format % value
476 xml += var_to_xml(value, '')
480 def array_to_meta_xml(array, name, format):
481 type = array.dtype.kind
485 # initial load, compute slice
488 slice += '[0]' * (l - 2)
489 for r in range(l - 2):
493 elif type == 'i' or type == 'u':
498 format = format.replace('%', '')
503 raise Exception("%s has more than 2 dimensions." % slice)
505 # special case with 1D arrays arr[i, :] - row, but arr[:, i] - column with equal shape and ndim
506 # http://stackoverflow.com/questions/16837946/numpy-a-2-rows-1-column-file-loadtxt-returns-1row-2-columns
507 # explanation: http://stackoverflow.com/questions/15165170/how-do-i-maintain-row-column-orientation-of-vectors-in-numpy?rq=1
508 # we use kind of a hack - get information about memory from C_CONTIGUOUS
509 is_row = array.flags['C_CONTIGUOUS']
513 cols = min(len(array), MAX_SLICE_SIZE)
514 if cols < len(array):
515 reslice = '[0:%s]' % (cols)
516 array = array[0:cols]
519 rows = min(len(array), MAX_SLICE_SIZE)
520 if rows < len(array):
521 reslice = '[0:%s]' % (rows)
522 array = array[0:rows]
524 rows = min(array.shape[-2], MAX_SLICE_SIZE)
525 cols = min(array.shape[-1], MAX_SLICE_SIZE)
526 if cols < array.shape[-1] or rows < array.shape[-2]:
527 reslice = '[0:%s, 0:%s]' % (rows, cols)
528 array = array[0:rows, 0:cols]
530 # avoid slice duplication
531 if not slice.endswith(reslice):
536 bounds = (array.min(), array.max())
537 xml = '<array slice=\"%s\" rows=\"%s\" cols=\"%s\" format=\"%s\" type=\"%s\" max=\"%s\" min=\"%s\"/>' % \
538 (slice, rows, cols, format, type, bounds[1], bounds[0])
539 return array, xml, rows, cols, format
542 def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format):
544 :type df: pandas.core.frame.DataFrame
554 num_rows = min(df.shape[0], MAX_SLICE_SIZE)
555 num_cols = min(df.shape[1], MAX_SLICE_SIZE)
556 if (num_rows, num_cols) != df.shape:
557 df = df.iloc[0:num_rows, 0: num_cols]
558 slice = '.iloc[0:%s, 0:%s]' % (num_rows, num_cols)
562 xml = '<array slice=\"%s\" rows=\"%s\" cols=\"%s\" format=\"\" type=\"\" max=\"0\" min=\"0\"/>\n' % \
563 (slice, num_rows, num_cols)
565 if (rows, cols) == (-1, -1):
566 rows, cols = num_rows, num_cols
568 rows = min(rows, MAXIMUM_ARRAY_SIZE)
569 cols = min(min(cols, MAXIMUM_ARRAY_SIZE), num_cols)
570 # need to precompute column bounds here before slicing!
571 col_bounds = [None] * cols
572 for col in range(cols):
573 dtype = df.dtypes.iloc[col].kind
575 cvalues = df.iloc[:, col]
576 bounds = (cvalues.min(), cvalues.max())
579 col_bounds[col] = bounds
581 df = df.iloc[roffset: roffset + rows, coffset: coffset + cols]
582 rows, cols = df.shape
584 def default_format(type):
587 elif type == 'i' or type == 'u':
592 xml += "<headerdata rows=\"%s\" cols=\"%s\">\n" % (rows, cols)
593 format = format.replace('%', '')
596 get_label = lambda label: str(label) if not isinstance(label, tuple) else '/'.join([str(i) for i in label])
598 for col in range(cols):
599 dtype = df.dtypes.iloc[col].kind
600 fmt = format if (dtype == 'f' and format) else default_format(dtype)
601 col_formats.append('%' + fmt)
602 bounds = col_bounds[col]
604 xml += '<colheader index=\"%s\" label=\"%s\" type=\"%s\" format=\"%s\" max=\"%s\" min=\"%s\" />\n' % \
605 (str(col), get_label(df.axes[1].values[col]), dtype, fmt, bounds[1], bounds[0])
606 for row, label in enumerate(iter(df.axes[0])):
607 xml += "<rowheader index=\"%s\" label = \"%s\"/>\n" % \
608 (str(row), get_label(label))
609 xml += "</headerdata>\n"
610 xml += "<arraydata rows=\"%s\" cols=\"%s\"/>\n" % (rows, cols)
611 for row in range(rows):
612 xml += "<row index=\"%s\"/>\n" % str(row)
613 for col in range(cols):
614 value = df.iat[row, col]
615 value = col_formats[col] % value
616 xml += var_to_xml(value, '')