diff options
Diffstat (limited to 'Lib/inspect.py')
| -rw-r--r-- | Lib/inspect.py | 125 |
1 files changed, 109 insertions, 16 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py index 2ec991f..33065f5 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -17,7 +17,7 @@ Here are some of the useful functions provided by this module: 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 + getargspec(), getargvalues(), getcallargs() - get info about function arguments formatargspec(), formatargvalues() - format an argument spec getouterframes(), getinnerframes() - get info about frames currentframe() - get the current stack frame @@ -62,7 +62,7 @@ def isclass(object): Class objects provide these attributes: __doc__ documentation string __module__ name of module in which this class was defined""" - return isinstance(object, types.ClassType) or hasattr(object, '__bases__') + return isinstance(object, (type, types.ClassType)) def ismethod(object): """Return true if the object is an instance method. @@ -157,7 +157,7 @@ def isgeneratorfunction(object): Generator function objects provides same attributes as functions. - See isfunction.__doc__ for attributes listing.""" + See help(isfunction) for attributes listing.""" return bool((isfunction(object) or ismethod(object)) and object.func_code.co_flags & CO_GENERATOR) @@ -242,14 +242,17 @@ def isroutine(object): def isabstract(object): """Return true if the object is an abstract base class (ABC).""" - return isinstance(object, type) and object.__flags__ & TPFLAGS_IS_ABSTRACT + return bool(isinstance(object, type) and object.__flags__ & TPFLAGS_IS_ABSTRACT) 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) + try: + value = getattr(object, key) + except AttributeError: + continue if not predicate or predicate(value): results.append((key, value)) results.sort() @@ -399,12 +402,12 @@ def getfile(object): if ismodule(object): if hasattr(object, '__file__'): return object.__file__ - raise TypeError('arg is a built-in module') + raise TypeError('{!r} is a built-in module'.format(object)) if isclass(object): object = sys.modules.get(object.__module__) if hasattr(object, '__file__'): return object.__file__ - raise TypeError('arg is a built-in class') + raise TypeError('{!r} is a built-in class'.format(object)) if ismethod(object): object = object.im_func if isfunction(object): @@ -415,8 +418,8 @@ def getfile(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') + raise TypeError('{!r} is not a module, class, method, ' + 'function, traceback, frame, or code object'.format(object)) ModuleInfo = namedtuple('ModuleInfo', 'name suffix mode module_type') @@ -437,7 +440,9 @@ def getmodulename(path): if info: return info[0] def getsourcefile(object): - """Return the Python source file an object was defined in, if it exists.""" + """Return the filename that can be used to locate an object's source. + Return None if no way can be identified to get the source. + """ filename = getfile(object) if string.lower(filename[-4:]) in ('.pyc', '.pyo'): filename = filename[:-4] + '.py' @@ -450,6 +455,9 @@ def getsourcefile(object): # only return a non-existent filename if the module has a PEP 302 loader if hasattr(getmodule(object, filename), '__loader__'): return filename + # or it is in the linecache + if filename in linecache.cache: + return filename def getabsfile(object, _filename=None): """Return an absolute path to the source or compiled file for an object. @@ -516,7 +524,9 @@ def findsource(object): 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.""" - file = getsourcefile(object) or getfile(object) + file = getsourcefile(object) + if not file: + raise IOError('source code not available') module = getmodule(object, file) if module: lines = linecache.getlines(file, module.__dict__) @@ -736,7 +746,7 @@ def getargs(co): 'varargs' and 'varkw' are the names of the * and ** arguments or None.""" if not iscode(co): - raise TypeError('arg is not a code object') + raise TypeError('{!r} is not a code object'.format(co)) nargs = co.co_argcount names = co.co_varnames @@ -800,7 +810,7 @@ def getargspec(func): if ismethod(func): func = func.im_func if not isfunction(func): - raise TypeError('arg is not a Python function') + raise TypeError('{!r} is not a Python function'.format(func)) args, varargs, varkw = getargs(func.func_code) return ArgSpec(args, varargs, varkw, func.func_defaults) @@ -844,8 +854,8 @@ def formatargspec(args, varargs=None, varkw=None, defaults=None, specs = [] if defaults: firstdefault = len(args) - len(defaults) - for i in range(len(args)): - spec = strseq(args[i], formatarg, join) + for i, arg in enumerate(args): + spec = strseq(arg, formatarg, join) if defaults and i >= firstdefault: spec = spec + formatvalue(defaults[i - firstdefault]) specs.append(spec) @@ -879,6 +889,89 @@ def formatargvalues(args, varargs, varkw, locals, specs.append(formatvarkw(varkw) + formatvalue(locals[varkw])) return '(' + string.join(specs, ', ') + ')' +def getcallargs(func, *positional, **named): + """Get the mapping of arguments to values. + + A dict is returned, with keys the function argument names (including the + names of the * and ** arguments, if any), and values the respective bound + values from 'positional' and 'named'.""" + args, varargs, varkw, defaults = getargspec(func) + f_name = func.__name__ + arg2value = {} + + # The following closures are basically because of tuple parameter unpacking. + assigned_tuple_params = [] + def assign(arg, value): + if isinstance(arg, str): + arg2value[arg] = value + else: + assigned_tuple_params.append(arg) + value = iter(value) + for i, subarg in enumerate(arg): + try: + subvalue = next(value) + except StopIteration: + raise ValueError('need more than %d %s to unpack' % + (i, 'values' if i > 1 else 'value')) + assign(subarg,subvalue) + try: + next(value) + except StopIteration: + pass + else: + raise ValueError('too many values to unpack') + def is_assigned(arg): + if isinstance(arg,str): + return arg in arg2value + return arg in assigned_tuple_params + if ismethod(func) and func.im_self is not None: + # implicit 'self' (or 'cls' for classmethods) argument + positional = (func.im_self,) + positional + num_pos = len(positional) + num_total = num_pos + len(named) + num_args = len(args) + num_defaults = len(defaults) if defaults else 0 + for arg, value in zip(args, positional): + assign(arg, value) + if varargs: + if num_pos > num_args: + assign(varargs, positional[-(num_pos-num_args):]) + else: + assign(varargs, ()) + elif 0 < num_args < num_pos: + raise TypeError('%s() takes %s %d %s (%d given)' % ( + f_name, 'at most' if defaults else 'exactly', num_args, + 'arguments' if num_args > 1 else 'argument', num_total)) + elif num_args == 0 and num_total: + raise TypeError('%s() takes no arguments (%d given)' % + (f_name, num_total)) + for arg in args: + if isinstance(arg, str) and arg in named: + if is_assigned(arg): + raise TypeError("%s() got multiple values for keyword " + "argument '%s'" % (f_name, arg)) + else: + assign(arg, named.pop(arg)) + if defaults: # fill in any missing values with the defaults + for arg, value in zip(args[-num_defaults:], defaults): + if not is_assigned(arg): + assign(arg, value) + if varkw: + assign(varkw, named) + elif named: + unexpected = next(iter(named)) + if isinstance(unexpected, unicode): + unexpected = unexpected.encode(sys.getdefaultencoding(), 'replace') + raise TypeError("%s() got an unexpected keyword argument '%s'" % + (f_name, unexpected)) + unassigned = num_args - len([arg for arg in args if is_assigned(arg)]) + if unassigned: + num_required = num_args - num_defaults + raise TypeError('%s() takes %s %d %s (%d given)' % ( + f_name, 'at least' if defaults else 'exactly', num_required, + 'arguments' if num_required > 1 else 'argument', num_total)) + return arg2value + # -------------------------------------------------- stack frame extraction Traceback = namedtuple('Traceback', 'filename lineno function code_context index') @@ -897,7 +990,7 @@ def getframeinfo(frame, context=1): else: lineno = frame.f_lineno if not isframe(frame): - raise TypeError('arg is not a frame or traceback object') + raise TypeError('{!r} is not a frame or traceback object'.format(frame)) filename = getsourcefile(frame) or getfile(frame) if context > 0: |
