--- a/eric6/DebugClients/Python/DebugVariables.py Sun Apr 21 17:27:52 2019 +0200 +++ b/eric6/DebugClients/Python/DebugVariables.py Sun Apr 21 21:20:24 2019 +0200 @@ -7,14 +7,16 @@ Module implementing classes and functions to dump variable contents. """ +import sys + +from DebugConfig import ConfigQtNames, BatchSize + # # This code was inspired by pydevd. # -MaxItemsToHandle = 300 -TooLargeMessage = ("Too large to show contents. Max items to show: " + - str(MaxItemsToHandle)) -TooLargeAttribute = "Too large to be handled." +if sys.version_info[0] > 2: + basestring = str ############################################################ ## Classes implementing resolvers for various compund types @@ -35,44 +37,6 @@ @type str @return value of the attribute @rtype any - @exception NotImplementedError raised to indicate a missing - implementation - """ # __IGNORE_WARNING_D235__ - raise NotImplementedError - - def getDictionary(self, var): - """ - Public method to get the attributes of a variable as a dictionary. - - @param var variable to be converted - @type any - @return dictionary containing the variable attributes - @rtype dict - @exception NotImplementedError raised to indicate a missing - implementation - """ # __IGNORE_WARNING_D235__ - raise NotImplementedError - - -############################################################ -## Default Resolver -############################################################ - - -class DefaultResolver(BaseResolver): - """ - Class used to resolve the default way. - """ - def resolve(self, var, attribute): - """ - Public method to get an attribute from a variable. - - @param var variable to extract an attribute or value from - @type any - @param attribute name of the attribute to extract - @type str - @return value of the attribute - @rtype any """ return getattr(var, attribute, None) @@ -101,6 +65,41 @@ ############################################################ +## Default Resolver +############################################################ + + +class DefaultResolver(BaseResolver): + """ + Class used to resolve the default way. + """ + def getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @return dictionary containing the variable attributes + @rtype dict + """ + names = dir(var) + if not names and hasattr(var, "__members__"): + names = var.__members__ + + d = {} + for name in names: + try: + attribute = getattr(var, name) + d[name] = attribute + except Exception: + pass # if we can't get it, simply ignore it + + yield -1, d + while True: + yield -2, {} + + +############################################################ ## Resolver for Dictionaries ############################################################ @@ -120,16 +119,13 @@ @return value of the attribute @rtype any """ - if attribute in ('___len___', TooLargeAttribute): - return None - - if "(ID:" not in attribute: + if " (ID:" not in attribute: try: return var[attribute] except Exception: return getattr(var, attribute, None) - expectedID = int(attribute.split("(ID:")[-1][:-1]) + expectedID = int(attribute.split(" (ID:")[-1][:-1]) for key, value in var.items(): if id(key) == expectedID: return value @@ -145,10 +141,14 @@ @return string representation of the given key @rtype str """ - if isinstance(key, str): - return repr(key) - else: - return key + if isinstance(key, basestring): + key = repr(key) + # Special handling for Python2 unicode strings and bytes object + # Raw and f-Strings are always converted to (unicode) str + if key[0] in 'ub': + key = key[1:] + + return key # __IGNORE_WARNING_M834__ def getDictionary(self, var): """ @@ -160,22 +160,34 @@ @rtype dict """ d = {} - count = 0 - for key, value in var.items(): - count += 1 + start = count = 0 + allItems = list(var.items()) + try: + # Fast path: all items from same type + allItems.sort(key=lambda x: x[0]) + except TypeError: + # Slow path: only sort items with same type (Py3 only) + allItems.sort(key=lambda x: (str(x[0]), x[0])) + + for key, value in allItems: key = "{0} (ID:{1})".format(self.keyToStr(key), id(key)) d[key] = value - if count > MaxItemsToHandle: - d[TooLargeAttribute] = TooLargeMessage - break + count += 1 + if count >= BatchSize: + yield start, d + start += count + count = 0 + d = {} - d["___len___"] = len(var) + if d: + yield start, d # in case it has additional fields - additionals = defaultResolver.getDictionary(var) - d.update(additionals) + d = super(DictResolver, self).getDictionary(var) + yield -1, d - return d + while True: + yield -2, {} ############################################################ @@ -198,9 +210,6 @@ @return value of the attribute @rtype any """ - if attribute in ('___len___', TooLargeAttribute): - return None - try: return var[int(attribute)] except Exception: @@ -216,22 +225,26 @@ @rtype dict """ d = {} - count = 0 - for value in var: - d[str(count)] = value + start = count = 0 + for idx, value in enumerate(var): + d[str(idx)] = value count += 1 - if count > MaxItemsToHandle: - d[TooLargeAttribute] = TooLargeMessage - break + if count >= BatchSize: + yield start, d + start = idx + 1 + count = 0 + d = {} - d["___len___"] = len(var) + if d: + yield start, d # in case it has additional fields - additionals = defaultResolver.getDictionary(var) - d.update(additionals) + d = super(ListResolver, self).getDictionary(var) + yield -1, d - return d - + while True: + yield -2, {} + ############################################################ ## Resolver for Sets and Frozensets @@ -253,11 +266,8 @@ @return value of the attribute @rtype any """ - if attribute in ('___len___', TooLargeAttribute): - return None - - if attribute.startswith("ID: "): - attribute = attribute.split(None, 1)[1] + if attribute.startswith("'ID: "): + attribute = attribute.split(None, 1)[1][:-1] try: attribute = int(attribute) except Exception: @@ -279,22 +289,26 @@ @rtype dict """ d = {} - count = 0 + start = count = 0 for value in var: count += 1 - d["ID: " + str(id(value))] = value - if count > MaxItemsToHandle: - d[TooLargeAttribute] = TooLargeMessage - break - - d["___len___"] = len(var) + d["'ID: {0}'".format(id(value))] = value + if count >= BatchSize: + yield start, d + start += count + count = 0 + d = {} + + if d: + yield start, d # in case it has additional fields - additionals = defaultResolver.getDictionary(var) - d.update(additionals) + additionals = super(SetResolver, self).getDictionary(var) + yield -1, additionals - return d - + while True: + yield -2, {} + ############################################################ ## Resolver for Numpy Arrays @@ -330,9 +344,6 @@ @return value of the attribute @rtype any """ - if attribute == '__internals__': - return defaultResolver.getDictionary(var) - if attribute == 'min': if self.__isNumeric(var): return var.min() @@ -351,25 +362,10 @@ else: return None - if attribute == 'shape': - return var.shape - - if attribute == 'dtype': - return var.dtype - - if attribute == 'size': - return var.size - - if attribute.startswith('['): - container = NdArrayItemsContainer() - count = 0 - for element in var: - setattr(container, str(count), element) - count += 1 - if count > MaxItemsToHandle: - setattr(container, TooLargeAttribute, TooLargeMessage) - break - return container + try: + return var[int(attribute)] + except Exception: + return getattr(var, attribute, None) return None @@ -383,38 +379,49 @@ @rtype dict """ d = {} - d['__internals__'] = defaultResolver.getDictionary(var) + start = count = 0 + allItems = var.tolist() + + for idx, value in enumerate(allItems): + d[str(idx)] = value + count += 1 + if count >= BatchSize: + yield start, d + start += count + count = 0 + d = {} + + if d: + yield start, d + + # in case it has additional fields + d = super(NdArrayResolver, self).getDictionary(var) + if var.size > 1024 * 1024: d['min'] = 'ndarray too big, calculating min would slow down' \ ' debugging' d['max'] = 'ndarray too big, calculating max would slow down' \ ' debugging' - else: - if self.__isNumeric(var): - if var.size == 0: - d['min'] = 'empty array' - d['max'] = 'empty array' - d['mean'] = 'empty array' - else: - d['min'] = var.min() - d['max'] = var.max() - d['mean'] = var.mean() + d['mean'] = 'ndarray too big, calculating mean would slow down' \ + ' debugging' + elif self.__isNumeric(var): + if var.size == 0: + d['min'] = 'empty array' + d['max'] = 'empty array' + d['mean'] = 'empty array' else: - d['min'] = 'not a numeric object' - d['max'] = 'not a numeric object' - d['mean'] = 'not a numeric object' - d['shape'] = var.shape - d['dtype'] = var.dtype - d['size'] = var.size - d['[0:{0}]'.format(len(var) - 1)] = list(var[0:MaxItemsToHandle]) - return d - - -class NdArrayItemsContainer: - """ - Class to store ndarray items. - """ - pass + d['min'] = var.min() + d['max'] = var.max() + d['mean'] = var.mean() + else: + d['min'] = 'not a numeric object' + d['max'] = 'not a numeric object' + d['mean'] = 'not a numeric object' + + yield -1, d + + while True: + yield -2, {} ############################################################ @@ -437,20 +444,16 @@ @return value of the attribute @rtype any """ - if attribute in ('___len___', TooLargeAttribute): - return None - - if "(ID:" not in attribute: + if " (ID:" not in attribute: try: return var[attribute] except Exception: return getattr(var, attribute, None) - expectedID = int(attribute.split("(ID:")[-1][:-1]) + expectedID = int(attribute.split(" (ID:")[-1][:-1]) for key in var.keys(): if id(key) == expectedID: - value = var.getlist(key) - return value + return var.getlist(key) return None @@ -464,20 +467,35 @@ @rtype dict """ d = {} - count = 0 - for key in var.keys(): + start = count = 0 + allKeys = list(var.keys()) + try: + # Fast path: all items from same type + allKeys.sort() + except TypeError: + # Slow path: only sort items with same type (Py3 only) + allKeys.sort(key=lambda x: (str(x), x)) + + for key in allKeys: + dkey = "{0} (ID:{1})".format(self.keyToStr(key), id(key)) + d[dkey] = var.getlist(key) count += 1 - value = var.getlist(key) - key = "{0} (ID:{1})".format(self.keyToStr(key), id(key)) - d[key] = value - if count > MaxItemsToHandle: - d[TooLargeAttribute] = TooLargeMessage - break + if count >= BatchSize: + yield start, d + start += count + count = 0 + d = {} - d["___len___"] = len(var) + if d: + yield start, d - return d - + # in case it has additional fields + d = super(DictResolver, self).getDictionary(var) + yield -1, d + + while True: + yield -2, {} + ############################################################ ## Resolver for array.array @@ -515,28 +533,10 @@ @return value of the attribute @rtype any """ - if attribute == 'itemsize': - return var.itemsize - - if attribute == 'typecode': - return var.typecode - - if attribute == 'type': - if var.typecode in ArrayResolver.TypeCodeMap: - return ArrayResolver.TypeCodeMap[var.typecode] - else: - return 'illegal type' - - if attribute.startswith('['): - container = ArrayItemsContainer() - count = 0 - for element in var: - setattr(container, str(count), element) - count += 1 - if count > MaxItemsToHandle: - setattr(container, TooLargeAttribute, TooLargeMessage) - break - return container + try: + return var[int(attribute)] + except Exception: + return getattr(var, attribute, None) return None @@ -550,21 +550,31 @@ @rtype dict """ d = {} - d['typecode'] = var.typecode - if var.typecode in ArrayResolver.TypeCodeMap: - d['type'] = ArrayResolver.TypeCodeMap[var.typecode] - else: - d['type'] = 'illegal type' - d['itemsize'] = var.itemsize - d['[0:{0}]'.format(len(var) - 1)] = var.tolist()[0:MaxItemsToHandle] - return d - - -class ArrayItemsContainer: - """ - Class to store array.array items. - """ - pass + start = count = 0 + allItems = var.tolist() + + for idx, value in enumerate(allItems): + d[str(idx)] = value + count += 1 + if count >= BatchSize: + yield start, d + start += count + count = 0 + d = {} + + if d: + yield start, d + + # in case it has additional fields + d = super(ArrayResolver, self).getDictionary(var) + + # Special data for array type: convert typecode to readable text + d['type'] = self.TypeCodeMap.get(var.typecode, 'illegal type') + + yield -1, d + + while True: + yield -2, {} defaultResolver = DefaultResolver() @@ -598,6 +608,8 @@ (tuple, listResolver), (list, listResolver), (dict, dictResolver), + (set, setResolver), + (frozenset, setResolver), ] try: @@ -611,16 +623,6 @@ pass # not available on all python versions try: - _TypeMap.append((set, setResolver)) # __IGNORE_WARNING__ - except Exception: - pass # not available on all python versions - - try: - _TypeMap.append((frozenset, setResolver)) # __IGNORE_WARNING__ - except Exception: - pass # not available on all python versions - - try: import array _TypeMap.append((array.array, arrayResolver)) except ImportError: @@ -634,8 +636,8 @@ try: from django.utils.datastructures import MultiValueDict + # it should go before dict _TypeMap.insert(0, (MultiValueDict, multiValueDictResolver)) - # it should go before dict except ImportError: pass # django may not be installed @@ -646,27 +648,28 @@ @param obj object to get type information for @type any - @return tuple containing the type, type name, type string and resolver - @rtype tuple of type, str, str, BaseResolver + @return tuple containing the type name, type string and resolver + @rtype tuple of str, str, BaseResolver """ typeObject = type(obj) typeName = typeObject.__name__ - typeStr = str(typeObject)[8:-2] + # Between PyQt and PySide the returned type is different (class vs. type) + typeStr = str(typeObject).split(' ', 1)[-1] + typeStr = typeStr[1:-2] - if typeStr.startswith(("PyQt5.", "PyQt4.")): + if typeStr.startswith(ConfigQtNames): resolver = None else: if _TypeMap is None: _initTypeMap() - for typeData in _TypeMap: - if isinstance(obj, typeData[0]): - resolver = typeData[1] + for typeData, resolver in _TypeMap: # __IGNORE_WARNING_M507__ + if isinstance(obj, typeData): break else: resolver = defaultResolver - return typeObject, typeName, typeStr, resolver + return typeName, typeStr, resolver # # eflag: noqa = M702