1# -*- coding: utf-8 -*- 2"""Tools for inspecting Python objects. 3 4This file was forked from the IPython project: 5 6* Copyright (c) 2008-2014, IPython Development Team 7* Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu> 8* Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> 9* Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> 10""" 11import os 12import io 13import sys 14import types 15import inspect 16import itertools 17import linecache 18import collections 19 20from xonsh.lazyasd import LazyObject 21from xonsh.tokenize import detect_encoding 22from xonsh.openpy import read_py_file 23from xonsh.tools import cast_unicode, safe_hasattr, indent, print_color, format_color 24from xonsh.platform import HAS_PYGMENTS, PYTHON_VERSION_INFO 25from xonsh.lazyimps import pygments, pyghooks 26from xonsh.style_tools import partial_color_tokenize 27 28 29# builtin docstrings to ignore 30_func_call_docstring = LazyObject( 31 lambda: types.FunctionType.__call__.__doc__, globals(), "_func_call_docstring" 32) 33_object_init_docstring = LazyObject( 34 lambda: object.__init__.__doc__, globals(), "_object_init_docstring" 35) 36_builtin_type_docstrings = LazyObject( 37 lambda: { 38 t.__doc__ for t in (types.ModuleType, types.MethodType, types.FunctionType) 39 }, 40 globals(), 41 "_builtin_type_docstrings", 42) 43 44_builtin_func_type = LazyObject(lambda: type(all), globals(), "_builtin_func_type") 45# Bound methods have the same type as builtin functions 46_builtin_meth_type = LazyObject( 47 lambda: type(str.upper), globals(), "_builtin_meth_type" 48) 49 50info_fields = LazyObject( 51 lambda: [ 52 "type_name", 53 "base_class", 54 "string_form", 55 "namespace", 56 "length", 57 "file", 58 "definition", 59 "docstring", 60 "source", 61 "init_definition", 62 "class_docstring", 63 "init_docstring", 64 "call_def", 65 "call_docstring", 66 # These won't be printed but will be used to determine how to 67 # format the object 68 "ismagic", 69 "isalias", 70 "isclass", 71 "argspec", 72 "found", 73 "name", 74 ], 75 globals(), 76 "info_fields", 77) 78 79 80def object_info(**kw): 81 """Make an object info dict with all fields present.""" 82 infodict = dict(itertools.zip_longest(info_fields, [None])) 83 infodict.update(kw) 84 return infodict 85 86 87def get_encoding(obj): 88 """Get encoding for python source file defining obj 89 90 Returns None if obj is not defined in a sourcefile. 91 """ 92 ofile = find_file(obj) 93 # run contents of file through pager starting at line where the object 94 # is defined, as long as the file isn't binary and is actually on the 95 # filesystem. 96 if ofile is None: 97 return None 98 elif ofile.endswith((".so", ".dll", ".pyd")): 99 return None 100 elif not os.path.isfile(ofile): 101 return None 102 else: 103 # Print only text files, not extension binaries. Note that 104 # getsourcelines returns lineno with 1-offset and page() uses 105 # 0-offset, so we must adjust. 106 with io.open(ofile, "rb") as buf: # Tweaked to use io.open for Python 2 107 encoding, _ = detect_encoding(buf.readline) 108 return encoding 109 110 111def getdoc(obj): 112 """Stable wrapper around inspect.getdoc. 113 114 This can't crash because of attribute problems. 115 116 It also attempts to call a getdoc() method on the given object. This 117 allows objects which provide their docstrings via non-standard mechanisms 118 (like Pyro proxies) to still be inspected by ipython's ? system.""" 119 # Allow objects to offer customized documentation via a getdoc method: 120 try: 121 ds = obj.getdoc() 122 except Exception: # pylint:disable=broad-except 123 pass 124 else: 125 # if we get extra info, we add it to the normal docstring. 126 if isinstance(ds, str): 127 return inspect.cleandoc(ds) 128 129 try: 130 docstr = inspect.getdoc(obj) 131 encoding = get_encoding(obj) 132 return cast_unicode(docstr, encoding=encoding) 133 except Exception: # pylint:disable=broad-except 134 # Harden against an inspect failure, which can occur with 135 # SWIG-wrapped extensions. 136 raise 137 138 139def getsource(obj, is_binary=False): 140 """Wrapper around inspect.getsource. 141 142 This can be modified by other projects to provide customized source 143 extraction. 144 145 Inputs: 146 147 - obj: an object whose source code we will attempt to extract. 148 149 Optional inputs: 150 151 - is_binary: whether the object is known to come from a binary source. 152 This implementation will skip returning any output for binary objects, 153 but custom extractors may know how to meaningfully process them.""" 154 155 if is_binary: 156 return None 157 else: 158 # get source if obj was decorated with @decorator 159 if hasattr(obj, "__wrapped__"): 160 obj = obj.__wrapped__ 161 try: 162 src = inspect.getsource(obj) 163 except TypeError: 164 if hasattr(obj, "__class__"): 165 src = inspect.getsource(obj.__class__) 166 encoding = get_encoding(obj) 167 return cast_unicode(src, encoding=encoding) 168 169 170def is_simple_callable(obj): 171 """True if obj is a function ()""" 172 return ( 173 inspect.isfunction(obj) 174 or inspect.ismethod(obj) 175 or isinstance(obj, _builtin_func_type) 176 or isinstance(obj, _builtin_meth_type) 177 ) 178 179 180def getargspec(obj): 181 """Wrapper around :func:`inspect.getfullargspec` on Python 3, and 182 :func:inspect.getargspec` on Python 2. 183 184 In addition to functions and methods, this can also handle objects with a 185 ``__call__`` attribute. 186 """ 187 if safe_hasattr(obj, "__call__") and not is_simple_callable(obj): 188 obj = obj.__call__ 189 190 return inspect.getfullargspec(obj) 191 192 193def format_argspec(argspec): 194 """Format argspect, convenience wrapper around inspect's. 195 196 This takes a dict instead of ordered arguments and calls 197 inspect.format_argspec with the arguments in the necessary order. 198 """ 199 return inspect.formatargspec( 200 argspec["args"], argspec["varargs"], argspec["varkw"], argspec["defaults"] 201 ) 202 203 204def call_tip(oinfo, format_call=True): 205 """Extract call tip data from an oinfo dict. 206 207 Parameters 208 ---------- 209 oinfo : dict 210 211 format_call : bool, optional 212 If True, the call line is formatted and returned as a string. If not, a 213 tuple of (name, argspec) is returned. 214 215 Returns 216 ------- 217 call_info : None, str or (str, dict) tuple. 218 When format_call is True, the whole call information is formatted as a 219 single string. Otherwise, the object's name and its argspec dict are 220 returned. If no call information is available, None is returned. 221 222 docstring : str or None 223 The most relevant docstring for calling purposes is returned, if 224 available. The priority is: call docstring for callable instances, then 225 constructor docstring for classes, then main object's docstring otherwise 226 (regular functions). 227 """ 228 # Get call definition 229 argspec = oinfo.get("argspec") 230 if argspec is None: 231 call_line = None 232 else: 233 # Callable objects will have 'self' as their first argument, prune 234 # it out if it's there for clarity (since users do *not* pass an 235 # extra first argument explicitly). 236 try: 237 has_self = argspec["args"][0] == "self" 238 except (KeyError, IndexError): 239 pass 240 else: 241 if has_self: 242 argspec["args"] = argspec["args"][1:] 243 244 call_line = oinfo["name"] + format_argspec(argspec) 245 246 # Now get docstring. 247 # The priority is: call docstring, constructor docstring, main one. 248 doc = oinfo.get("call_docstring") 249 if doc is None: 250 doc = oinfo.get("init_docstring") 251 if doc is None: 252 doc = oinfo.get("docstring", "") 253 254 return call_line, doc 255 256 257def find_file(obj): 258 """Find the absolute path to the file where an object was defined. 259 260 This is essentially a robust wrapper around `inspect.getabsfile`. 261 262 Returns None if no file can be found. 263 264 Parameters 265 ---------- 266 obj : any Python object 267 268 Returns 269 ------- 270 fname : str 271 The absolute path to the file where the object was defined. 272 """ 273 # get source if obj was decorated with @decorator 274 if safe_hasattr(obj, "__wrapped__"): 275 obj = obj.__wrapped__ 276 277 fname = None 278 try: 279 fname = inspect.getabsfile(obj) 280 except TypeError: 281 # For an instance, the file that matters is where its class was 282 # declared. 283 if hasattr(obj, "__class__"): 284 try: 285 fname = inspect.getabsfile(obj.__class__) 286 except TypeError: 287 # Can happen for builtins 288 pass 289 except: # pylint:disable=bare-except 290 pass 291 return cast_unicode(fname) 292 293 294def find_source_lines(obj): 295 """Find the line number in a file where an object was defined. 296 297 This is essentially a robust wrapper around `inspect.getsourcelines`. 298 299 Returns None if no file can be found. 300 301 Parameters 302 ---------- 303 obj : any Python object 304 305 Returns 306 ------- 307 lineno : int 308 The line number where the object definition starts. 309 """ 310 # get source if obj was decorated with @decorator 311 if safe_hasattr(obj, "__wrapped__"): 312 obj = obj.__wrapped__ 313 314 try: 315 try: 316 lineno = inspect.getsourcelines(obj)[1] 317 except TypeError: 318 # For instances, try the class object like getsource() does 319 if hasattr(obj, "__class__"): 320 lineno = inspect.getsourcelines(obj.__class__)[1] 321 else: 322 lineno = None 323 except: # pylint:disable=bare-except 324 return None 325 326 return lineno 327 328 329if PYTHON_VERSION_INFO < (3, 5, 0): 330 FrameInfo = collections.namedtuple( 331 "FrameInfo", 332 ["frame", "filename", "lineno", "function", "code_context", "index"], 333 ) 334 335 def getouterframes(frame, context=1): 336 """Wrapper for getouterframes so that it acts like the Python v3.5 version.""" 337 return [FrameInfo(*f) for f in inspect.getouterframes(frame, context=context)] 338 339 340else: 341 getouterframes = inspect.getouterframes 342 343 344class Inspector(object): 345 """Inspects objects.""" 346 347 def __init__(self, str_detail_level=0): 348 self.str_detail_level = str_detail_level 349 350 def _getdef(self, obj, oname=""): 351 """Return the call signature for any callable object. 352 353 If any exception is generated, None is returned instead and the 354 exception is suppressed. 355 """ 356 try: 357 hdef = oname + inspect.formatargspec(*getargspec(obj)) 358 return cast_unicode(hdef) 359 except: # pylint:disable=bare-except 360 return None 361 362 def noinfo(self, msg, oname): 363 """Generic message when no information is found.""" 364 print("No %s found" % msg, end=" ") 365 if oname: 366 print("for %s" % oname) 367 else: 368 print() 369 370 def pdef(self, obj, oname=""): 371 """Print the call signature for any callable object. 372 373 If the object is a class, print the constructor information. 374 """ 375 376 if not callable(obj): 377 print("Object is not callable.") 378 return 379 380 header = "" 381 382 if inspect.isclass(obj): 383 header = self.__head("Class constructor information:\n") 384 obj = obj.__init__ 385 386 output = self._getdef(obj, oname) 387 if output is None: 388 self.noinfo("definition header", oname) 389 else: 390 print(header, output, end=" ", file=sys.stdout) 391 392 def pdoc(self, obj, oname=""): 393 """Print the docstring for any object. 394 395 Optional 396 397 -formatter: a function to run the docstring through for specially 398 formatted docstrings. 399 """ 400 401 head = self.__head # For convenience 402 lines = [] 403 ds = getdoc(obj) 404 if ds: 405 lines.append(head("Class docstring:")) 406 lines.append(indent(ds)) 407 if inspect.isclass(obj) and hasattr(obj, "__init__"): 408 init_ds = getdoc(obj.__init__) 409 if init_ds is not None: 410 lines.append(head("Init docstring:")) 411 lines.append(indent(init_ds)) 412 elif hasattr(obj, "__call__"): 413 call_ds = getdoc(obj.__call__) 414 if call_ds: 415 lines.append(head("Call docstring:")) 416 lines.append(indent(call_ds)) 417 418 if not lines: 419 self.noinfo("documentation", oname) 420 else: 421 print("\n".join(lines)) 422 423 def psource(self, obj, oname=""): 424 """Print the source code for an object.""" 425 # Flush the source cache because inspect can return out-of-date source 426 linecache.checkcache() 427 try: 428 src = getsource(obj) 429 except: # pylint:disable=bare-except 430 self.noinfo("source", oname) 431 else: 432 print(src) 433 434 def pfile(self, obj, oname=""): 435 """Show the whole file where an object was defined.""" 436 lineno = find_source_lines(obj) 437 if lineno is None: 438 self.noinfo("file", oname) 439 return 440 441 ofile = find_file(obj) 442 # run contents of file through pager starting at line where the object 443 # is defined, as long as the file isn't binary and is actually on the 444 # filesystem. 445 if ofile.endswith((".so", ".dll", ".pyd")): 446 print("File %r is binary, not printing." % ofile) 447 elif not os.path.isfile(ofile): 448 print("File %r does not exist, not printing." % ofile) 449 else: 450 # Print only text files, not extension binaries. Note that 451 # getsourcelines returns lineno with 1-offset and page() uses 452 # 0-offset, so we must adjust. 453 o = read_py_file(ofile, skip_encoding_cookie=False) 454 print(o, lineno - 1) 455 456 def _format_fields_str(self, fields, title_width=0): 457 """Formats a list of fields for display using color strings. 458 459 Parameters 460 ---------- 461 fields : list 462 A list of 2-tuples: (field_title, field_content) 463 title_width : int 464 How many characters to pad titles to. Default to longest title. 465 """ 466 out = [] 467 if title_width == 0: 468 title_width = max(len(title) + 2 for title, _ in fields) 469 for title, content in fields: 470 title_len = len(title) 471 title = "{BOLD_RED}" + title + ":{NO_COLOR}" 472 if len(content.splitlines()) > 1: 473 title += "\n" 474 else: 475 title += " ".ljust(title_width - title_len) 476 out.append(cast_unicode(title) + cast_unicode(content)) 477 return format_color("\n".join(out) + "\n") 478 479 def _format_fields_tokens(self, fields, title_width=0): 480 """Formats a list of fields for display using color tokens from 481 pygments. 482 483 Parameters 484 ---------- 485 fields : list 486 A list of 2-tuples: (field_title, field_content) 487 title_width : int 488 How many characters to pad titles to. Default to longest title. 489 """ 490 out = [] 491 if title_width == 0: 492 title_width = max(len(title) + 2 for title, _ in fields) 493 for title, content in fields: 494 title_len = len(title) 495 title = "{BOLD_RED}" + title + ":{NO_COLOR}" 496 if not isinstance(content, str) or len(content.splitlines()) > 1: 497 title += "\n" 498 else: 499 title += " ".ljust(title_width - title_len) 500 out += partial_color_tokenize(title) 501 if isinstance(content, str): 502 out[-1] = (out[-1][0], out[-1][1] + content + "\n") 503 else: 504 out += content 505 out[-1] = (out[-1][0], out[-1][1] + "\n") 506 out[-1] = (out[-1][0], out[-1][1] + "\n") 507 return out 508 509 def _format_fields(self, fields, title_width=0): 510 """Formats a list of fields for display using color tokens from 511 pygments. 512 513 Parameters 514 ---------- 515 fields : list 516 A list of 2-tuples: (field_title, field_content) 517 title_width : int 518 How many characters to pad titles to. Default to longest title. 519 """ 520 if HAS_PYGMENTS: 521 rtn = self._format_fields_tokens(fields, title_width=title_width) 522 else: 523 rtn = self._format_fields_str(fields, title_width=title_width) 524 return rtn 525 526 # The fields to be displayed by pinfo: (fancy_name, key_in_info_dict) 527 pinfo_fields1 = [("Type", "type_name")] 528 529 pinfo_fields2 = [("String form", "string_form")] 530 531 pinfo_fields3 = [ 532 ("Length", "length"), 533 ("File", "file"), 534 ("Definition", "definition"), 535 ] 536 537 pinfo_fields_obj = [ 538 ("Class docstring", "class_docstring"), 539 ("Init docstring", "init_docstring"), 540 ("Call def", "call_def"), 541 ("Call docstring", "call_docstring"), 542 ] 543 544 def pinfo(self, obj, oname="", info=None, detail_level=0): 545 """Show detailed information about an object. 546 547 Parameters 548 ---------- 549 obj : object 550 oname : str, optional 551 name of the variable pointing to the object. 552 info : dict, optional 553 a structure with some information fields which may have been 554 precomputed already. 555 detail_level : int, optional 556 if set to 1, more information is given. 557 """ 558 info = self.info(obj, oname=oname, info=info, detail_level=detail_level) 559 displayfields = [] 560 561 def add_fields(fields): 562 for title, key in fields: 563 field = info[key] 564 if field is not None: 565 displayfields.append((title, field.rstrip())) 566 567 add_fields(self.pinfo_fields1) 568 add_fields(self.pinfo_fields2) 569 570 # Namespace 571 if info["namespace"] is not None and info["namespace"] != "Interactive": 572 displayfields.append(("Namespace", info["namespace"].rstrip())) 573 574 add_fields(self.pinfo_fields3) 575 if info["isclass"] and info["init_definition"]: 576 displayfields.append(("Init definition", info["init_definition"].rstrip())) 577 578 # Source or docstring, depending on detail level and whether 579 # source found. 580 if detail_level > 0 and info["source"] is not None: 581 displayfields.append(("Source", cast_unicode(info["source"]))) 582 elif info["docstring"] is not None: 583 displayfields.append(("Docstring", info["docstring"])) 584 585 # Constructor info for classes 586 if info["isclass"]: 587 if info["init_docstring"] is not None: 588 displayfields.append(("Init docstring", info["init_docstring"])) 589 590 # Info for objects: 591 else: 592 add_fields(self.pinfo_fields_obj) 593 594 # Finally send to printer/pager: 595 if displayfields: 596 print_color(self._format_fields(displayfields)) 597 598 def info(self, obj, oname="", info=None, detail_level=0): 599 """Compute a dict with detailed information about an object. 600 601 Optional arguments: 602 603 - oname: name of the variable pointing to the object. 604 605 - info: a structure with some information fields which may have been 606 precomputed already. 607 608 - detail_level: if set to 1, more information is given. 609 """ 610 obj_type = type(obj) 611 if info is None: 612 ismagic = 0 613 isalias = 0 614 ospace = "" 615 else: 616 ismagic = info.ismagic 617 isalias = info.isalias 618 ospace = info.namespace 619 # Get docstring, special-casing aliases: 620 if isalias: 621 if not callable(obj): 622 if len(obj) >= 2 and isinstance(obj[1], str): 623 ds = "Alias to the system command:\n {0}".format(obj[1]) 624 else: # pylint:disable=bare-except 625 ds = "Alias: " + str(obj) 626 else: 627 ds = "Alias to " + str(obj) 628 if obj.__doc__: 629 ds += "\nDocstring:\n" + obj.__doc__ 630 else: 631 ds = getdoc(obj) 632 if ds is None: 633 ds = "<no docstring>" 634 635 # store output in a dict, we initialize it here and fill it as we go 636 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic) 637 638 string_max = 200 # max size of strings to show (snipped if longer) 639 shalf = int((string_max - 5) / 2) 640 641 if ismagic: 642 obj_type_name = "Magic function" 643 elif isalias: 644 obj_type_name = "System alias" 645 else: 646 obj_type_name = obj_type.__name__ 647 out["type_name"] = obj_type_name 648 649 try: 650 bclass = obj.__class__ 651 out["base_class"] = str(bclass) 652 except: # pylint:disable=bare-except 653 pass 654 655 # String form, but snip if too long in ? form (full in ??) 656 if detail_level >= self.str_detail_level: 657 try: 658 ostr = str(obj) 659 str_head = "string_form" 660 if not detail_level and len(ostr) > string_max: 661 ostr = ostr[:shalf] + " <...> " + ostr[-shalf:] 662 ostr = ("\n" + " " * len(str_head.expandtabs())).join( 663 q.strip() for q in ostr.split("\n") 664 ) 665 out[str_head] = ostr 666 except: # pylint:disable=bare-except 667 pass 668 669 if ospace: 670 out["namespace"] = ospace 671 672 # Length (for strings and lists) 673 try: 674 out["length"] = str(len(obj)) 675 except: # pylint:disable=bare-except 676 pass 677 678 # Filename where object was defined 679 binary_file = False 680 fname = find_file(obj) 681 if fname is None: 682 # if anything goes wrong, we don't want to show source, so it's as 683 # if the file was binary 684 binary_file = True 685 else: 686 if fname.endswith((".so", ".dll", ".pyd")): 687 binary_file = True 688 elif fname.endswith("<string>"): 689 fname = "Dynamically generated function. " "No source code available." 690 out["file"] = fname 691 692 # Docstrings only in detail 0 mode, since source contains them (we 693 # avoid repetitions). If source fails, we add them back, see below. 694 if ds and detail_level == 0: 695 out["docstring"] = ds 696 697 # Original source code for any callable 698 if detail_level: 699 # Flush the source cache because inspect can return out-of-date 700 # source 701 linecache.checkcache() 702 source = None 703 try: 704 try: 705 source = getsource(obj, binary_file) 706 except TypeError: 707 if hasattr(obj, "__class__"): 708 source = getsource(obj.__class__, binary_file) 709 if source is not None: 710 source = source.rstrip() 711 if HAS_PYGMENTS: 712 lexer = pyghooks.XonshLexer() 713 source = list(pygments.lex(source, lexer=lexer)) 714 out["source"] = source 715 except Exception: # pylint:disable=broad-except 716 pass 717 718 if ds and source is None: 719 out["docstring"] = ds 720 721 # Constructor docstring for classes 722 if inspect.isclass(obj): 723 out["isclass"] = True 724 # reconstruct the function definition and print it: 725 try: 726 obj_init = obj.__init__ 727 except AttributeError: 728 init_def = init_ds = None 729 else: 730 init_def = self._getdef(obj_init, oname) 731 init_ds = getdoc(obj_init) 732 # Skip Python's auto-generated docstrings 733 if init_ds == _object_init_docstring: 734 init_ds = None 735 736 if init_def or init_ds: 737 if init_def: 738 out["init_definition"] = init_def 739 if init_ds: 740 out["init_docstring"] = init_ds 741 742 # and class docstring for instances: 743 else: 744 # reconstruct the function definition and print it: 745 defln = self._getdef(obj, oname) 746 if defln: 747 out["definition"] = defln 748 749 # First, check whether the instance docstring is identical to the 750 # class one, and print it separately if they don't coincide. In 751 # most cases they will, but it's nice to print all the info for 752 # objects which use instance-customized docstrings. 753 if ds: 754 try: 755 cls = getattr(obj, "__class__") 756 except: # pylint:disable=bare-except 757 class_ds = None 758 else: 759 class_ds = getdoc(cls) 760 # Skip Python's auto-generated docstrings 761 if class_ds in _builtin_type_docstrings: 762 class_ds = None 763 if class_ds and ds != class_ds: 764 out["class_docstring"] = class_ds 765 766 # Next, try to show constructor docstrings 767 try: 768 init_ds = getdoc(obj.__init__) 769 # Skip Python's auto-generated docstrings 770 if init_ds == _object_init_docstring: 771 init_ds = None 772 except AttributeError: 773 init_ds = None 774 if init_ds: 775 out["init_docstring"] = init_ds 776 777 # Call form docstring for callable instances 778 if safe_hasattr(obj, "__call__") and not is_simple_callable(obj): 779 call_def = self._getdef(obj.__call__, oname) 780 if call_def: 781 call_def = call_def 782 # it may never be the case that call def and definition 783 # differ, but don't include the same signature twice 784 if call_def != out.get("definition"): 785 out["call_def"] = call_def 786 call_ds = getdoc(obj.__call__) 787 # Skip Python's auto-generated docstrings 788 if call_ds == _func_call_docstring: 789 call_ds = None 790 if call_ds: 791 out["call_docstring"] = call_ds 792 793 # Compute the object's argspec as a callable. The key is to decide 794 # whether to pull it from the object itself, from its __init__ or 795 # from its __call__ method. 796 797 if inspect.isclass(obj): 798 # Old-style classes need not have an __init__ 799 callable_obj = getattr(obj, "__init__", None) 800 elif callable(obj): 801 callable_obj = obj 802 else: 803 callable_obj = None 804 805 if callable_obj: 806 try: 807 argspec = getargspec(callable_obj) 808 except (TypeError, AttributeError): 809 # For extensions/builtins we can't retrieve the argspec 810 pass 811 else: 812 # named tuples' _asdict() method returns an OrderedDict, but we 813 # we want a normal 814 out["argspec"] = argspec_dict = dict(argspec._asdict()) 815 # We called this varkw before argspec became a named tuple. 816 # With getfullargspec it's also called varkw. 817 if "varkw" not in argspec_dict: 818 argspec_dict["varkw"] = argspec_dict.pop("keywords") 819 820 return object_info(**out) 821