eric7/DebugClients/Python/DebugVariables.py

branch
eric7
changeset 8312
800c432b34c8
parent 8243
cc717c2ae956
child 8479
903b7d3b58af
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2016 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing classes and functions to dump variable contents.
8 """
9
10 import contextlib
11
12 from DebugConfig import ConfigQtNames, ConfigKnownQtTypes, BatchSize
13
14 #
15 # This code was inspired by pydevd.
16 #
17
18 ############################################################
19 ## Classes implementing resolvers for various compund types
20 ############################################################
21
22
23 class BaseResolver:
24 """
25 Base class of the resolver class tree.
26 """
27 def resolve(self, var, attribute):
28 """
29 Public method to get an attribute from a variable.
30
31 @param var variable to extract an attribute or value from
32 @type any
33 @param attribute name of the attribute to extract
34 @type str
35 @return value of the attribute
36 @rtype any
37 """
38 return getattr(var, attribute, None)
39
40 def getDictionary(self, var):
41 """
42 Public method to get the attributes of a variable as a dictionary.
43
44 @param var variable to be converted
45 @type any
46 @return dictionary containing the variable attributes
47 @rtype dict
48 """
49 names = dir(var)
50 if not names and hasattr(var, "__members__"):
51 names = var.__members__
52
53 d = {}
54 for name in names:
55 with contextlib.suppress(Exception):
56 attribute = getattr(var, name)
57 d[name] = attribute
58
59 return d
60
61
62 ############################################################
63 ## Default Resolver
64 ############################################################
65
66
67 class DefaultResolver(BaseResolver):
68 """
69 Class used to resolve the default way.
70 """
71 def getDictionary(self, var):
72 """
73 Public method to get the attributes of a variable as a dictionary.
74
75 @param var variable to be converted
76 @type any
77 @yield tuple containing the batch start index and a dictionary
78 containing the variable attributes
79 @ytype tuple of (int, dict)
80 """
81 names = dir(var)
82 if not names and hasattr(var, "__members__"):
83 names = var.__members__
84
85 d = {}
86 for name in names:
87 with contextlib.suppress(Exception):
88 attribute = getattr(var, name)
89 d[name] = attribute
90
91 yield -1, d
92 while True:
93 yield -2, {}
94
95
96 ############################################################
97 ## Resolver for Dictionaries
98 ############################################################
99
100
101 class DictResolver(BaseResolver):
102 """
103 Class used to resolve from a dictionary.
104 """
105 def resolve(self, var, attribute):
106 """
107 Public method to get an attribute from a variable.
108
109 @param var variable to extract an attribute or value from
110 @type dict
111 @param attribute name of the attribute to extract
112 @type str
113 @return value of the attribute
114 @rtype any
115 """
116 if " (ID:" not in attribute:
117 try:
118 return var[attribute]
119 except Exception:
120 return getattr(var, attribute, None)
121
122 expectedID = int(attribute.split(" (ID:")[-1][:-1])
123 for key, value in var.items():
124 if id(key) == expectedID:
125 return value
126
127 return None
128
129 def keyToStr(self, key):
130 """
131 Public method to get a string representation for a key.
132
133 @param key key to be converted
134 @type any
135 @return string representation of the given key
136 @rtype str
137 """
138 if isinstance(key, str):
139 key = repr(key)
140 # Special handling for bytes object
141 # Raw and f-Strings are always converted to str
142 if key[0] == 'b':
143 key = key[1:]
144
145 return key # __IGNORE_WARNING_M834__
146
147 def getDictionary(self, var):
148 """
149 Public method to get the attributes of a variable as a dictionary.
150
151 @param var variable to be converted
152 @type any
153 @yield tuple containing the batch start index and a dictionary
154 containing the variable attributes
155 @ytype tuple of (int, dict)
156 """
157 d = {}
158 start = count = 0
159 allItems = list(var.items())
160 try:
161 # Fast path: all items from same type
162 allItems.sort(key=lambda x: x[0])
163 except TypeError:
164 # Slow path: only sort items with same type (Py3 only)
165 allItems.sort(key=lambda x: (str(x[0]), x[0]))
166
167 for key, value in allItems:
168 key = "{0} (ID:{1})".format(self.keyToStr(key), id(key))
169 d[key] = value
170 count += 1
171 if count >= BatchSize:
172 yield start, d
173 start += count
174 count = 0
175 d = {}
176
177 if d:
178 yield start, d
179
180 # in case it has additional fields
181 d = super().getDictionary(var)
182 yield -1, d
183
184 while True:
185 yield -2, {}
186
187
188 ############################################################
189 ## Resolver for Lists and Tuples
190 ############################################################
191
192
193 class ListResolver(BaseResolver):
194 """
195 Class used to resolve from a tuple or list.
196 """
197 def resolve(self, var, attribute):
198 """
199 Public method to get an attribute from a variable.
200
201 @param var variable to extract an attribute or value from
202 @type tuple or list
203 @param attribute name of the attribute to extract
204 @type str
205 @return value of the attribute
206 @rtype any
207 """
208 try:
209 return var[int(attribute)]
210 except Exception:
211 return getattr(var, attribute, None)
212
213 def getDictionary(self, var):
214 """
215 Public method to get the attributes of a variable as a dictionary.
216
217 @param var variable to be converted
218 @type any
219 @yield tuple containing the batch start index and a dictionary
220 containing the variable attributes
221 @ytype tuple of (int, dict)
222 """
223 d = {}
224 start = count = 0
225 for idx, value in enumerate(var):
226 d[idx] = value
227 count += 1
228 if count >= BatchSize:
229 yield start, d
230 start = idx + 1
231 count = 0
232 d = {}
233
234 if d:
235 yield start, d
236
237 # in case it has additional fields
238 d = super().getDictionary(var)
239 yield -1, d
240
241 while True:
242 yield -2, {}
243
244
245 ############################################################
246 ## Resolver for dict_items, dict_keys and dict_values
247 ############################################################
248
249
250 class DictViewResolver(ListResolver):
251 """
252 Class used to resolve from dict views.
253 """
254 def resolve(self, var, attribute):
255 """
256 Public method to get an attribute from a variable.
257
258 @param var variable to extract an attribute or value from
259 @type tuple or list
260 @param attribute id of the value to extract
261 @type str
262 @return value of the attribute
263 @rtype any
264 """
265 return super().resolve(list(var), attribute)
266
267 def getDictionary(self, var):
268 """
269 Public method to get the attributes of a variable as a dictionary.
270
271 @param var variable to be converted
272 @type any
273 @return dictionary containing the variable attributes
274 @rtype dict
275 """
276 return super().getDictionary(list(var))
277
278
279 ############################################################
280 ## Resolver for Sets and Frozensets
281 ############################################################
282
283
284 class SetResolver(BaseResolver):
285 """
286 Class used to resolve from a set or frozenset.
287 """
288 def resolve(self, var, attribute):
289 """
290 Public method to get an attribute from a variable.
291
292 @param var variable to extract an attribute or value from
293 @type tuple or list
294 @param attribute id of the value to extract
295 @type str
296 @return value of the attribute
297 @rtype any
298 """
299 if attribute.startswith("'ID: "):
300 attribute = attribute.split(None, 1)[1][:-1]
301 try:
302 attribute = int(attribute)
303 except Exception:
304 return getattr(var, attribute, None)
305
306 for v in var:
307 if id(v) == attribute:
308 return v
309
310 return None
311
312 def getDictionary(self, var):
313 """
314 Public method to get the attributes of a variable as a dictionary.
315
316 @param var variable to be converted
317 @type any
318 @yield tuple containing the batch start index and a dictionary
319 containing the variable attributes
320 @ytype tuple of (int, dict)
321 """
322 d = {}
323 start = count = 0
324 for value in var:
325 count += 1
326 d["'ID: {0}'".format(id(value))] = value
327 if count >= BatchSize:
328 yield start, d
329 start += count
330 count = 0
331 d = {}
332
333 if d:
334 yield start, d
335
336 # in case it has additional fields
337 additionals = super().getDictionary(var)
338 yield -1, additionals
339
340 while True:
341 yield -2, {}
342
343
344 ############################################################
345 ## Resolver for Numpy Arrays
346 ############################################################
347
348
349 class NdArrayResolver(BaseResolver):
350 """
351 Class used to resolve from numpy ndarray including some meta data.
352 """
353 def __isNumeric(self, arr):
354 """
355 Private method to check, if an array is of a numeric type.
356
357 @param arr array to check
358 @type ndarray
359 @return flag indicating a numeric array
360 @rtype bool
361 """
362 try:
363 return arr.dtype.kind in 'biufc'
364 except AttributeError:
365 return False
366
367 def resolve(self, var, attribute):
368 """
369 Public method to get an attribute from a variable.
370
371 @param var variable to extract an attribute or value from
372 @type tuple or list
373 @param attribute id of the value to extract
374 @type str
375 @return value of the attribute
376 @rtype any
377 """
378 if attribute == 'min':
379 if self.__isNumeric(var):
380 return var.min()
381 else:
382 return None
383
384 if attribute == 'max':
385 if self.__isNumeric(var):
386 return var.max()
387 else:
388 return None
389
390 if attribute == 'mean':
391 if self.__isNumeric(var):
392 return var.mean()
393 else:
394 return None
395
396 try:
397 return var[int(attribute)]
398 except Exception:
399 return getattr(var, attribute, None)
400
401 return None
402
403 def getDictionary(self, var):
404 """
405 Public method to get the attributes of a variable as a dictionary.
406
407 @param var variable to be converted
408 @type any
409 @yield tuple containing the batch start index and a dictionary
410 containing the variable attributes
411 @ytype tuple of (int, dict)
412 """
413 d = {}
414 start = count = 0
415 try:
416 len(var) # Check if it's an unsized object, e.g. np.ndarray(())
417 allItems = var.tolist()
418 except TypeError: # TypeError: len() of unsized object
419 allItems = []
420
421 for idx, value in enumerate(allItems):
422 d[str(idx)] = value
423 count += 1
424 if count >= BatchSize:
425 yield start, d
426 start += count
427 count = 0
428 d = {}
429
430 if d:
431 yield start, d
432
433 # in case it has additional fields
434 d = super().getDictionary(var)
435
436 if var.size > 1024 * 1024:
437 d['min'] = (
438 'ndarray too big, calculating min would slow down debugging')
439 d['max'] = (
440 'ndarray too big, calculating max would slow down debugging')
441 d['mean'] = (
442 'ndarray too big, calculating mean would slow down debugging')
443 elif self.__isNumeric(var):
444 if var.size == 0:
445 d['min'] = 'empty array'
446 d['max'] = 'empty array'
447 d['mean'] = 'empty array'
448 else:
449 d['min'] = var.min()
450 d['max'] = var.max()
451 d['mean'] = var.mean()
452 else:
453 d['min'] = 'not a numeric object'
454 d['max'] = 'not a numeric object'
455 d['mean'] = 'not a numeric object'
456
457 yield -1, d
458
459 while True:
460 yield -2, {}
461
462
463 ############################################################
464 ## Resolver for Django Multi Value Dictionaries
465 ############################################################
466
467
468 class MultiValueDictResolver(DictResolver):
469 """
470 Class used to resolve from Django multi value dictionaries.
471 """
472 def resolve(self, var, attribute):
473 """
474 Public method to get an attribute from a variable.
475
476 @param var variable to extract an attribute or value from
477 @type dict
478 @param attribute name of the attribute to extract
479 @type str
480 @return value of the attribute
481 @rtype any
482 """
483 if " (ID:" not in attribute:
484 try:
485 return var[attribute]
486 except Exception:
487 return getattr(var, attribute, None)
488
489 expectedID = int(attribute.split(" (ID:")[-1][:-1])
490 for key in var:
491 if id(key) == expectedID:
492 return var.getlist(key)
493
494 return None
495
496 def getDictionary(self, var):
497 """
498 Public method to get the attributes of a variable as a dictionary.
499
500 @param var variable to be converted
501 @type any
502 @yield tuple containing the batch start index and a dictionary
503 containing the variable attributes
504 @ytype tuple of (int, dict)
505 """
506 d = {}
507 start = count = 0
508 allKeys = list(var.keys())
509 try:
510 # Fast path: all items from same type
511 allKeys.sort()
512 except TypeError:
513 # Slow path: only sort items with same type (Py3 only)
514 allKeys.sort(key=lambda x: (str(x), x))
515
516 for key in allKeys:
517 dkey = "{0} (ID:{1})".format(self.keyToStr(key), id(key))
518 d[dkey] = var.getlist(key)
519 count += 1
520 if count >= BatchSize:
521 yield start, d
522 start += count
523 count = 0
524 d = {}
525
526 if d:
527 yield start, d
528
529 # in case it has additional fields
530 d = super().getDictionary(var)
531 yield -1, d
532
533 while True:
534 yield -2, {}
535
536
537 ############################################################
538 ## Resolver for array.array
539 ############################################################
540
541
542 class ArrayResolver(BaseResolver):
543 """
544 Class used to resolve from array.array including some meta data.
545 """
546 TypeCodeMap = {
547 "b": "int (signed char)",
548 "B": "int (unsigned char)",
549 "u": "Unicode character (Py_UNICODE)",
550 "h": "int (signed short)",
551 "H": "int (unsigned short)",
552 "i": "int (signed int)",
553 "I": "int (unsigned int)",
554 "l": "int (signed long)",
555 "L": "int (unsigned long)",
556 "q": "int (signed long long)",
557 "Q": "int (unsigned long long)",
558 "f": "float (float)",
559 "d": "float (double)",
560 }
561
562 def resolve(self, var, attribute):
563 """
564 Public method to get an attribute from a variable.
565
566 @param var variable to extract an attribute or value from
567 @type tuple or list
568 @param attribute id of the value to extract
569 @type str
570 @return value of the attribute
571 @rtype any
572 """
573 try:
574 return var[int(attribute)]
575 except Exception:
576 return getattr(var, attribute, None)
577
578 return None
579
580 def getDictionary(self, var):
581 """
582 Public method to get the attributes of a variable as a dictionary.
583
584 @param var variable to be converted
585 @type any
586 @yield tuple containing the batch start index and a dictionary
587 containing the variable attributes
588 @ytype tuple of (int, dict)
589 """
590 d = {}
591 start = count = 0
592 allItems = var.tolist()
593
594 for idx, value in enumerate(allItems):
595 d[str(idx)] = value
596 count += 1
597 if count >= BatchSize:
598 yield start, d
599 start += count
600 count = 0
601 d = {}
602
603 if d:
604 yield start, d
605
606 # in case it has additional fields
607 d = super().getDictionary(var)
608
609 # Special data for array type: convert typecode to readable text
610 d['type'] = self.TypeCodeMap.get(var.typecode, 'illegal type')
611
612 yield -1, d
613
614 while True:
615 yield -2, {}
616
617
618 defaultResolver = DefaultResolver()
619 dictResolver = DictResolver()
620 listResolver = ListResolver()
621 dictViewResolver = DictViewResolver()
622 setResolver = SetResolver()
623 ndarrayResolver = NdArrayResolver()
624 multiValueDictResolver = MultiValueDictResolver()
625 arrayResolver = ArrayResolver()
626
627 ############################################################
628 ## Methods to determine the type of a variable and the
629 ## resolver class to use
630 ############################################################
631
632 _TypeMap = None
633
634
635 def _initTypeMap():
636 """
637 Protected function to initialize the type map.
638 """
639 global _TypeMap
640
641 _TypeMap = [
642 (type(None), None,),
643 (int, None),
644 (float, None),
645 (complex, None),
646 (str, None),
647 (tuple, listResolver),
648 (list, listResolver),
649 (dict, dictResolver),
650 (set, setResolver),
651 (frozenset, setResolver),
652 ]
653
654 with contextlib.suppress(Exception):
655 _TypeMap.append((long, None)) # __IGNORE_WARNING__
656
657 with contextlib.suppress(ImportError):
658 import array
659 _TypeMap.append((array.array, arrayResolver))
660 # array.array may not be available
661
662 with contextlib.suppress(ImportError):
663 import numpy
664 _TypeMap.append((numpy.ndarray, ndarrayResolver))
665 # numpy may not be installed
666
667 with contextlib.suppress(ImportError):
668 from django.utils.datastructures import MultiValueDict
669 # it should go before dict
670 _TypeMap.insert(0, (MultiValueDict, multiValueDictResolver))
671 # django may not be installed
672
673 with contextlib.suppress(ImportError):
674 from collections.abc import ItemsView, KeysView, ValuesView
675 _TypeMap.append((ItemsView, dictViewResolver))
676 _TypeMap.append((KeysView, dictViewResolver))
677 _TypeMap.append((ValuesView, dictViewResolver))
678 # not available on all Python versions
679
680
681 def getType(obj):
682 """
683 Public method to get the type information for an object.
684
685 @param obj object to get type information for
686 @type any
687 @return tuple containing the type name, type string and resolver
688 @rtype tuple of str, str, BaseResolver
689 """
690 typeObject = type(obj)
691 typeName = typeObject.__name__
692 # Between PyQt and PySide the returned type is different (class vs. type)
693 typeStr = str(typeObject).split(' ', 1)[-1]
694 typeStr = typeStr[1:-2]
695
696 if (
697 typeStr.startswith(ConfigQtNames) and
698 typeStr.endswith(ConfigKnownQtTypes)
699 ):
700 resolver = None
701 else:
702 if _TypeMap is None:
703 _initTypeMap()
704
705 for typeData, resolver in _TypeMap: # __IGNORE_WARNING_M507__
706 if isinstance(obj, typeData):
707 break
708 else:
709 resolver = defaultResolver
710
711 return typeName, typeStr, resolver
712
713 #
714 # eflag: noqa = Y113

eric ide

mercurial