summaryrefslogtreecommitdiff
path: root/Lib/inspect.py
diff options
context:
space:
mode:
authorYury Selivanov <yselivanov@sprymix.com>2015-05-27 21:59:03 -0400
committerYury Selivanov <yselivanov@sprymix.com>2015-05-27 21:59:03 -0400
commit9e98f339807d5314d15e134713597ef6f871a02f (patch)
treeec56f708c6ab3e2951a0ba2ce4c283cd4bbbfa58 /Lib/inspect.py
parent36860c38a1af9e608dbb44d3a3e8770d1e0dfb67 (diff)
parenta8f012b3465090f6f2998693066f6647b395ea4d (diff)
downloadcpython-9e98f339807d5314d15e134713597ef6f871a02f.tar.gz
Issue 24298: Fix signature() to properly unwrap wrappers around bound methods
Diffstat (limited to 'Lib/inspect.py')
-rw-r--r--Lib/inspect.py701
1 files changed, 468 insertions, 233 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 49e66ce078..57cb3dc0ac 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,9 @@ __author__ = ('Ka-Ping Yee <ping@lfw.org>',
'Yury Selivanov <yselivanov@sprymix.com>')
import ast
+import dis
+import collections.abc
+import enum
import importlib.machinery
import itertools
import linecache
@@ -48,18 +51,10 @@ from operator import attrgetter
from collections import namedtuple, OrderedDict
# Create constants for the compiler flags in Include/code.h
-# We try to get them from dis to avoid duplication, but fall
-# back to hard-coding 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
+# We try to get them from dis to avoid duplication
+mod_dict = globals()
+for k, v in dis.COMPILER_FLAG_NAMES.items():
+ mod_dict["CO_" + v] = k
# See Include/object.h
TPFLAGS_IS_ABSTRACT = 1 << 20
@@ -180,7 +175,22 @@ def isgeneratorfunction(object):
See help(isfunction) for attributes listing."""
return bool((isfunction(object) or ismethod(object)) and
- object.__code__.co_flags & CO_GENERATOR)
+ object.__code__.co_flags & CO_GENERATOR and
+ not object.__code__.co_flags & CO_COROUTINE)
+
+def iscoroutinefunction(object):
+ """Return true if the object is a coroutine function.
+
+ Coroutine functions are defined with "async def" syntax,
+ or generators decorated with "types.coroutine".
+ """
+ return bool((isfunction(object) or ismethod(object)) and
+ object.__code__.co_flags & (CO_ITERABLE_COROUTINE |
+ CO_COROUTINE))
+
+def isawaitable(object):
+ """Return true if the object can be used in "await" expression."""
+ return isinstance(object, collections.abc.Awaitable)
def isgenerator(object):
"""Return true if the object is a generator.
@@ -197,7 +207,12 @@ def isgenerator(object):
send resumes the generator and "sends" a value that becomes
the result of the current yield-expression
throw used to raise an exception inside the generator"""
- return isinstance(object, types.GeneratorType)
+ return (isinstance(object, types.GeneratorType) and
+ not object.gi_code.co_flags & CO_COROUTINE)
+
+def iscoroutine(object):
+ """Return true if the object is a coroutine."""
+ return isinstance(object, collections.abc.Coroutine)
def istraceback(object):
"""Return true if the object is a traceback.
@@ -467,6 +482,74 @@ def indentsize(line):
expline = line.expandtabs()
return len(expline) - len(expline.lstrip())
+def _findclass(func):
+ cls = sys.modules.get(func.__module__)
+ if cls is None:
+ return None
+ for name in func.__qualname__.split('.')[:-1]:
+ cls = getattr(cls, name)
+ if not isclass(cls):
+ return None
+ return cls
+
+def _finddoc(obj):
+ if isclass(obj):
+ for base in obj.__mro__:
+ if base is not object:
+ try:
+ doc = base.__doc__
+ except AttributeError:
+ continue
+ if doc is not None:
+ return doc
+ return None
+
+ if ismethod(obj):
+ name = obj.__func__.__name__
+ self = obj.__self__
+ if (isclass(self) and
+ getattr(getattr(self, name, None), '__func__') is obj.__func__):
+ # classmethod
+ cls = self
+ else:
+ cls = self.__class__
+ elif isfunction(obj):
+ name = obj.__name__
+ cls = _findclass(obj)
+ if cls is None or getattr(cls, name) is not obj:
+ return None
+ elif isbuiltin(obj):
+ name = obj.__name__
+ self = obj.__self__
+ if (isclass(self) and
+ self.__qualname__ + '.' + name == obj.__qualname__):
+ # classmethod
+ cls = self
+ else:
+ cls = self.__class__
+ elif ismethoddescriptor(obj) or isdatadescriptor(obj):
+ name = obj.__name__
+ cls = obj.__objclass__
+ if getattr(cls, name) is not obj:
+ return None
+ elif isinstance(obj, property):
+ func = f.fget
+ name = func.__name__
+ cls = _findclass(func)
+ if cls is None or getattr(cls, name) is not obj:
+ return None
+ else:
+ return None
+
+ for base in cls.__mro__:
+ try:
+ doc = getattr(base, name).__doc__
+ except AttributeError:
+ continue
+ if doc is not None:
+ return doc
+ return None
+
def getdoc(object):
"""Get the documentation string for an object.
@@ -477,6 +560,11 @@ def getdoc(object):
doc = object.__doc__
except AttributeError:
return None
+ if doc is None:
+ try:
+ doc = _finddoc(object)
+ except (AttributeError, TypeError):
+ return None
if not isinstance(doc, str):
return None
return cleandoc(doc)
@@ -814,6 +902,14 @@ def getblock(lines):
pass
return lines[:blockfinder.last]
+def _line_number_helper(code_obj, lines, lnum):
+ """Return a list of source lines and starting line number for a code object.
+
+ The arguments must be a code object with lines and lnum from findsource.
+ """
+ _, end_line = list(dis.findlinestarts(code_obj))[-1]
+ return lines[lnum:end_line], lnum + 1
+
def getsourcelines(object):
"""Return a list of source lines and starting line number for an object.
@@ -822,10 +918,19 @@ def getsourcelines(object):
corresponding to the object and the line number indicates where in the
original source file the first line of code was found. An OSError is
raised if the source code cannot be retrieved."""
+ object = unwrap(object)
lines, lnum = findsource(object)
- if ismodule(object): return lines, 0
- else: return getblock(lines[lnum:]), lnum + 1
+ if ismodule(object):
+ return lines, 0
+ elif iscode(object):
+ return _line_number_helper(object, lines, lnum)
+ elif isfunction(object):
+ return _line_number_helper(object.__code__, lines, lnum)
+ elif ismethod(object):
+ return _line_number_helper(object.__func__.__code__, lines, lnum)
+ else:
+ return getblock(lines[lnum:]), lnum + 1
def getsource(object):
"""Return the text of the source code for an object.
@@ -919,17 +1024,18 @@ ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
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.
- 'args' will include keyword-only argument names.
- 'varargs' and 'varkw' are the names of the * and ** arguments or None.
+ A tuple of four things is returned: (args, varargs, keywords, defaults).
+ 'args' is a list of the argument names, including keyword-only argument names.
+ 'varargs' and 'keywords' 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.
"""
-
+ warnings.warn("inspect.getargspec() is deprecated, "
+ "use inspect.signature() instead", DeprecationWarning,
+ stacklevel=2)
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = \
getfullargspec(func)
if kwonlyargs or ann:
@@ -953,6 +1059,8 @@ def getfullargspec(func):
'annotations' is a dictionary mapping argument names to annotations.
The first four items in the tuple correspond to getargspec().
+
+ This function is deprecated, use inspect.signature() instead.
"""
try:
@@ -972,9 +1080,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
@@ -1043,8 +1152,8 @@ def getargvalues(frame):
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 annotation.__qualname__
+ return annotation.__module__+'.'+annotation.__qualname__
return repr(annotation)
def formatannotationrelativeto(object):
@@ -1317,6 +1426,8 @@ def getlineno(frame):
# FrameType.f_lineno is now a descriptor that grovels co_lnotab
return frame.f_lineno
+FrameInfo = namedtuple('FrameInfo', ('frame',) + Traceback._fields)
+
def getouterframes(frame, context=1):
"""Get a list of records for a frame and all higher (calling) frames.
@@ -1324,7 +1435,8 @@ def getouterframes(frame, context=1):
name, a list of lines of context, and index within the context."""
framelist = []
while frame:
- framelist.append((frame,) + getframeinfo(frame, context))
+ frameinfo = (frame,) + getframeinfo(frame, context)
+ framelist.append(FrameInfo(*frameinfo))
frame = frame.f_back
return framelist
@@ -1335,7 +1447,8 @@ def getinnerframes(tb, context=1):
name, a list of lines of context, and index within the context."""
framelist = []
while tb:
- framelist.append((tb.tb_frame,) + getframeinfo(tb, context))
+ frameinfo = (tb.tb_frame,) + getframeinfo(tb, context)
+ framelist.append(FrameInfo(*frameinfo))
tb = tb.tb_next
return framelist
@@ -1501,6 +1614,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:
@@ -1513,9 +1630,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.
+ """
old_params = wrapped_sig.parameters
new_params = OrderedDict(old_params.items())
@@ -1588,8 +1706,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())
@@ -1613,8 +1732,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
@@ -1624,10 +1744,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,
@@ -1648,11 +1769,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('($')
@@ -1671,7 +1793,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
@@ -1740,8 +1864,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 = \
@@ -1879,8 +2005,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))
@@ -1892,7 +2020,95 @@ 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_function(cls, func):
+ """Private helper: constructs Signature for the given python function."""
+
+ is_duck_function = False
+ if not isfunction(func):
+ if _signature_is_functionlike(func):
+ is_duck_function = True
+ else:
+ # If it's not a pure Python function, and not a duck type
+ # of pure function:
+ raise TypeError('{!r} is not a Python function'.format(func))
+
+ Parameter = cls._parameter_cls
+
+ # Parameter information.
+ func_code = func.__code__
+ pos_count = func_code.co_argcount
+ arg_names = func_code.co_varnames
+ positional = tuple(arg_names[:pos_count])
+ keyword_only_count = func_code.co_kwonlyargcount
+ keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
+ annotations = func.__annotations__
+ defaults = func.__defaults__
+ kwdefaults = func.__kwdefaults__
+
+ if defaults:
+ pos_default_count = len(defaults)
+ else:
+ pos_default_count = 0
+
+ parameters = []
+
+ # Non-keyword-only parameters w/o defaults.
+ non_default_count = pos_count - pos_default_count
+ for name in positional[:non_default_count]:
+ annotation = annotations.get(name, _empty)
+ parameters.append(Parameter(name, annotation=annotation,
+ kind=_POSITIONAL_OR_KEYWORD))
+
+ # ... w/ defaults.
+ for offset, name in enumerate(positional[non_default_count:]):
+ annotation = annotations.get(name, _empty)
+ parameters.append(Parameter(name, annotation=annotation,
+ kind=_POSITIONAL_OR_KEYWORD,
+ default=defaults[offset]))
+
+ # *args
+ if func_code.co_flags & CO_VARARGS:
+ name = arg_names[pos_count + keyword_only_count]
+ annotation = annotations.get(name, _empty)
+ parameters.append(Parameter(name, annotation=annotation,
+ kind=_VAR_POSITIONAL))
+
+ # Keyword-only parameters.
+ for name in keyword_only:
+ default = _empty
+ if kwdefaults is not None:
+ default = kwdefaults.get(name, _empty)
+
+ annotation = annotations.get(name, _empty)
+ parameters.append(Parameter(name, annotation=annotation,
+ kind=_KEYWORD_ONLY,
+ default=default))
+ # **kwargs
+ if func_code.co_flags & CO_VARKEYWORDS:
+ index = pos_count + keyword_only_count
+ if func_code.co_flags & CO_VARARGS:
+ index += 1
+
+ name = arg_names[index]
+ annotation = annotations.get(name, _empty)
+ parameters.append(Parameter(name, annotation=annotation,
+ kind=_VAR_KEYWORD))
+
+ # Is 'func' is a pure Python function - don't validate the
+ # parameters list (for correct order and defaults), it should be OK.
+ return cls(parameters,
+ return_annotation=annotations.get('return', _empty),
+ __validate_parameters__=is_duck_function)
+
+
+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))
@@ -1900,9 +2116,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:
@@ -1915,10 +2134,11 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
# If the unwrapped object is a *method*, we might want to
# skip its first parameter (self).
# See test_signature_wrapped_bound_method for details.
- return _signature_internal(
+ return _signature_from_callable(
obj,
follow_wrapper_chains=follow_wrapper_chains,
- skip_bound_arg=skip_bound_arg)
+ skip_bound_arg=skip_bound_arg,
+ sigcls=sigcls)
try:
sig = obj.__signature__
@@ -1945,9 +2165,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]
@@ -1958,16 +2181,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 _signature_from_function(sigcls, 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
@@ -1978,23 +2203,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-
@@ -2016,7 +2247,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
@@ -2036,9 +2267,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
@@ -2058,41 +2291,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
+ return self._name_
- def __repr__(self):
- return '<_ParameterKind: {!r}>'.format(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:
@@ -2111,7 +2338,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')
@@ -2148,6 +2375,16 @@ class Parameter:
self._name = name
+ def __reduce__(self):
+ return (type(self),
+ (self._name, self._kind),
+ {'_default': self._default,
+ '_annotation': self._annotation})
+
+ def __setstate__(self, state):
+ self._default = state['_default']
+ self._annotation = state['_annotation']
+
@property
def name(self):
return self._name
@@ -2166,7 +2403,7 @@ class Parameter:
def replace(self, *, name=_void, kind=_void,
annotation=_void, default=_void):
- '''Creates a customized copy of the Parameter.'''
+ """Creates a customized copy of the Parameter."""
if name is _void:
name = self._name
@@ -2202,22 +2439,25 @@ class Parameter:
return formatted
def __repr__(self):
- return '<{} at {:#x} {!r}>'.format(self.__class__.__name__,
- id(self), self.name)
+ return '<{} "{}">'.format(self.__class__.__name__, self)
+
+ def __hash__(self):
+ return hash((self.name, self.kind, self.annotation, self.default))
def __eq__(self, other):
- return (issubclass(other.__class__, Parameter) and
- self._name == other._name and
- self._kind == other._kind and
- self._default == other._default and
- self._annotation == other._annotation)
+ return (self is other or
+ (issubclass(other.__class__, Parameter) and
+ self._name == other._name and
+ self._kind == other._kind and
+ self._default == other._default and
+ self._annotation == other._annotation))
def __ne__(self, other):
return not self.__eq__(other)
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:
@@ -2231,7 +2471,9 @@ class BoundArguments:
Tuple of positional arguments values.
* kwargs : dict
Dict of keyword arguments values.
- '''
+ """
+
+ __slots__ = ('arguments', '_signature', '__weakref__')
def __init__(self, signature, arguments):
self.arguments = arguments
@@ -2294,17 +2536,61 @@ class BoundArguments:
return kwargs
+ def apply_defaults(self):
+ """Set default values for missing arguments.
+
+ For variable-positional arguments (*args) the default is an
+ empty tuple.
+
+ For variable-keyword arguments (**kwargs) the default is an
+ empty dict.
+ """
+ arguments = self.arguments
+ if not arguments:
+ return
+ new_arguments = []
+ for name, param in self._signature.parameters.items():
+ try:
+ new_arguments.append((name, arguments[name]))
+ except KeyError:
+ if param.default is not _empty:
+ val = param.default
+ elif param.kind is _VAR_POSITIONAL:
+ val = ()
+ elif param.kind is _VAR_KEYWORD:
+ val = {}
+ else:
+ # This BoundArguments was likely produced by
+ # Signature.bind_partial().
+ continue
+ new_arguments.append((name, val))
+ self.arguments = OrderedDict(new_arguments)
+
def __eq__(self, other):
- return (issubclass(other.__class__, BoundArguments) and
- self.signature == other.signature and
- self.arguments == other.arguments)
+ return (self is other or
+ (issubclass(other.__class__, BoundArguments) and
+ self.signature == other.signature and
+ self.arguments == other.arguments))
def __ne__(self, other):
return not self.__eq__(other)
+ def __setstate__(self, state):
+ self._signature = state['_signature']
+ self.arguments = state['arguments']
+
+ def __getstate__(self):
+ return {'_signature': self._signature, 'arguments': self.arguments}
+
+ def __repr__(self):
+ args = []
+ for arg, value in self.arguments.items():
+ args.append('{}={!r}'.format(arg, value))
+ return '<{} ({})>'.format(self.__class__.__name__, ', '.join(args))
+
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.
@@ -2324,7 +2610,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')
@@ -2335,9 +2621,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()
@@ -2386,89 +2672,28 @@ class Signature:
@classmethod
def from_function(cls, func):
- '''Constructs Signature for the given python function'''
-
- is_duck_function = False
- if not isfunction(func):
- if _signature_is_functionlike(func):
- is_duck_function = True
- else:
- # If it's not a pure Python function, and not a duck type
- # of pure function:
- raise TypeError('{!r} is not a Python function'.format(func))
-
- Parameter = cls._parameter_cls
-
- # Parameter information.
- func_code = func.__code__
- pos_count = func_code.co_argcount
- arg_names = func_code.co_varnames
- positional = tuple(arg_names[:pos_count])
- keyword_only_count = func_code.co_kwonlyargcount
- keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
- annotations = func.__annotations__
- defaults = func.__defaults__
- kwdefaults = func.__kwdefaults__
-
- if defaults:
- pos_default_count = len(defaults)
- else:
- pos_default_count = 0
-
- parameters = []
+ """Constructs Signature for the given python function."""
- # Non-keyword-only parameters w/o defaults.
- non_default_count = pos_count - pos_default_count
- for name in positional[:non_default_count]:
- annotation = annotations.get(name, _empty)
- parameters.append(Parameter(name, annotation=annotation,
- kind=_POSITIONAL_OR_KEYWORD))
-
- # ... w/ defaults.
- for offset, name in enumerate(positional[non_default_count:]):
- annotation = annotations.get(name, _empty)
- parameters.append(Parameter(name, annotation=annotation,
- kind=_POSITIONAL_OR_KEYWORD,
- default=defaults[offset]))
-
- # *args
- if func_code.co_flags & CO_VARARGS:
- name = arg_names[pos_count + keyword_only_count]
- annotation = annotations.get(name, _empty)
- parameters.append(Parameter(name, annotation=annotation,
- kind=_VAR_POSITIONAL))
-
- # Keyword-only parameters.
- for name in keyword_only:
- default = _empty
- if kwdefaults is not None:
- default = kwdefaults.get(name, _empty)
-
- annotation = annotations.get(name, _empty)
- parameters.append(Parameter(name, annotation=annotation,
- kind=_KEYWORD_ONLY,
- default=default))
- # **kwargs
- if func_code.co_flags & CO_VARKEYWORDS:
- index = pos_count + keyword_only_count
- if func_code.co_flags & CO_VARARGS:
- index += 1
-
- name = arg_names[index]
- annotation = annotations.get(name, _empty)
- parameters.append(Parameter(name, annotation=annotation,
- kind=_VAR_KEYWORD))
-
- # Is 'func' is a pure Python function - don't validate the
- # parameters list (for correct order and defaults), it should be OK.
- return cls(parameters,
- return_annotation=annotations.get('return', _empty),
- __validate_parameters__=is_duck_function)
+ warnings.warn("inspect.Signature.from_function() is deprecated, "
+ "use Signature.from_callable()",
+ DeprecationWarning, stacklevel=2)
+ return _signature_from_function(cls, func)
@classmethod
def from_builtin(cls, func):
+ """Constructs Signature for the given builtin function."""
+
+ warnings.warn("inspect.Signature.from_builtin() is deprecated, "
+ "use Signature.from_callable()",
+ DeprecationWarning, stacklevel=2)
return _signature_from_builtin(cls, func)
+ @classmethod
+ def from_callable(cls, obj, *, follow_wrapped=True):
+ """Constructs Signature for the given callable object."""
+ return _signature_from_callable(obj, sigcls=cls,
+ follow_wrapper_chains=follow_wrapped)
+
@property
def parameters(self):
return self._parameters
@@ -2478,10 +2703,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()
@@ -2492,41 +2717,30 @@ class Signature:
return type(self)(parameters,
return_annotation=return_annotation)
- def __eq__(self, other):
- if (not issubclass(type(other), Signature) or
- self.return_annotation != other.return_annotation or
- len(self.parameters) != len(other.parameters)):
- return False
+ def _hash_basis(self):
+ params = tuple(param for param in self.parameters.values()
+ if param.kind != _KEYWORD_ONLY)
- other_positions = {param: idx
- for idx, param in enumerate(other.parameters.keys())}
+ kwo_params = {param.name: param for param in self.parameters.values()
+ if param.kind == _KEYWORD_ONLY}
- for idx, (param_name, param) in enumerate(self.parameters.items()):
- if param.kind == _KEYWORD_ONLY:
- try:
- other_param = other.parameters[param_name]
- except KeyError:
- return False
- else:
- if param != other_param:
- return False
- else:
- try:
- other_idx = other_positions[param_name]
- except KeyError:
- return False
- else:
- if (idx != other_idx or
- param != other.parameters[param_name]):
- return False
+ return params, kwo_params, self.return_annotation
- return True
+ def __hash__(self):
+ params, kwo_params, return_annotation = self._hash_basis()
+ kwo_params = frozenset(kwo_params.values())
+ return hash((params, kwo_params, return_annotation))
+
+ def __eq__(self, other):
+ return (self is other or
+ (isinstance(other, Signature) and
+ self._hash_basis() == other._hash_basis()))
def __ne__(self, other):
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()
@@ -2574,7 +2788,7 @@ class Signature:
parameters_ex = (param,)
break
else:
- msg = '{arg!r} parameter lacking default value'
+ msg = 'missing a required argument: {arg!r}'
msg = msg.format(arg=param.name)
raise TypeError(msg) from None
else:
@@ -2587,7 +2801,8 @@ class Signature:
if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
# Looks like we have no parameter for this positional
# argument
- raise TypeError('too many positional arguments')
+ raise TypeError(
+ 'too many positional arguments') from None
if param.kind == _VAR_POSITIONAL:
# We have an '*args'-like argument, let's fill it with
@@ -2599,8 +2814,9 @@ class Signature:
break
if param.name in kwargs:
- raise TypeError('multiple values for argument '
- '{arg!r}'.format(arg=param.name))
+ raise TypeError(
+ 'multiple values for argument {arg!r}'.format(
+ arg=param.name)) from None
arguments[param.name] = arg_val
@@ -2629,7 +2845,7 @@ class Signature:
# arguments.
if (not partial and param.kind != _VAR_POSITIONAL and
param.default is _empty):
- raise TypeError('{arg!r} parameter lacking default value'. \
+ raise TypeError('missing a required argument: {arg!r}'. \
format(arg=param_name)) from None
else:
@@ -2648,24 +2864,37 @@ class Signature:
# Process our '**kwargs'-like parameter
arguments[kwargs_param.name] = kwargs
else:
- raise TypeError('too many keyword arguments')
+ raise TypeError(
+ 'got an unexpected keyword argument {arg!r}'.format(
+ arg=next(iter(kwargs))))
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 '<{} {}>'.format(self.__class__.__name__, self)
+
def __str__(self):
result = []
render_pos_only_separator = False
@@ -2711,6 +2940,12 @@ class Signature:
return rendered
+
+def signature(obj, *, follow_wrapped=True):
+ """Get a signature object for the passed callable."""
+ return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
+
+
def _main():
""" Logic for inspecting an object given at command line """
import argparse