diff options
Diffstat (limited to 'Lib/inspect.py')
-rw-r--r-- | Lib/inspect.py | 534 |
1 files changed, 335 insertions, 199 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py index 66d51865734..203175568b9 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1,8 +1,7 @@ -# -*- coding: iso-8859-1 -*- """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. +attributes (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: @@ -18,6 +17,7 @@ Here are some of the useful functions provided by this module: getclasstree() - arrange classes so as to represent their hierarchy getargspec(), getargvalues(), getcallargs() - get info about function arguments + getfullargspec() - same, with support for Python-3000 features formatargspec(), formatargvalues() - format an argument spec getouterframes(), getinnerframes() - get info about frames currentframe() - get the current stack frame @@ -32,18 +32,29 @@ __date__ = '1 Jan 2001' import sys import os import types +import itertools import string import re -import dis import imp import tokenize import linecache from operator import attrgetter from collections import namedtuple -# These constants are from Include/code.h. -CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS = 0x1, 0x2, 0x4, 0x8 -CO_NESTED, CO_GENERATOR, CO_NOFREE = 0x10, 0x20, 0x40 +# Create constants for the compiler flags in Include/code.h +# We try to get them from dis to avoid duplication, but fall +# back to hardcording so the dependency is optional +try: + from dis import COMPILER_FLAG_NAMES as _flag_names +except ImportError: + CO_OPTIMIZED, CO_NEWLOCALS = 0x1, 0x2 + CO_VARARGS, CO_VARKEYWORDS = 0x4, 0x8 + CO_NESTED, CO_GENERATOR, CO_NOFREE = 0x10, 0x20, 0x40 +else: + mod_dict = globals() + for k, v in _flag_names.items(): + mod_dict["CO_" + v] = k + # See Include/object.h TPFLAGS_IS_ABSTRACT = 1 << 20 @@ -52,6 +63,7 @@ def ismodule(object): """Return true if the object is a module. Module objects provide these attributes: + __cached__ pathname to byte compiled file __doc__ documentation string __file__ filename (missing for built-in modules)""" return isinstance(object, types.ModuleType) @@ -62,7 +74,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, (type, types.ClassType)) + return isinstance(object, type) def ismethod(object): """Return true if the object is an instance method. @@ -70,9 +82,8 @@ def ismethod(object): 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""" + __func__ function object containing implementation of method + __self__ instance to which this method is bound""" return isinstance(object, types.MethodType) def ismethoddescriptor(object): @@ -88,12 +99,12 @@ def ismethoddescriptor(object): Methods implemented via descriptors that also pass one of the other tests return false from the ismethoddescriptor() test, simply because the other tests promise more -- you can, e.g., count on having the - im_func attribute (etc) when an object passes ismethod().""" - return (hasattr(object, "__get__") - and not hasattr(object, "__set__") # else it's a data descriptor - and not ismethod(object) # mutual exclusion - and not isfunction(object) - and not isclass(object)) + __func__ attribute (etc) when an object passes ismethod().""" + if isclass(object) or ismethod(object) or isfunction(object): + # mutual exclusion + return False + tp = type(object) + return hasattr(tp, "__get__") and not hasattr(tp, "__set__") def isdatadescriptor(object): """Return true if the object is a data descriptor. @@ -103,7 +114,11 @@ def isdatadescriptor(object): Typically, data descriptors will also have __name__ and __doc__ attributes (properties, getsets, and members have both of these attributes), but this is not guaranteed.""" - return (hasattr(object, "__set__") and hasattr(object, "__get__")) + if isclass(object) or ismethod(object) or isfunction(object): + # mutual exclusion + return False + tp = type(object) + return hasattr(tp, "__set__") and hasattr(tp, "__get__") if hasattr(types, 'MemberDescriptorType'): # CPython and equivalent @@ -145,11 +160,11 @@ def isfunction(object): 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__)""" + __code__ code object containing compiled function bytecode + __defaults__ tuple of any default values for arguments + __globals__ global namespace in which this function was defined + __annotations__ dict of parameter annotations + __kwdefaults__ dict of keyword only parameters with defaults""" return isinstance(object, types.FunctionType) def isgeneratorfunction(object): @@ -159,7 +174,7 @@ def isgeneratorfunction(object): See help(isfunction) for attributes listing.""" return bool((isfunction(object) or ismethod(object)) and - object.func_code.co_flags & CO_GENERATOR) + object.__code__.co_flags & CO_GENERATOR) def isgenerator(object): """Return true if the object is a generator. @@ -195,14 +210,10 @@ def isframe(object): 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 isinstance(object, types.FrameType) @@ -247,12 +258,23 @@ def isabstract(object): 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.""" + if isclass(object): + mro = (object,) + getmro(object) + else: + mro = () results = [] for key in dir(object): - try: - value = getattr(object, key) - except AttributeError: - continue + # First try to get the value via __dict__. Some descriptors don't + # like calling their __get__ (see bug #1785). + for base in mro: + if key in base.__dict__: + value = base.__dict__[key] + break + else: + try: + value = getattr(object, key) + except AttributeError: + continue if not predicate or predicate(value): results.append((key, value)) results.sort() @@ -317,7 +339,7 @@ def classify_class_attrs(cls): kind = "data" else: obj_via_getattr = getattr(cls, name) - if (ismethod(obj_via_getattr) or + if (isfunction(obj_via_getattr) or ismethoddescriptor(obj_via_getattr)): kind = "method" else: @@ -329,28 +351,16 @@ def classify_class_attrs(cls): return result # ----------------------------------------------------------- class helpers -def _searchbases(cls, accum): - # Simulate the "classic class" search order. - if cls in accum: - return - accum.append(cls) - for base in cls.__bases__: - _searchbases(base, accum) def getmro(cls): "Return tuple of base classes (including cls) in method resolution order." - if hasattr(cls, "__mro__"): - return cls.__mro__ - else: - result = [] - _searchbases(cls, result) - return tuple(result) + return cls.__mro__ # -------------------------------------------------- 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)) + expline = line.expandtabs() + return len(expline) - len(expline.lstrip()) def getdoc(object): """Get the documentation string for an object. @@ -362,7 +372,7 @@ def getdoc(object): doc = object.__doc__ except AttributeError: return None - if not isinstance(doc, types.StringTypes): + if not isinstance(doc, str): return None return cleandoc(doc) @@ -372,28 +382,28 @@ def cleandoc(doc): Any whitespace that can be uniformly removed from the second line onwards is removed.""" try: - lines = string.split(string.expandtabs(doc), '\n') + lines = doc.expandtabs().split('\n') except UnicodeError: return None else: # Find minimum indentation of any non-blank lines after first line. - margin = sys.maxint + margin = sys.maxsize for line in lines[1:]: - content = len(string.lstrip(line)) + content = len(line.lstrip()) if content: indent = len(line) - content margin = min(margin, indent) # Remove indentation. if lines: lines[0] = lines[0].lstrip() - if margin < sys.maxint: + if margin < sys.maxsize: for i in range(1, len(lines)): lines[i] = lines[i][margin:] # Remove any trailing or leading blank lines. while lines and not lines[-1]: lines.pop() while lines and not lines[0]: lines.pop(0) - return string.join(lines, '\n') + return '\n'.join(lines) def getfile(object): """Work out which source or compiled file an object was defined in.""" @@ -407,9 +417,9 @@ def getfile(object): return object.__file__ raise TypeError('{!r} is a built-in class'.format(object)) if ismethod(object): - object = object.im_func + object = object.__func__ if isfunction(object): - object = object.func_code + object = object.__code__ if istraceback(object): object = object.tb_frame if isframe(object): @@ -424,9 +434,8 @@ ModuleInfo = namedtuple('ModuleInfo', 'name suffix mode module_type') def getmoduleinfo(path): """Get the module name, suffix, mode, and module type for a given file.""" filename = os.path.basename(path) - suffixes = map(lambda info: - (-len(info[0]), info[0], info[1], info[2]), - imp.get_suffixes()) + suffixes = [(-len(suffix), suffix, mode, mtype) + for suffix, mode, mtype in imp.get_suffixes()] suffixes.sort() # try longest suffixes first, in case they overlap for neglen, suffix, mode, mtype in suffixes: if filename[neglen:] == suffix: @@ -442,10 +451,10 @@ def getsourcefile(object): Return None if no way can be identified to get the source. """ filename = getfile(object) - if string.lower(filename[-4:]) in ('.pyc', '.pyo'): + if filename[-4:].lower() in ('.pyc', '.pyo'): filename = filename[:-4] + '.py' for suffix, mode, kind in imp.get_suffixes(): - if 'b' in mode and string.lower(filename[-len(suffix):]) == suffix: + if 'b' in mode and filename[-len(suffix):].lower() == suffix: # Looks like a binary file. We want to only return a text file. return None if os.path.exists(filename): @@ -487,7 +496,7 @@ def getmodule(object, _filename=None): return sys.modules.get(modulesbyfile[file]) # Update the filename to module name cache and check yet again # Copy sys.modules in order to cope with changes while iterating - for modname, module in sys.modules.items(): + for modname, module in list(sys.modules.items()): if ismodule(module) and hasattr(module, '__file__'): f = module.__file__ if f == _filesbymodname.get(modname, None): @@ -509,7 +518,7 @@ def getmodule(object, _filename=None): if mainobject is object: return main # Check builtins - builtin = sys.modules['__builtin__'] + builtin = sys.modules['builtins'] if hasattr(builtin, object.__name__): builtinobject = getattr(builtin, object.__name__) if builtinobject is object: @@ -564,9 +573,9 @@ def findsource(object): raise IOError('could not find class definition') if ismethod(object): - object = object.im_func + object = object.__func__ if isfunction(object): - object = object.func_code + object = object.__code__ if istraceback(object): object = object.tb_frame if isframe(object): @@ -596,36 +605,36 @@ def getcomments(object): # Look for a comment block at the top of the file. start = 0 if lines and lines[0][:2] == '#!': start = 1 - while start < len(lines) and string.strip(lines[start]) in ('', '#'): + while start < len(lines) and lines[start].strip() in ('', '#'): start = start + 1 if start < len(lines) and lines[start][:1] == '#': comments = [] end = start while end < len(lines) and lines[end][:1] == '#': - comments.append(string.expandtabs(lines[end])) + comments.append(lines[end].expandtabs()) end = end + 1 - return string.join(comments, '') + return ''.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 \ + if end >= 0 and lines[end].lstrip()[:1] == '#' and \ indentsize(lines[end]) == indent: - comments = [string.lstrip(string.expandtabs(lines[end]))] + comments = [lines[end].expandtabs().lstrip()] if end > 0: end = end - 1 - comment = string.lstrip(string.expandtabs(lines[end])) + comment = lines[end].expandtabs().lstrip() 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]) == '#': + comment = lines[end].expandtabs().lstrip() + while comments and comments[0].strip() == '#': comments[:1] = [] - while comments and string.strip(comments[-1]) == '#': + while comments and comments[-1].strip() == '#': comments[-1:] = [] - return string.join(comments, '') + return ''.join(comments) class EndOfBlock(Exception): pass @@ -638,9 +647,7 @@ class BlockFinder: self.passline = False self.last = 1 - def tokeneater(self, type, token, srow_scol, erow_ecol, line): - srow, scol = srow_scol - erow, ecol = erow_ecol + def tokeneater(self, type, token, srowcol, erowcol, line): if not self.started: # look for the first "def", "class" or "lambda" if token in ("def", "class", "lambda"): @@ -650,7 +657,7 @@ class BlockFinder: self.passline = True # skip to the end of the line elif type == tokenize.NEWLINE: self.passline = False # stop skipping when a NEWLINE is seen - self.last = srow + self.last = srowcol[0] if self.islambda: # lambdas always end at the first NEWLINE raise EndOfBlock elif self.passline: @@ -674,7 +681,9 @@ def getblock(lines): """Extract the block of code at the top of the given list of lines.""" blockfinder = BlockFinder() try: - tokenize.tokenize(iter(lines).next, blockfinder.tokeneater) + tokens = tokenize.generate_tokens(iter(lines).__next__) + for _token in tokens: + blockfinder.tokeneater(*_token) except (EndOfBlock, IndentationError): pass return lines[:blockfinder.last] @@ -699,7 +708,7 @@ def getsource(object): 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, '') + return ''.join(lines) # --------------------------------------------------- class tree extraction def walktree(classes, children, parent): @@ -712,7 +721,7 @@ def walktree(classes, children, parent): results.append(walktree(children[c], children, c)) return results -def getclasstree(classes, unique=0): +def getclasstree(classes, unique=False): """Arrange the given list of classes into a hierarchy of nested lists. Where a nested list appears, it contains classes derived from the class @@ -738,57 +747,36 @@ def getclasstree(classes, unique=0): return walktree(roots, children, None) # ------------------------------------------------ argument list extraction -Arguments = namedtuple('Arguments', 'args varargs keywords') +Arguments = namedtuple('Arguments', 'args, varargs, varkw') 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.""" + Three things are returned: (args, varargs, varkw), where + 'args' is the list of argument names. Keyword-only arguments are + appended. 'varargs' and 'varkw' are the names of the * and ** + arguments or None.""" + args, varargs, kwonlyargs, varkw = _getfullargs(co) + return Arguments(args + kwonlyargs, varargs, varkw) + +def _getfullargs(co): + """Get information about the arguments accepted by a code object. + + Four things are returned: (args, varargs, kwonlyargs, varkw), where + 'args' and 'kwonlyargs' are lists of argument names, and 'varargs' + and 'varkw' are the names of the * and ** arguments or None.""" if not iscode(co): raise TypeError('{!r} is not a code object'.format(co)) nargs = co.co_argcount names = co.co_varnames + nkwargs = co.co_kwonlyargcount args = list(names[:nargs]) + kwonlyargs = list(names[nargs:nargs+nkwargs]) 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(co.co_code): - op = ord(co.co_code[step]) - step = step + 1 - if op >= dis.HAVE_ARGUMENT: - opname = dis.opname[op] - value = ord(co.co_code[step]) + ord(co.co_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]) - - # Special case for sublists of length 1: def foo((bar)) - # doesn't generate the UNPACK_TUPLE bytecode, so if - # `remain` is empty here, we have such a sublist. - if not remain: - stack[0] = [stack[0]] - break - else: - 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] - + nargs += nkwargs varargs = None if co.co_flags & CO_VARARGS: varargs = co.co_varnames[nargs] @@ -796,7 +784,8 @@ def getargs(co): varkw = None if co.co_flags & CO_VARKEYWORDS: varkw = co.co_varnames[nargs] - return Arguments(args, varargs, varkw) + return args, varargs, kwonlyargs, varkw + ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults') @@ -804,17 +793,48 @@ 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). + 'args' is a list of the argument names. + 'args' will include keyword-only argument names. + '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. + + Use the getfullargspec() API for Python-3000 code, as annotations + and keyword arguments are supported. getargspec() will raise ValueError + if the func has either annotations or keyword arguments. + """ + + args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \ + getfullargspec(func) + if kwonlyargs or ann: + raise ValueError("Function has keyword-only arguments or annotations" + ", use getfullargspec() API which can support them") + return ArgSpec(args, varargs, varkw, defaults) + +FullArgSpec = namedtuple('FullArgSpec', + 'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations') + +def getfullargspec(func): + """Get the names and default values of a function's arguments. + + A tuple of seven things is returned: + (args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults annotations). + 'args' is a list of the argument names. '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. + 'kwonlyargs' is a list of keyword-only argument names. + 'kwonlydefaults' is a dictionary mapping names from kwonlyargs to defaults. + 'annotations' is a dictionary mapping argument names to annotations. + + The first four items in the tuple correspond to getargspec(). """ if ismethod(func): - func = func.im_func + func = func.__func__ if not isfunction(func): 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) + args, varargs, kwonlyargs, varkw = _getfullargs(func.__code__) + return FullArgSpec(args, varargs, varkw, func.__defaults__, + kwonlyargs, func.__kwdefaults__, func.__annotations__) ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals') @@ -822,57 +842,77 @@ 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). + 'args' is a list of the argument names. '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 ArgInfo(args, varargs, varkw, frame.f_locals) -def joinseq(seq): - if len(seq) == 1: - return '(' + seq[0] + ',)' - else: - return '(' + string.join(seq, ', ') + ')' +def formatannotation(annotation, base_module=None): + if isinstance(annotation, type): + if annotation.__module__ in ('builtins', base_module): + return annotation.__name__ + return annotation.__module__+'.'+annotation.__name__ + return repr(annotation) -def strseq(object, convert, join=joinseq): - """Recursively walk a sequence, stringifying each element.""" - if type(object) in (list, tuple): - return join(map(lambda o, c=convert, j=join: strseq(o, c, j), object)) - else: - return convert(object) +def formatannotationrelativeto(object): + module = getattr(object, '__module__', None) + def _formatannotation(annotation): + return formatannotation(annotation, module) + return _formatannotation def formatargspec(args, varargs=None, varkw=None, defaults=None, + kwonlyargs=(), kwonlydefaults={}, annotations={}, 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.""" + formatreturns=lambda text: ' -> ' + text, + formatannotation=formatannotation): + """Format an argument spec from the values returned by getargspec + or getfullargspec. + + The first seven arguments are (args, varargs, varkw, defaults, + kwonlyargs, kwonlydefaults, annotations). The other five arguments + are the corresponding optional formatting functions that are called to + turn names and values into strings. The last argument is an optional + function to format the sequence of arguments.""" + def formatargandannotation(arg): + result = formatarg(arg) + if arg in annotations: + result += ': ' + formatannotation(annotations[arg]) + return result specs = [] if defaults: firstdefault = len(args) - len(defaults) for i, arg in enumerate(args): - spec = strseq(arg, formatarg, join) + spec = formatargandannotation(arg) if defaults and i >= firstdefault: spec = spec + formatvalue(defaults[i - firstdefault]) specs.append(spec) if varargs is not None: - specs.append(formatvarargs(varargs)) + specs.append(formatvarargs(formatargandannotation(varargs))) + else: + if kwonlyargs: + specs.append('*') + if kwonlyargs: + for kwonlyarg in kwonlyargs: + spec = formatargandannotation(kwonlyarg) + if kwonlydefaults and kwonlyarg in kwonlydefaults: + spec += formatvalue(kwonlydefaults[kwonlyarg]) + specs.append(spec) if varkw is not None: - specs.append(formatvarkw(varkw)) - return '(' + string.join(specs, ', ') + ')' + specs.append(formatvarkw(formatargandannotation(varkw))) + result = '(' + ', '.join(specs) + ')' + if 'return' in annotations: + result += formatreturns(formatannotation(annotations['return'])) + return result def formatargvalues(args, varargs, varkw, locals, formatarg=str, formatvarargs=lambda name: '*' + name, formatvarkw=lambda name: '**' + name, - formatvalue=lambda value: '=' + repr(value), - join=joinseq): + formatvalue=lambda value: '=' + repr(value)): """Format an argument spec from the 4 values returned by getargvalues. The first four arguments are (args, varargs, varkw, locals). The @@ -884,12 +924,12 @@ def formatargvalues(args, varargs, varkw, locals, return formatarg(name) + formatvalue(locals[name]) specs = [] for i in range(len(args)): - specs.append(strseq(args[i], convert, join)) + specs.append(convert(args[i])) if varargs: specs.append(formatvarargs(varargs) + formatvalue(locals[varargs])) if varkw: specs.append(formatvarkw(varkw) + formatvalue(locals[varkw])) - return '(' + string.join(specs, ', ') + ')' + return '(' + ', '.join(specs) + ')' def getcallargs(func, *positional, **named): """Get the mapping of arguments to values. @@ -897,82 +937,64 @@ def getcallargs(func, *positional, **named): 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) + spec = getfullargspec(func) + args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec 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: + if ismethod(func) and func.__self__ is not None: # implicit 'self' (or 'cls' for classmethods) argument - positional = (func.im_self,) + positional + positional = (func.__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) + arg2value[arg] = value if varargs: if num_pos > num_args: - assign(varargs, positional[-(num_pos-num_args):]) + arg2value[varargs] = positional[-(num_pos-num_args):] else: - assign(varargs, ()) + arg2value[varargs] = () elif 0 < num_args < num_pos: - raise TypeError('%s() takes %s %d %s (%d given)' % ( + raise TypeError('%s() takes %s %d positional %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: - if varkw: + if varkw or kwonlyargs: if num_pos: # XXX: We should use num_pos, but Python also uses num_total: - raise TypeError('%s() takes exactly 0 arguments ' + raise TypeError('%s() takes exactly 0 positional arguments ' '(%d given)' % (f_name, num_total)) else: 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): + + for arg in itertools.chain(args, kwonlyargs): + if arg in named: + if arg in arg2value: raise TypeError("%s() got multiple values for keyword " "argument '%s'" % (f_name, arg)) else: - assign(arg, named.pop(arg)) + arg2value[arg] = named.pop(arg) + for kwonlyarg in kwonlyargs: + if kwonlyarg not in arg2value: + try: + arg2value[kwonlyarg] = kwonlydefaults[kwonlyarg] + except KeyError: + raise TypeError("%s() needs keyword-only argument %s" % + (f_name, kwonlyarg)) 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 arg not in arg2value: + arg2value[arg] = value if varkw: - assign(varkw, named) + arg2value[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)]) + unassigned = num_args - len([arg for arg in args if arg in arg2value]) if unassigned: num_required = num_args - num_defaults raise TypeError('%s() takes %s %d %s (%d given)' % ( @@ -1044,10 +1066,9 @@ def getinnerframes(tb, context=1): tb = tb.tb_next return framelist -if hasattr(sys, '_getframe'): - currentframe = sys._getframe -else: - currentframe = lambda _=None: None +def currentframe(): + """Return the frame of the caller or None if this is not possible.""" + return sys._getframe(1) if hasattr(sys, "_getframe") else None def stack(context=1): """Return a list of records for the stack above the caller's frame.""" @@ -1056,3 +1077,118 @@ def stack(context=1): def trace(context=1): """Return a list of records for the stack below the current exception.""" return getinnerframes(sys.exc_info()[2], context) + + +# ------------------------------------------------ static version of getattr + +_sentinel = object() + +def _static_getmro(klass): + return type.__dict__['__mro__'].__get__(klass) + +def _check_instance(obj, attr): + instance_dict = {} + try: + instance_dict = object.__getattribute__(obj, "__dict__") + except AttributeError: + pass + return dict.get(instance_dict, attr, _sentinel) + + +def _check_class(klass, attr): + for entry in _static_getmro(klass): + if _shadowed_dict(type(entry)) is _sentinel: + try: + return entry.__dict__[attr] + except KeyError: + pass + return _sentinel + +def _is_type(obj): + try: + _static_getmro(obj) + except TypeError: + return False + return True + +def _shadowed_dict(klass): + dict_attr = type.__dict__["__dict__"] + for entry in _static_getmro(klass): + try: + class_dict = dict_attr.__get__(entry)["__dict__"] + except KeyError: + pass + else: + if not (type(class_dict) is types.GetSetDescriptorType and + class_dict.__name__ == "__dict__" and + class_dict.__objclass__ is entry): + return class_dict + return _sentinel + +def getattr_static(obj, attr, default=_sentinel): + """Retrieve attributes without triggering dynamic lookup via the + descriptor protocol, __getattr__ or __getattribute__. + + Note: this function may not be able to retrieve all attributes + that getattr can fetch (like dynamically created attributes) + and may find attributes that getattr can't (like descriptors + that raise AttributeError). It can also return descriptor objects + instead of instance members in some cases. See the + documentation for details. + """ + instance_result = _sentinel + if not _is_type(obj): + klass = type(obj) + dict_attr = _shadowed_dict(klass) + if (dict_attr is _sentinel or + type(dict_attr) is types.MemberDescriptorType): + instance_result = _check_instance(obj, attr) + else: + klass = obj + + klass_result = _check_class(klass, attr) + + if instance_result is not _sentinel and klass_result is not _sentinel: + if (_check_class(type(klass_result), '__get__') is not _sentinel and + _check_class(type(klass_result), '__set__') is not _sentinel): + return klass_result + + if instance_result is not _sentinel: + return instance_result + if klass_result is not _sentinel: + return klass_result + + if obj is klass: + # for types we check the metaclass too + for entry in _static_getmro(type(klass)): + if _shadowed_dict(type(entry)) is _sentinel: + try: + return entry.__dict__[attr] + except KeyError: + pass + if default is not _sentinel: + return default + raise AttributeError(attr) + + +GEN_CREATED = 'GEN_CREATED' +GEN_RUNNING = 'GEN_RUNNING' +GEN_SUSPENDED = 'GEN_SUSPENDED' +GEN_CLOSED = 'GEN_CLOSED' + +def getgeneratorstate(generator): + """Get current state of a generator-iterator. + + Possible states are: + GEN_CREATED: Waiting to start execution. + GEN_RUNNING: Currently being executed by the interpreter. + GEN_SUSPENDED: Currently suspended at a yield expression. + GEN_CLOSED: Execution has completed. + """ + if generator.gi_running: + return GEN_RUNNING + if generator.gi_frame is None: + return GEN_CLOSED + if generator.gi_frame.f_lasti == -1: + return GEN_CREATED + return GEN_SUSPENDED |