eric6/DebugClients/Python/DebugVariables.py

branch
Variables Viewer
changeset 6969
fd7af2312383
parent 6952
31602c3f09fd
child 6978
720247f98e1f
diff -r c634f51e40ec -r fd7af2312383 eric6/DebugClients/Python/DebugVariables.py
--- 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

eric ide

mercurial