summaryrefslogtreecommitdiffstats
path: root/Lib/inspect.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/inspect.py')
-rw-r--r--Lib/inspect.py125
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: