From 6397c7c9a96de6a253da0210239a6fdeafe42a18 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Tue, 27 Feb 2001 14:43:21 +0000 Subject: inspect: a module for getting information out of live Python objects --- Doc/lib/libinspect.tex | 261 ++++++++++++++++++ Lib/inspect.py | 616 +++++++++++++++++++++++++++++++++++++++++++ Lib/test/output/test_inspect | 1 + Lib/test/test_inspect.py | 207 +++++++++++++++ 4 files changed, 1085 insertions(+) create mode 100644 Doc/lib/libinspect.tex create mode 100644 Lib/inspect.py create mode 100644 Lib/test/output/test_inspect create mode 100644 Lib/test/test_inspect.py diff --git a/Doc/lib/libinspect.tex b/Doc/lib/libinspect.tex new file mode 100644 index 0000000..091f821 --- /dev/null +++ b/Doc/lib/libinspect.tex @@ -0,0 +1,261 @@ +\section{\module{inspect} --- + Inspect live objects} +\declaremodule{standard}{inspect} +\modulesynopsis{Extract information and source code from live objects.} +\index{inspect} + +\versionadded{2.1} + +The \code{inspect} module provides several useful functions +to help get information about live objects such as modules, +classes, methods, functions, tracebacks, frame objects, and +code objects. For example, it can help you examine the +contents of a class, retrieve the source code of a method, +extract and format the argument list for a function, or +get all the information you need to display a detailed traceback. + +There are four main kinds of services provided by this module: +type checking, getting source code, inspecting classes +and functions, and examining the interpreter stack. + +\subsection{Types and members + \label{inspect-types}} + +The \function{getmembers()} function retrieves the members +of an object such as a class or module. +The nine functions whose names begin with ``is'' are mainly +provided as convenient choices for the second argument to +\function{getmembers()}. They also help you determine when +you can expect to find the following special attributes: + +\begin{tableiii}{c|l|l}{}{Type}{Attribute}{Description} + \lineiii{module}{__doc__}{documentation string} + \lineiii{}{__file__}{filename (missing for built-in modules)} + \lineiii{}{}{} + + \lineiii{class}{__doc__}{documentation string} + \lineiii{}{__module__}{name of module in which this class was defined} + \lineiii{}{}{} + + \lineiii{method}{__doc__}{documentation string} + \lineiii{}{__name__}{name with which this method was defined} + \lineiii{}{im_class}{class object in which this method belongs} + \lineiii{}{im_func}{function object containing implementation of method} + \lineiii{}{im_self}{instance to which this method is bound, or \code{None}} + \lineiii{}{}{} + + \lineiii{function}{__doc__}{documentation string} + \lineiii{}{__name__}{name with which this function was defined} + \lineiii{}{func_code}{code object containing compiled function bytecode} + \lineiii{}{func_defaults}{tuple of any default values for arguments} + \lineiii{}{func_doc}{(same as __doc__)} + \lineiii{}{func_globals}{global namespace in which this function was defined} + \lineiii{}{func_name}{(same as __name__)} + \lineiii{}{}{} + + \lineiii{traceback}{tb_frame}{frame object at this level} + \lineiii{}{tb_lasti}{index of last attempted instruction in bytecode} + \lineiii{}{tb_lineno}{current line number in Python source code} + \lineiii{}{tb_next}{next inner traceback object (called by this level)} + \lineiii{}{}{} + + \lineiii{frame}{f_back}{next outer frame object (this frame's caller)} + \lineiii{}{f_builtins}{built-in namespace seen by this frame} + \lineiii{}{f_code}{code object being executed in this frame} + \lineiii{}{f_exc_traceback}{traceback if raised in this frame, or \code{None}} + \lineiii{}{f_exc_type}{exception type if raised in this frame, or \code{None}} + \lineiii{}{f_exc_value}{exception value if raised in this frame, or \code{None}} + \lineiii{}{f_globals}{global namespace seen by this frame} + \lineiii{}{f_lasti}{index of last attempted instruction in bytecode} + \lineiii{}{f_lineno}{current line number in Python source code} + \lineiii{}{f_locals}{local namespace seen by this frame} + \lineiii{}{f_restricted}{0 or 1 if frame is in restricted execution mode} + \lineiii{}{f_trace}{tracing function for this frame, or \code{None}} + \lineiii{}{}{} + + \lineiii{code}{co_argcount}{number of arguments (not including * or ** args)} + \lineiii{}{co_code}{string of raw compiled bytecode} + \lineiii{}{co_consts}{tuple of constants used in the bytecode} + \lineiii{}{co_filename}{name of file in which this code object was created} + \lineiii{}{co_firstlineno}{number of first line in Python source code} + \lineiii{}{co_flags}{bitmap: 1=optimized \code{|} 2=newlocals \code{|} 4=*arg \code{|} 8=**arg} + \lineiii{}{co_lnotab}{encoded mapping of line numbers to bytecode indices} + \lineiii{}{co_name}{name with which this code object was defined} + \lineiii{}{co_names}{tuple of names of local variables} + \lineiii{}{co_nlocals}{number of local variables} + \lineiii{}{co_stacksize}{virtual machine stack space required} + \lineiii{}{co_varnames}{tuple of names of arguments and local variables} + \lineiii{}{}{} + + \lineiii{builtin}{__doc__}{documentation string} + \lineiii{}{__name__}{original name of this function or method} + \lineiii{}{__self__}{instance to which a method is bound, or \code{None}} +\end{tableiii} + +\begin{funcdesc}{getmembers}{object\optional{, predicate}} + Return all the members of an object in a list of (name, value) pairs + sorted by name. If the optional \var{predicate} argument is supplied, + only members for which the predicate returns a true value are included. +\end{funcdesc} + +\begin{funcdesc}{ismodule}{object} + Return true if the object is a module. +\end{funcdesc} + +\begin{funcdesc}{isclass}{object} + Return true if the object is a class. +\end{funcdesc} + +\begin{funcdesc}{ismethod}{object} + Return true if the object is a method. +\end{funcdesc} + +\begin{funcdesc}{isfunction}{object} + Return true if the object is a Python function or unnamed (lambda) function. +\end{funcdesc} + +\begin{funcdesc}{istraceback}{object} + Return true if the object is a traceback. +\end{funcdesc} + +\begin{funcdesc}{isframe}{object} + Return true if the object is a frame. +\end{funcdesc} + +\begin{funcdesc}{iscode}{object} + Return true if the object is a code. +\end{funcdesc} + +\begin{funcdesc}{isbuiltin}{object} + Return true if the object is a built-in function. +\end{funcdesc} + +\begin{funcdesc}{isroutine}{object} + Return true if the object is a user-defined or built-in function or method. +\end{funcdesc} + +\subsection{Retrieving source code + \label{inspect-source}} + +\begin{funcdesc}{getdoc}{object} + Get the documentation string for an object. + All tabs are expanded to spaces. To clean up docstrings that are + indented to line up with blocks of code, any whitespace than can be + uniformly removed from the second line onwards is removed. +\end{funcdesc} + +\begin{funcdesc}{getcomments}{object} + Return in a single string any lines of comments immediately preceding + the object's source code (for a class, function, or method), or at the + top of the Python source file (if the object is a module). +\end{funcdesc} + +\begin{funcdesc}{getfile}{object} + Return the name of the (text or binary) file in which an object was defined. + This will fail with a TypeError if the object is a built-in module, + class, or function. +\end{funcdesc} + +\begin{funcdesc}{getmodule}{object} + Try to guess which module an object was defined in. +\end{funcdesc} + +\begin{funcdesc}{getsourcefile}{object} + Return the name of the Python source file in which an object was defined. + This will fail with a TypeError if the object is a built-in module, + class, or function. +\end{funcdesc} + +\begin{funcdesc}{getsourcelines}{object} + Return a list of source lines and starting line number for an object. + The argument may be a module, class, method, function, traceback, frame, + or code object. The source code is returned as a list of the lines + corresponding to the object and the line number indicates where in the + original source file the first line of code was found. An IOError is + raised if the source code cannot be retrieved. +\end{funcdesc} + +\begin{funcdesc}{getsource}{object} + Return the text of the source code for an object. + The argument may be a module, class, method, function, traceback, frame, + or code object. The source code is returned as a single string. An + IOError is raised if the source code cannot be retrieved. +\end{funcdesc} + +\subsection{Classes and functions + \label{inspect-classes-functions}} + +\begin{funcdesc}{getclasstree}{classes\optional{, unique}} + Arrange the given list of classes into a hierarchy of nested lists. + Where a nested list appears, it contains classes derived from the class + whose entry immediately precedes the list. Each entry is a 2-tuple + containing a class and a tuple of its base classes. If the \var{unique} + argument is true, exactly one entry appears in the returned structure + for each class in the given list. Otherwise, classes using multiple + inheritance and their descendants will appear multiple times. +\end{funcdesc} + +\begin{funcdesc}{getargspec}{func} + Get the names and default values of a function's arguments. + A tuple of four things is returned: (args, varargs, varkw, defaults). + \var{args} is a list of the argument names (it may contain nested lists). + \var{varargs} and \var{varkw} are the names of the * and ** arguments or + \code{None}. + \var{defaults} is a tuple of default argument values; if this tuple + has \var{n} elements, they correspond to the last \var{n} elements + listed in \var{args}. +\end{funcdesc} + +\begin{funcdesc}{getargvalues}{frame} + Get information about arguments passed into a particular frame. + A tuple of four things is returned: (args, varargs, varkw, locals). + \var{args} is a list of the argument names (it may contain nested lists). + \var{varargs} and \var{varkw} are the names of the * and ** arguments or + \code{None}. + \var{locals} is the locals dictionary of the given frame. +\end{funcdesc} + +\begin{funcdesc}{formatargspec}{args\optional{, varargs, varkw, defaults, +argformat, varargsformat, varkwformat, defaultformat}} + Format a pretty argument spec from the four values returned by getargspec. + The other four arguments are the corresponding optional formatting functions + that are called to turn names and values into strings. +\end{funcdesc} + +\begin{funcdesc}{formatargvalues}{args\optional{, varargs, varkw, locals, +argformat, varargsformat, varkwformat, valueformat}} + Format a pretty argument spec from the four values returned by getargvalues. + The other four arguments are the corresponding optional formatting functions + that are called to turn names and values into strings. +\end{funcdesc} + +\subsection{The interpreter stack + \label{inspect-stack}} + +When the following functions return ``frame records'', each record +is a tuple of six items: the frame object, the filename, +the line number of the current line, the function name, a list of +lines of context from the source code, and the index of the current +line within that list. +The optional \var{context} argument specifies the number of lines of +context to return, which are centered around the current line. + +\begin{funcdesc}{getouterframes}{frame\optional{, context}} + Get a list of frame records for a frame and all higher (calling) frames. +\end{funcdesc} + +\begin{funcdesc}{getinnerframes}{traceback\optional{, context}} + Get a list of frame records for a traceback's frame and all lower frames. +\end{funcdesc} + +\begin{funcdesc}{currentframe}{} + Return the frame object for the caller's stack frame. +\end{funcdesc} + +\begin{funcdesc}{stack}{\optional{context}} + Return a list of frame records for the stack above the caller's frame. +\end{funcdesc} + +\begin{funcdesc}{trace}{\optional{context}} + Return a list of frame records for the stack below the current exception. +\end{funcdesc} diff --git a/Lib/inspect.py b/Lib/inspect.py new file mode 100644 index 0000000..408e454 --- /dev/null +++ b/Lib/inspect.py @@ -0,0 +1,616 @@ +"""Get useful information from live Python objects. + +This module encapsulates the interface provided by the internal special +attributes (func_*, co_*, im_*, tb_*, etc.) in a friendlier fashion. +It also provides some help for examining source code and class layout. + +Here are some of the useful functions provided by this module: + + ismodule(), isclass(), ismethod(), isfunction(), istraceback(), + isframe(), iscode(), isbuiltin(), isroutine() - check object types + getmembers() - get members of an object that satisfy a given condition + + getfile(), getsourcefile(), getsource() - find an object's source code + getdoc(), getcomments() - get documentation on an object + getmodule() - determine the module that an object came from + getclasstree() - arrange classes so as to represent their hierarchy + + getargspec(), getargvalues() - get info about function arguments + formatargspec(), formatargvalues() - format an argument spec + getouterframes(), getinnerframes() - get info about frames + currentframe() - get the current stack frame + stack(), trace() - get info about frames on the stack or in a traceback +""" + +# This module is in the public domain. No warranties. + +__version__ = 'Ka-Ping Yee , 1 Jan 2001' + +import sys, types, string, dis, imp, tokenize + +# ----------------------------------------------------------- type-checking +def ismodule(object): + """Return true if the object is a module. + + Module objects provide these attributes: + __doc__ documentation string + __file__ filename (missing for built-in modules)""" + return type(object) is types.ModuleType + +def isclass(object): + """Return true if the object is a class. + + Class objects provide these attributes: + __doc__ documentation string + __module__ name of module in which this class was defined""" + return type(object) is types.ClassType + +def ismethod(object): + """Return true if the object is an instance method. + + Instance method objects provide these attributes: + __doc__ documentation string + __name__ name with which this method was defined + im_class class object in which this method belongs + im_func function object containing implementation of method + im_self instance to which this method is bound, or None""" + return type(object) is types.MethodType + +def isfunction(object): + """Return true if the object is a user-defined function. + + Function objects provide these attributes: + __doc__ documentation string + __name__ name with which this function was defined + func_code code object containing compiled function bytecode + func_defaults tuple of any default values for arguments + func_doc (same as __doc__) + func_globals global namespace in which this function was defined + func_name (same as __name__)""" + return type(object) in [types.FunctionType, types.LambdaType] + +def istraceback(object): + """Return true if the object is a traceback. + + Traceback objects provide these attributes: + tb_frame frame object at this level + tb_lasti index of last attempted instruction in bytecode + tb_lineno current line number in Python source code + tb_next next inner traceback object (called by this level)""" + return type(object) is types.TracebackType + +def isframe(object): + """Return true if the object is a frame object. + + Frame objects provide these attributes: + f_back next outer frame object (this frame's caller) + f_builtins built-in namespace seen by this frame + f_code code object being executed in this frame + f_exc_traceback traceback if raised in this frame, or None + f_exc_type exception type if raised in this frame, or None + f_exc_value exception value if raised in this frame, or None + f_globals global namespace seen by this frame + f_lasti index of last attempted instruction in bytecode + f_lineno current line number in Python source code + f_locals local namespace seen by this frame + f_restricted 0 or 1 if frame is in restricted execution mode + f_trace tracing function for this frame, or None""" + return type(object) is types.FrameType + +def iscode(object): + """Return true if the object is a code object. + + Code objects provide these attributes: + co_argcount number of arguments (not including * or ** args) + co_code string of raw compiled bytecode + co_consts tuple of constants used in the bytecode + co_filename name of file in which this code object was created + co_firstlineno number of first line in Python source code + co_flags bitmap: 1=optimized | 2=newlocals | 4=*arg | 8=**arg + co_lnotab encoded mapping of line numbers to bytecode indices + co_name name with which this code object was defined + co_names tuple of names of local variables + co_nlocals number of local variables + co_stacksize virtual machine stack space required + co_varnames tuple of names of arguments and local variables""" + return type(object) is types.CodeType + +def isbuiltin(object): + """Return true if the object is a built-in function or method. + + Built-in functions and methods provide these attributes: + __doc__ documentation string + __name__ original name of this function or method + __self__ instance to which a method is bound, or None""" + return type(object) in [types.BuiltinFunctionType, + types.BuiltinMethodType] + +def isroutine(object): + """Return true if the object is any kind of function or method.""" + return type(object) in [types.FunctionType, types.LambdaType, + types.MethodType, types.BuiltinFunctionType, + types.BuiltinMethodType] + +def getmembers(object, predicate=None): + """Return all members of an object as (name, value) pairs sorted by name. + Optionally, only return members that satisfy a given predicate.""" + results = [] + for key in dir(object): + value = getattr(object, key) + if not predicate or predicate(value): + results.append((key, value)) + results.sort() + return results + +# -------------------------------------------------- source code extraction +def indentsize(line): + """Return the indent size, in spaces, at the start of a line of text.""" + expline = string.expandtabs(line) + return len(expline) - len(string.lstrip(expline)) + +def getdoc(object): + """Get the documentation string for an object. + + All tabs are expanded to spaces. To clean up docstrings that are + indented to line up with blocks of code, any whitespace than can be + uniformly removed from the second line onwards is removed.""" + if hasattr(object, '__doc__') and object.__doc__: + lines = string.split(string.expandtabs(object.__doc__), '\n') + margin = None + for line in lines[1:]: + content = len(string.lstrip(line)) + if not content: continue + indent = len(line) - content + if margin is None: margin = indent + else: margin = min(margin, indent) + if margin is not None: + for i in range(1, len(lines)): lines[i] = lines[i][margin:] + return string.join(lines, '\n') + +def getfile(object): + """Try to guess which (text or binary) file an object was defined in.""" + if ismodule(object): + if hasattr(object, '__file__'): + return object.__file__ + raise TypeError, 'arg is a built-in module' + if isclass(object): + object = sys.modules[object.__module__] + if hasattr(object, '__file__'): + return object.__file__ + raise TypeError, 'arg is a built-in class' + if ismethod(object): + object = object.im_func + if isfunction(object): + object = object.func_code + if istraceback(object): + object = object.tb_frame + if isframe(object): + object = object.f_code + if iscode(object): + return object.co_filename + raise TypeError, 'arg is not a module, class, method, ' \ + 'function, traceback, frame, or code object' + +modulesbyfile = {} + +def getmodule(object): + """Try to guess which module an object was defined in.""" + if isclass(object): + return sys.modules[object.__module__] + try: + file = getsourcefile(object) + except TypeError: + return None + if modulesbyfile.has_key(file): + return sys.modules[modulesbyfile[file]] + for module in sys.modules.values(): + if hasattr(module, '__file__'): + modulesbyfile[getsourcefile(module)] = module.__name__ + if modulesbyfile.has_key(file): + return sys.modules[modulesbyfile[file]] + main = sys.modules['__main__'] + try: + mainobject = getattr(main, object.__name__) + if mainobject is object: return main + except AttributeError: pass + builtin = sys.modules['__builtin__'] + try: + builtinobject = getattr(builtin, object.__name__) + if builtinobject is object: return builtin + except AttributeError: pass + +def getsourcefile(object): + """Try to guess which Python source file an object was defined in.""" + filename = getfile(object) + if filename[-4:] == '.pyc': + filename = filename[:-4] + '.py' + return filename + +def findsource(object): + """Return the entire source file and starting line number for an object. + + The argument may be a module, class, method, function, traceback, frame, + or code object. The source code is returned as a list of all the lines + in the file and the line number indexes a line in that list. An IOError + is raised if the source code cannot be retrieved.""" + try: + file = open(getsourcefile(object)) + lines = file.readlines() + file.close() + except (TypeError, IOError): + raise IOError, 'could not get source code' + + if ismodule(object): + return lines, 0 + + if isclass(object): + name = object.__name__ + matches = (['class', name], ['class', name + ':']) + for i in range(len(lines)): + if string.split(lines[i])[:2] in matches: + return lines, i + else: raise IOError, 'could not find class definition' + + if ismethod(object): + object = object.im_func + if isfunction(object): + object = object.func_code + if istraceback(object): + object = object.tb_frame + if isframe(object): + object = object.f_code + if iscode(object): + try: + lnum = object.co_firstlineno - 1 + except AttributeError: + raise IOError, 'could not find function definition' + else: + while lnum > 0: + if string.split(lines[lnum])[:1] == ['def']: break + lnum = lnum - 1 + return lines, lnum + +def getcomments(object): + """Get lines of comments immediately preceding an object's source code.""" + try: lines, lnum = findsource(object) + except: return None + + if ismodule(object): + # Look for a comment block at the top of the file. + start = 0 + if lines[0][:2] == '#!': start = 1 + while start < len(lines) and string.strip(lines[start]) in ['', '#']: + start = start + 1 + if lines[start][:1] == '#': + comments = [] + end = start + while end < len(lines) and lines[end][:1] == '#': + comments.append(string.expandtabs(lines[end])) + end = end + 1 + return string.join(comments, '') + + # Look for a preceding block of comments at the same indentation. + elif lnum > 0: + indent = indentsize(lines[lnum]) + end = lnum - 1 + if end >= 0 and string.lstrip(lines[end])[:1] == '#' and \ + indentsize(lines[end]) == indent: + comments = [string.lstrip(string.expandtabs(lines[end]))] + if end > 0: + end = end - 1 + comment = string.lstrip(string.expandtabs(lines[end])) + while comment[:1] == '#' and indentsize(lines[end]) == indent: + comments[:0] = [comment] + end = end - 1 + if end < 0: break + comment = string.lstrip(string.expandtabs(lines[end])) + while comments and string.strip(comments[0]) == '#': + comments[:1] = [] + while comments and string.strip(comments[-1]) == '#': + comments[-1:] = [] + return string.join(comments, '') + +class ListReader: + """Provide a readline() method to return lines from a list of strings.""" + def __init__(self, lines): + self.lines = lines + self.index = 0 + + def readline(self): + i = self.index + if i < len(self.lines): + self.index = i + 1 + return self.lines[i] + else: return '' + +class EndOfBlock(Exception): pass + +class BlockFinder: + """Provide a tokeneater() method to detect the end of a code block.""" + def __init__(self): + self.indent = 0 + self.started = 0 + self.last = 0 + + def tokeneater(self, type, token, (srow, scol), (erow, ecol), line): + if not self.started: + if type == tokenize.NAME: self.started = 1 + elif type == tokenize.NEWLINE: + self.last = srow + elif type == tokenize.INDENT: + self.indent = self.indent + 1 + elif type == tokenize.DEDENT: + self.indent = self.indent - 1 + if self.indent == 0: raise EndOfBlock, self.last + +def getblock(lines): + """Extract the block of code at the top of the given list of lines.""" + try: + tokenize.tokenize(ListReader(lines).readline, BlockFinder().tokeneater) + except EndOfBlock, eob: + return lines[:eob.args[0]] + +def getsourcelines(object): + """Return a list of source lines and starting line number for an object. + + The argument may be a module, class, method, function, traceback, frame, + or code object. The source code is returned as a list of the lines + corresponding to the object and the line number indicates where in the + original source file the first line of code was found. An IOError is + raised if the source code cannot be retrieved.""" + lines, lnum = findsource(object) + + if ismodule(object): return lines, 0 + else: return getblock(lines[lnum:]), lnum + 1 + +def getsource(object): + """Return the text of the source code for an object. + + The argument may be a module, class, method, function, traceback, frame, + or code object. The source code is returned as a single string. An + IOError is raised if the source code cannot be retrieved.""" + lines, lnum = getsourcelines(object) + return string.join(lines, '') + +# --------------------------------------------------- class tree extraction +def walktree(classes, children, parent): + """Recursive helper function for getclasstree().""" + results = [] + classes.sort(lambda a, b: cmp(a.__name__, b.__name__)) + for c in classes: + results.append((c, c.__bases__)) + if children.has_key(c): + results.append(walktree(children[c], children, c)) + return results + +def getclasstree(classes, unique=0): + """Arrange the given list of classes into a hierarchy of nested lists. + + Where a nested list appears, it contains classes derived from the class + whose entry immediately precedes the list. Each entry is a 2-tuple + containing a class and a tuple of its base classes. If the 'unique' + argument is true, exactly one entry appears in the returned structure + for each class in the given list. Otherwise, classes using multiple + inheritance and their descendants will appear multiple times.""" + children = {} + roots = [] + for c in classes: + if c.__bases__: + for parent in c.__bases__: + if not children.has_key(parent): + children[parent] = [] + children[parent].append(c) + if unique and parent in classes: break + elif c not in roots: + roots.append(c) + for parent in children.keys(): + if parent not in classes: + roots.append(parent) + return walktree(roots, children, None) + +# ------------------------------------------------ argument list extraction +# These constants are from Python's compile.h. +CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS = 1, 2, 4, 8 + +def getargs(co): + """Get information about the arguments accepted by a code object. + + Three things are returned: (args, varargs, varkw), where 'args' is + a list of argument names (possibly containing nested lists), and + 'varargs' and 'varkw' are the names of the * and ** arguments or None.""" + if not iscode(co): raise TypeError, 'arg is not a code object' + + code = co.co_code + nargs = co.co_argcount + names = co.co_varnames + args = list(names[:nargs]) + step = 0 + + # The following acrobatics are for anonymous (tuple) arguments. + for i in range(nargs): + if args[i][:1] in ['', '.']: + stack, remain, count = [], [], [] + while step < len(code): + op = ord(code[step]) + step = step + 1 + if op >= dis.HAVE_ARGUMENT: + opname = dis.opname[op] + value = ord(code[step]) + ord(code[step+1])*256 + step = step + 2 + if opname in ['UNPACK_TUPLE', 'UNPACK_SEQUENCE']: + remain.append(value) + count.append(value) + elif opname == 'STORE_FAST': + stack.append(names[value]) + remain[-1] = remain[-1] - 1 + while remain[-1] == 0: + remain.pop() + size = count.pop() + stack[-size:] = [stack[-size:]] + if not remain: break + remain[-1] = remain[-1] - 1 + if not remain: break + args[i] = stack[0] + + varargs = None + if co.co_flags & CO_VARARGS: + varargs = co.co_varnames[nargs] + nargs = nargs + 1 + varkw = None + if co.co_flags & CO_VARKEYWORDS: + varkw = co.co_varnames[nargs] + return args, varargs, varkw + +def getargspec(func): + """Get the names and default values of a function's arguments. + + A tuple of four things is returned: (args, varargs, varkw, defaults). + 'args' is a list of the argument names (it may contain nested lists). + 'varargs' and 'varkw' are the names of the * and ** arguments or None. + 'defaults' is an n-tuple of the default values of the last n arguments.""" + if not isfunction(func): raise TypeError, 'arg is not a Python function' + args, varargs, varkw = getargs(func.func_code) + return args, varargs, varkw, func.func_defaults + +def getargvalues(frame): + """Get information about arguments passed into a particular frame. + + A tuple of four things is returned: (args, varargs, varkw, locals). + 'args' is a list of the argument names (it may contain nested lists). + 'varargs' and 'varkw' are the names of the * and ** arguments or None. + 'locals' is the locals dictionary of the given frame.""" + args, varargs, varkw = getargs(frame.f_code) + return args, varargs, varkw, frame.f_locals + +def joinseq(seq): + if len(seq) == 1: + return '(' + seq[0] + ',)' + else: + return '(' + string.join(seq, ', ') + ')' + +def strseq(object, convert, join=joinseq): + """Recursively walk a sequence, stringifying each element.""" + if type(object) in [types.ListType, types.TupleType]: + return join(map(lambda o, c=convert, j=join: strseq(o, c, j), object)) + else: + return convert(object) + +def formatargspec(args, varargs=None, varkw=None, defaults=None, + formatarg=str, + formatvarargs=lambda name: '*' + name, + formatvarkw=lambda name: '**' + name, + formatvalue=lambda value: '=' + repr(value), + join=joinseq): + """Format an argument spec from the 4 values returned by getargspec. + + The first four arguments are (args, varargs, varkw, defaults). The + other four arguments are the corresponding optional formatting functions + that are called to turn names and values into strings. The ninth + argument is an optional function to format the sequence of arguments.""" + specs = [] + if defaults: + firstdefault = len(args) - len(defaults) + for i in range(len(args)): + spec = strseq(args[i], formatarg, join) + if defaults and i >= firstdefault: + spec = spec + formatvalue(defaults[i - firstdefault]) + specs.append(spec) + if varargs: + specs.append(formatvarargs(varargs)) + if varkw: + specs.append(formatvarkw(varkw)) + return '(' + string.join(specs, ', ') + ')' + +def formatargvalues(args, varargs, varkw, locals, + formatarg=str, + formatvarargs=lambda name: '*' + name, + formatvarkw=lambda name: '**' + name, + formatvalue=lambda value: '=' + repr(value), + join=joinseq): + """Format an argument spec from the 4 values returned by getargvalues. + + The first four arguments are (args, varargs, varkw, locals). The + next four arguments are the corresponding optional formatting functions + that are called to turn names and values into strings. The ninth + argument is an optional function to format the sequence of arguments.""" + def convert(name, locals=locals, + formatarg=formatarg, formatvalue=formatvalue): + return formatarg(name) + formatvalue(locals[name]) + specs = [] + for i in range(len(args)): + specs.append(strseq(args[i], convert, join)) + if varargs: + specs.append(formatvarargs(varargs) + formatvalue(locals[varargs])) + if varkw: + specs.append(formatvarkw(varkw) + formatvalue(locals[varkw])) + return '(' + string.join(specs, ', ') + ')' + +# -------------------------------------------------- stack frame extraction +def getframeinfo(frame, context=1): + """Get information about a frame or traceback object. + + A tuple of five things is returned: the filename, the line number of + the current line, the function name, a list of lines of context from + the source code, and the index of the current line within that list. + The optional second argument specifies the number of lines of context + to return, which are centered around the current line.""" + if istraceback(frame): + frame = frame.tb_frame + if not isframe(frame): + raise TypeError, 'arg is not a frame or traceback object' + + filename = getsourcefile(frame) + if context > 0: + start = frame.f_lineno - 1 - context/2 + try: + lines, lnum = findsource(frame) + start = max(start, 1) + start = min(start, len(lines) - context) + lines = lines[start:start+context] + index = frame.f_lineno - 1 - start + except: + lines = index = None + else: + lines = index = None + + return (filename, frame.f_lineno, frame.f_code.co_name, lines, index) + +def getouterframes(frame, context=1): + """Get a list of records for a frame and all higher (calling) frames. + + Each record contains a frame object, filename, line number, function + name, a list of lines of context, and index within the context.""" + framelist = [] + while frame: + framelist.append((frame,) + getframeinfo(frame, context)) + frame = frame.f_back + return framelist + +def getinnerframes(tb, context=1): + """Get a list of records for a traceback's frame and all lower frames. + + Each record contains a frame object, filename, line number, function + name, a list of lines of context, and index within the context.""" + tb = tb.tb_next + framelist = [] + while tb: + framelist.append((tb.tb_frame,) + getframeinfo(tb, context)) + tb = tb.tb_next + return framelist + +def currentframe(): + """Return the frame object for the caller's stack frame.""" + try: + raise 'catch me' + except: + return sys.exc_traceback.tb_frame.f_back + +if hasattr(sys, '_getframe'): currentframe = sys._getframe + +def stack(context=1): + """Return a list of records for the stack above the caller's frame.""" + return getouterframes(currentframe().f_back, context) + +def trace(context=1): + """Return a list of records for the stack below the current exception.""" + return getinnerframes(sys.exc_traceback, context) diff --git a/Lib/test/output/test_inspect b/Lib/test/output/test_inspect new file mode 100644 index 0000000..6d7f8dd --- /dev/null +++ b/Lib/test/output/test_inspect @@ -0,0 +1 @@ +test_inspect diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py new file mode 100644 index 0000000..00b3d7d --- /dev/null +++ b/Lib/test/test_inspect.py @@ -0,0 +1,207 @@ +source = '''# line 1 +'A module docstring.' + +import sys, inspect +# line 5 + +# line 7 +def spam(a, b, c, d=3, (e, (f,))=(4, (5,)), *g, **h): + eggs(b + d, c + f) + +# line 11 +def eggs(x, y): + "A docstring." + global fr, st + fr = inspect.currentframe() + st = inspect.stack() + p = x + q = y / 0 + +# line 20 +class StupidGit: + """A longer, + + indented + + docstring.""" +# line 27 + + def abuse(self, a, b, c): + """Another + +\tdocstring + + containing + +\ttabs +\t + """ + self.argue(a, b, c) +# line 40 + def argue(self, a, b, c): + try: + spam(a, b, c) + except: + self.ex = sys.exc_info() + self.tr = inspect.trace() + +# line 48 +class MalodorousPervert(StupidGit): + pass + +class ParrotDroppings: + pass + +class FesteringGob(MalodorousPervert, ParrotDroppings): + pass +''' + +# Functions tested in this suite: +# ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode, +# isbuiltin, isroutine, getmembers, getdoc, getfile, getmodule, +# getsourcefile, getcomments, getsource, getclasstree, getargspec, +# getargvalues, formatargspec, formatargvalues, currentframe, stack, trace + +from test_support import TestFailed, TESTFN +import sys, imp, os, string + +def test(assertion, message, *args): + if not assertion: + raise TestFailed, message % args + +import inspect + +file = open(TESTFN, 'w') +file.write(source) +file.close() + +mod = imp.load_source('testmod', TESTFN) + +def istest(func, exp): + obj = eval(exp) + test(func(obj), '%s(%s)' % (func.__name__, exp)) + for other in [inspect.isbuiltin, inspect.isclass, inspect.iscode, + inspect.isframe, inspect.isfunction, inspect.ismethod, + inspect.ismodule, inspect.istraceback]: + if other is not func: + test(not other(obj), 'not %s(%s)' % (other.__name__, exp)) + +git = mod.StupidGit() +try: + 1/0 +except: + tb = sys.exc_traceback + +istest(inspect.isbuiltin, 'sys.exit') +istest(inspect.isbuiltin, '[].append') +istest(inspect.isclass, 'mod.StupidGit') +istest(inspect.iscode, 'mod.spam.func_code') +istest(inspect.isframe, 'tb.tb_frame') +istest(inspect.isfunction, 'mod.spam') +istest(inspect.ismethod, 'mod.StupidGit.abuse') +istest(inspect.ismethod, 'git.argue') +istest(inspect.ismodule, 'mod') +istest(inspect.istraceback, 'tb') +test(inspect.isroutine(mod.spam), 'isroutine(mod.spam)') +test(inspect.isroutine([].count), 'isroutine([].count)') + +classes = inspect.getmembers(mod, inspect.isclass) +test(classes == + [('FesteringGob', mod.FesteringGob), + ('MalodorousPervert', mod.MalodorousPervert), + ('ParrotDroppings', mod.ParrotDroppings), + ('StupidGit', mod.StupidGit)], 'class list') +tree = inspect.getclasstree(map(lambda x: x[1], classes), 1) +test(tree == + [(mod.ParrotDroppings, ()), + (mod.StupidGit, ()), + [(mod.MalodorousPervert, (mod.StupidGit,)), + [(mod.FesteringGob, (mod.MalodorousPervert, mod.ParrotDroppings)) + ] + ] + ], 'class tree') + +functions = inspect.getmembers(mod, inspect.isfunction) +test(functions == [('eggs', mod.eggs), ('spam', mod.spam)], 'function list') + +test(inspect.getdoc(mod) == 'A module docstring.', 'getdoc(mod)') +test(inspect.getcomments(mod) == '# line 1\n', 'getcomments(mod)') +test(inspect.getmodule(mod.StupidGit) == mod, 'getmodule(mod.StupidGit)') +test(inspect.getfile(mod.StupidGit) == TESTFN, 'getfile(mod.StupidGit)') +test(inspect.getsourcefile(mod.spam) == TESTFN, 'getsourcefile(mod.spam)') +test(inspect.getsourcefile(git.abuse) == TESTFN, 'getsourcefile(git.abuse)') + +def sourcerange(top, bottom): + lines = string.split(source, '\n') + return string.join(lines[top-1:bottom], '\n') + '\n' + +test(inspect.getsource(git.abuse) == sourcerange(29, 39), + 'getsource(git.abuse)') +test(inspect.getsource(mod.StupidGit) == sourcerange(21, 46), + 'getsource(mod.StupidGit)') +test(inspect.getdoc(mod.StupidGit) == + 'A longer,\n\nindented\n\ndocstring.', 'getdoc(mod.StupidGit)') +test(inspect.getdoc(git.abuse) == + 'Another\n\ndocstring\n\ncontaining\n\ntabs\n\n', 'getdoc(git.abuse)') +test(inspect.getcomments(mod.StupidGit) == '# line 20\n', + 'getcomments(mod.StupidGit)') + +args, varargs, varkw, defaults = inspect.getargspec(mod.eggs) +test(args == ['x', 'y'], 'mod.eggs args') +test(varargs == None, 'mod.eggs varargs') +test(varkw == None, 'mod.eggs varkw') +test(defaults == None, 'mod.eggs defaults') +test(inspect.formatargspec(args, varargs, varkw, defaults) == + '(x, y)', 'mod.eggs formatted argspec') +args, varargs, varkw, defaults = inspect.getargspec(mod.spam) +test(args == ['a', 'b', 'c', 'd', ['e', ['f']]], 'mod.spam args') +test(varargs == 'g', 'mod.spam varargs') +test(varkw == 'h', 'mod.spam varkw') +test(defaults == (3, (4, (5,))), 'mod.spam defaults') +test(inspect.formatargspec(args, varargs, varkw, defaults) == + '(a, b, c, d=3, (e, (f,))=(4, (5,)), *g, **h)', + 'mod.spam formatted argspec') + +git.abuse(7, 8, 9) + +istest(inspect.istraceback, 'git.ex[2]') +istest(inspect.isframe, 'mod.fr') + +test(len(git.tr) == 2, 'trace() length') +test(git.tr[0][1:] == ('@test', 9, 'spam', [' eggs(b + d, c + f)\n'], 0), + 'trace() row 1') +test(git.tr[1][1:] == ('@test', 18, 'eggs', [' q = y / 0\n'], 0), + 'trace() row 2') + +test(len(mod.st) >= 5, 'stack() length') +test(mod.st[0][1:] == + ('@test', 16, 'eggs', [' st = inspect.stack()\n'], 0), + 'stack() row 1') +test(mod.st[1][1:] == + ('@test', 9, 'spam', [' eggs(b + d, c + f)\n'], 0), + 'stack() row 2') +test(mod.st[2][1:] == + ('@test', 43, 'argue', [' spam(a, b, c)\n'], 0), + 'stack() row 3') +test(mod.st[3][1:] == + ('@test', 39, 'abuse', [' self.argue(a, b, c)\n'], 0), + 'stack() row 4') +# row 4 is in test_inspect.py + +args, varargs, varkw, locals = inspect.getargvalues(mod.fr) +test(args == ['x', 'y'], 'mod.fr args') +test(varargs == None, 'mod.fr varargs') +test(varkw == None, 'mod.fr varkw') +test(locals == {'x': 11, 'p': 11, 'y': 14}, 'mod.fr locals') +test(inspect.formatargvalues(args, varargs, varkw, locals) == + '(x=11, y=14)', 'mod.fr formatted argvalues') + +args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back) +test(args == ['a', 'b', 'c', 'd', ['e', ['f']]], 'mod.fr.f_back args') +test(varargs == 'g', 'mod.fr.f_back varargs') +test(varkw == 'h', 'mod.fr.f_back varkw') +test(inspect.formatargvalues(args, varargs, varkw, locals) == + '(a=7, b=8, c=9, d=3, (e=4, (f=5,)), *g=(), **h={})', + 'mod.fr.f_back formatted argvalues') + +os.unlink(TESTFN) -- cgit v0.12