diff options
Diffstat (limited to 'Lib/inspect.py')
-rw-r--r-- | Lib/inspect.py | 250 |
1 files changed, 160 insertions, 90 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py index 9f9a600..28c767c 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -17,7 +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 + getfullargspec() - same, with support for Python 3 features formatargspec(), formatargvalues() - format an argument spec getouterframes(), getinnerframes() - get info about frames currentframe() - get the current stack frame @@ -32,6 +32,7 @@ __author__ = ('Ka-Ping Yee <ping@lfw.org>', 'Yury Selivanov <yselivanov@sprymix.com>') import ast +import enum import importlib.machinery import itertools import linecache @@ -919,7 +920,7 @@ def getargspec(func): '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 + Use the getfullargspec() API for Python 3 code, as annotations and keyword arguments are supported. getargspec() will raise ValueError if the func has either annotations or keyword arguments. """ @@ -966,9 +967,10 @@ def getfullargspec(func): # getfullargspec() historically ignored __wrapped__ attributes, # so we ensure that remains the case in 3.3+ - sig = _signature_internal(func, - follow_wrapper_chains=False, - skip_bound_arg=False) + sig = _signature_from_callable(func, + follow_wrapper_chains=False, + skip_bound_arg=False, + sigcls=Signature) except Exception as ex: # Most of the times 'signature' will raise ValueError. # But, it can also raise AttributeError, and, maybe something @@ -1495,6 +1497,10 @@ _NonUserDefinedCallables = (_WrapperDescriptor, def _signature_get_user_defined_method(cls, method_name): + """Private helper. Checks if ``cls`` has an attribute + named ``method_name`` and returns it only if it is a + pure python function. + """ try: meth = getattr(cls, method_name) except AttributeError: @@ -1507,9 +1513,10 @@ def _signature_get_user_defined_method(cls, method_name): def _signature_get_partial(wrapped_sig, partial, extra_args=()): - # Internal helper to calculate how 'wrapped_sig' signature will - # look like after applying a 'functools.partial' object (or alike) - # on it. + """Private helper to calculate how 'wrapped_sig' signature will + look like after applying a 'functools.partial' object (or alike) + on it. + """ new_params = OrderedDict(wrapped_sig.parameters.items()) @@ -1556,8 +1563,9 @@ def _signature_get_partial(wrapped_sig, partial, extra_args=()): def _signature_bound_method(sig): - # Internal helper to transform signatures for unbound - # functions to bound methods + """Private helper to transform signatures for unbound + functions to bound methods. + """ params = tuple(sig.parameters.values()) @@ -1581,8 +1589,9 @@ def _signature_bound_method(sig): def _signature_is_builtin(obj): - # Internal helper to test if `obj` is a callable that might - # support Argument Clinic's __text_signature__ protocol. + """Private helper to test if `obj` is a callable that might + support Argument Clinic's __text_signature__ protocol. + """ return (isbuiltin(obj) or ismethoddescriptor(obj) or isinstance(obj, _NonUserDefinedCallables) or @@ -1592,10 +1601,11 @@ def _signature_is_builtin(obj): def _signature_is_functionlike(obj): - # Internal helper to test if `obj` is a duck type of FunctionType. - # A good example of such objects are functions compiled with - # Cython, which have all attributes that a pure Python function - # would have, but have their code statically compiled. + """Private helper to test if `obj` is a duck type of FunctionType. + A good example of such objects are functions compiled with + Cython, which have all attributes that a pure Python function + would have, but have their code statically compiled. + """ if not callable(obj) or isclass(obj): # All function-like objects are obviously callables, @@ -1616,11 +1626,12 @@ def _signature_is_functionlike(obj): def _signature_get_bound_param(spec): - # Internal helper to get first parameter name from a - # __text_signature__ of a builtin method, which should - # be in the following format: '($param1, ...)'. - # Assumptions are that the first argument won't have - # a default value or an annotation. + """ Private helper to get first parameter name from a + __text_signature__ of a builtin method, which should + be in the following format: '($param1, ...)'. + Assumptions are that the first argument won't have + a default value or an annotation. + """ assert spec.startswith('($') @@ -1639,7 +1650,9 @@ def _signature_get_bound_param(spec): def _signature_strip_non_python_syntax(signature): """ - Takes a signature in Argument Clinic's extended signature format. + Private helper function. Takes a signature in Argument Clinic's + extended signature format. + Returns a tuple of three things: * that signature re-rendered in standard Python syntax, * the index of the "self" parameter (generally 0), or None if @@ -1708,8 +1721,10 @@ def _signature_strip_non_python_syntax(signature): def _signature_fromstr(cls, obj, s, skip_bound_arg=True): - # Internal helper to parse content of '__text_signature__' - # and return a Signature based on it + """Private helper to parse content of '__text_signature__' + and return a Signature based on it. + """ + Parameter = cls._parameter_cls clean_signature, self_parameter, last_positional_only = \ @@ -1847,8 +1862,10 @@ def _signature_fromstr(cls, obj, s, skip_bound_arg=True): def _signature_from_builtin(cls, func, skip_bound_arg=True): - # Internal helper function to get signature for - # builtin callables + """Private helper function to get signature for + builtin callables. + """ + if not _signature_is_builtin(func): raise TypeError("{!r} is not a Python builtin " "function".format(func)) @@ -1860,7 +1877,14 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True): return _signature_fromstr(cls, func, s, skip_bound_arg) -def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True): +def _signature_from_callable(obj, *, + follow_wrapper_chains=True, + skip_bound_arg=True, + sigcls): + + """Private helper function to get signature for arbitrary + callable objects. + """ if not callable(obj): raise TypeError('{!r} is not a callable object'.format(obj)) @@ -1868,9 +1892,12 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True): if isinstance(obj, types.MethodType): # In this case we skip the first parameter of the underlying # function (usually `self` or `cls`). - sig = _signature_internal(obj.__func__, - follow_wrapper_chains, - skip_bound_arg) + sig = _signature_from_callable( + obj.__func__, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) + if skip_bound_arg: return _signature_bound_method(sig) else: @@ -1901,9 +1928,12 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True): # (usually `self`, or `cls`) will not be passed # automatically (as for boundmethods) - wrapped_sig = _signature_internal(partialmethod.func, - follow_wrapper_chains, - skip_bound_arg) + wrapped_sig = _signature_from_callable( + partialmethod.func, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) + sig = _signature_get_partial(wrapped_sig, partialmethod, (None,)) first_wrapped_param = tuple(wrapped_sig.parameters.values())[0] @@ -1914,16 +1944,18 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True): if isfunction(obj) or _signature_is_functionlike(obj): # If it's a pure Python function, or an object that is duck type # of a Python function (Cython functions, for instance), then: - return Signature.from_function(obj) + return sigcls.from_function(obj) if _signature_is_builtin(obj): - return _signature_from_builtin(Signature, obj, + return _signature_from_builtin(sigcls, obj, skip_bound_arg=skip_bound_arg) if isinstance(obj, functools.partial): - wrapped_sig = _signature_internal(obj.func, - follow_wrapper_chains, - skip_bound_arg) + wrapped_sig = _signature_from_callable( + obj.func, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) return _signature_get_partial(wrapped_sig, obj) sig = None @@ -1934,23 +1966,29 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True): # in its metaclass call = _signature_get_user_defined_method(type(obj), '__call__') if call is not None: - sig = _signature_internal(call, - follow_wrapper_chains, - skip_bound_arg) + sig = _signature_from_callable( + call, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) else: # Now we check if the 'obj' class has a '__new__' method new = _signature_get_user_defined_method(obj, '__new__') if new is not None: - sig = _signature_internal(new, - follow_wrapper_chains, - skip_bound_arg) + sig = _signature_from_callable( + new, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) else: # Finally, we should have at least __init__ implemented init = _signature_get_user_defined_method(obj, '__init__') if init is not None: - sig = _signature_internal(init, - follow_wrapper_chains, - skip_bound_arg) + sig = _signature_from_callable( + init, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) if sig is None: # At this point we know, that `obj` is a class, with no user- @@ -1972,7 +2010,7 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True): if text_sig: # If 'obj' class has a __text_signature__ attribute: # return a signature based on it - return _signature_fromstr(Signature, obj, text_sig) + return _signature_fromstr(sigcls, obj, text_sig) # No '__text_signature__' was found for the 'obj' class. # Last option is to check if its '__init__' is @@ -1992,9 +2030,11 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True): call = _signature_get_user_defined_method(type(obj), '__call__') if call is not None: try: - sig = _signature_internal(call, - follow_wrapper_chains, - skip_bound_arg) + sig = _signature_from_callable( + call, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + sigcls=sigcls) except ValueError as ex: msg = 'no signature found for {!r}'.format(obj) raise ValueError(msg) from ex @@ -2014,41 +2054,35 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True): raise ValueError('callable {!r} is not supported by signature'.format(obj)) -def signature(obj): - '''Get a signature object for the passed callable.''' - return _signature_internal(obj) - class _void: - '''A private marker - used in Parameter & Signature''' + """A private marker - used in Parameter & Signature.""" class _empty: - pass + """Marker object for Signature.empty and Parameter.empty.""" -class _ParameterKind(int): - def __new__(self, *args, name): - obj = int.__new__(self, *args) - obj._name = name - return obj +class _ParameterKind(enum.IntEnum): + POSITIONAL_ONLY = 0 + POSITIONAL_OR_KEYWORD = 1 + VAR_POSITIONAL = 2 + KEYWORD_ONLY = 3 + VAR_KEYWORD = 4 def __str__(self): - return self._name - - def __repr__(self): - return '<_ParameterKind: {!r}>'.format(self._name) + return self._name_ -_POSITIONAL_ONLY = _ParameterKind(0, name='POSITIONAL_ONLY') -_POSITIONAL_OR_KEYWORD = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD') -_VAR_POSITIONAL = _ParameterKind(2, name='VAR_POSITIONAL') -_KEYWORD_ONLY = _ParameterKind(3, name='KEYWORD_ONLY') -_VAR_KEYWORD = _ParameterKind(4, name='VAR_KEYWORD') +_POSITIONAL_ONLY = _ParameterKind.POSITIONAL_ONLY +_POSITIONAL_OR_KEYWORD = _ParameterKind.POSITIONAL_OR_KEYWORD +_VAR_POSITIONAL = _ParameterKind.VAR_POSITIONAL +_KEYWORD_ONLY = _ParameterKind.KEYWORD_ONLY +_VAR_KEYWORD = _ParameterKind.VAR_KEYWORD class Parameter: - '''Represents a parameter in a function signature. + """Represents a parameter in a function signature. Has the following public attributes: @@ -2067,7 +2101,7 @@ class Parameter: Possible values: `Parameter.POSITIONAL_ONLY`, `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`, `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`. - ''' + """ __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg') @@ -2107,6 +2141,18 @@ class Parameter: self._partial_kwarg = _partial_kwarg + def __reduce__(self): + return (type(self), + (self._name, self._kind), + {'_partial_kwarg': self._partial_kwarg, + '_default': self._default, + '_annotation': self._annotation}) + + def __setstate__(self, state): + self._partial_kwarg = state['_partial_kwarg'] + self._default = state['_default'] + self._annotation = state['_annotation'] + @property def name(self): return self._name @@ -2125,7 +2171,7 @@ class Parameter: def replace(self, *, name=_void, kind=_void, annotation=_void, default=_void, _partial_kwarg=_void): - '''Creates a customized copy of the Parameter.''' + """Creates a customized copy of the Parameter.""" if name is _void: name = self._name @@ -2165,8 +2211,8 @@ class Parameter: return formatted def __repr__(self): - return '<{} at {:#x} {!r}>'.format(self.__class__.__name__, - id(self), self.name) + return '<{} at {:#x} "{}">'.format(self.__class__.__name__, + id(self), self) def __eq__(self, other): # NB: We deliberately do not compare '_partial_kwarg' attributes @@ -2191,7 +2237,7 @@ class Parameter: class BoundArguments: - '''Result of `Signature.bind` call. Holds the mapping of arguments + """Result of `Signature.bind` call. Holds the mapping of arguments to the function's parameters. Has the following public attributes: @@ -2205,7 +2251,7 @@ class BoundArguments: Tuple of positional arguments values. * kwargs : dict Dict of keyword arguments values. - ''' + """ def __init__(self, signature, arguments): self.arguments = arguments @@ -2284,7 +2330,7 @@ class BoundArguments: class Signature: - '''A Signature object represents the overall signature of a function. + """A Signature object represents the overall signature of a function. It stores a Parameter object for each parameter accepted by the function, as well as information specific to the function itself. @@ -2304,7 +2350,7 @@ class Signature: * bind_partial(*args, **kwargs) -> BoundArguments Creates a partial mapping from positional and keyword arguments to parameters (simulating 'functools.partial' behavior.) - ''' + """ __slots__ = ('_return_annotation', '_parameters') @@ -2315,9 +2361,9 @@ class Signature: def __init__(self, parameters=None, *, return_annotation=_empty, __validate_parameters__=True): - '''Constructs Signature from the given list of Parameter + """Constructs Signature from the given list of Parameter objects and 'return_annotation'. All arguments are optional. - ''' + """ if parameters is None: params = OrderedDict() @@ -2370,7 +2416,7 @@ class Signature: @classmethod def from_function(cls, func): - '''Constructs Signature for the given python function''' + """Constructs Signature for the given python function.""" is_duck_function = False if not isfunction(func): @@ -2451,8 +2497,14 @@ class Signature: @classmethod def from_builtin(cls, func): + """Constructs Signature for the given builtin function.""" return _signature_from_builtin(cls, func) + @classmethod + def from_callable(cls, obj): + """Constructs Signature for the given callable object.""" + return _signature_from_callable(obj, sigcls=cls) + @property def parameters(self): return self._parameters @@ -2462,10 +2514,10 @@ class Signature: return self._return_annotation def replace(self, *, parameters=_void, return_annotation=_void): - '''Creates a customized copy of the Signature. + """Creates a customized copy of the Signature. Pass 'parameters' and/or 'return_annotation' arguments to override them in the new copy. - ''' + """ if parameters is _void: parameters = self.parameters.values() @@ -2510,7 +2562,7 @@ class Signature: return not self.__eq__(other) def _bind(self, args, kwargs, *, partial=False): - '''Private method. Don't use directly.''' + """Private method. Don't use directly.""" arguments = OrderedDict() @@ -2646,19 +2698,31 @@ class Signature: return self._bound_arguments_cls(self, arguments) def bind(*args, **kwargs): - '''Get a BoundArguments object, that maps the passed `args` + """Get a BoundArguments object, that maps the passed `args` and `kwargs` to the function's signature. Raises `TypeError` if the passed arguments can not be bound. - ''' + """ return args[0]._bind(args[1:], kwargs) def bind_partial(*args, **kwargs): - '''Get a BoundArguments object, that partially maps the + """Get a BoundArguments object, that partially maps the passed `args` and `kwargs` to the function's signature. Raises `TypeError` if the passed arguments can not be bound. - ''' + """ return args[0]._bind(args[1:], kwargs, partial=True) + def __reduce__(self): + return (type(self), + (tuple(self._parameters.values()),), + {'_return_annotation': self._return_annotation}) + + def __setstate__(self, state): + self._return_annotation = state['_return_annotation'] + + def __repr__(self): + return '<{} at {:#x} "{}">'.format(self.__class__.__name__, + id(self), self) + def __str__(self): result = [] render_pos_only_separator = False @@ -2704,6 +2768,12 @@ class Signature: return rendered + +def signature(obj): + """Get a signature object for the passed callable.""" + return Signature.from_callable(obj) + + def _main(): """ Logic for inspecting an object given at command line """ import argparse |