diff -r 5c0ee973a39a -r 8d0364c94b10 Include/methodobject.h --- a/Include/methodobject.h Sun Jun 03 14:40:32 2012 -0700 +++ b/Include/methodobject.h Fri Jun 15 13:23:22 2012 -0400 @@ -77,6 +77,7 @@ PyMethodDef *m_ml; /* Description of the C function to call */ PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */ PyObject *m_module; /* The __module__ attribute, can be anything */ + PyObject *m_signature; /* The __signature__ attribute, cached Signature*/ } PyCFunctionObject; #endif diff -r 5c0ee973a39a -r 8d0364c94b10 Lib/inspect.py --- a/Lib/inspect.py Sun Jun 03 14:40:32 2012 -0700 +++ b/Lib/inspect.py Fri Jun 15 13:23:22 2012 -0400 @@ -22,12 +22,14 @@ getouterframes(), getinnerframes() - get info about frames currentframe() - get the current stack frame stack(), trace() - get info about frames on the stack or in a traceback + + signature() - get a Signature object for the callable """ # This module is in the public domain. No warranties. -__author__ = 'Ka-Ping Yee ' -__date__ = '1 Jan 2001' +__author__ = ('Ka-Ping Yee ', + 'Yury Selivanov ') import imp import importlib.machinery @@ -38,9 +40,11 @@ import sys import tokenize import types +import copy import warnings +import functools from operator import attrgetter -from collections import namedtuple +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 @@ -1223,3 +1227,680 @@ if generator.gi_frame.f_lasti == -1: return GEN_CREATED return GEN_SUSPENDED + + +############################################################################### +### Function Signature Object (PEP 362) +############################################################################### + + +_WrapperDescriptor = type(type.__call__) +_MethodWrapper = type(all.__call__) + +_NonUserDefinedCallables = (_WrapperDescriptor, + _MethodWrapper, + types.BuiltinFunctionType) + + +def _get_user_defined_method(cls, method_name): + try: + meth = getattr(cls, method_name) + except AttributeError: + return + else: + if not isinstance(meth, _NonUserDefinedCallables): + # Once '__signature__' will be added to 'C'-level + # callables, this check won't be necessary + return meth + + +def signature(obj): + '''Get a signature object for the passed callable.''' + + if not callable(obj): + raise TypeError('{!r} is not a callable object'.format(obj)) + + if isinstance(obj, (types.MethodType, classmethod)): + # In this case we skip the first parameter of the underlying + # function (usually `self` or `cls`). + sig = signature(obj.__func__) + sig.parameters.popitem(last=False) + return sig + + try: + sig = obj.__signature__ + except AttributeError: + pass + else: + if sig is None: + if isinstance(obj, types.BuiltinFunctionType): + raise ValueError('no signature found for builtin ' \ + 'function {!r}'.format(obj)) from None + else: + return copy.deepcopy(sig) + + try: + # Was this function wrapped by a decorator? + wrapped = obj.__wrapped__ + except AttributeError: + pass + else: + return signature(wrapped) + + if isinstance(obj, types.FunctionType): + return Signature(obj) + + if isinstance(obj, staticmethod): + return signature(obj.__func__) + + if isinstance(obj, functools.partial): + sig = signature(obj.func) + + partial_args = obj.args or () + partial_keywords = obj.keywords or {} + try: + ba = sig.bind_partial(*partial_args, **partial_keywords) + except TypeError as ex: + raise ValueError('partial object {!r} has incorrect arguments'. \ + format(obj)) from ex + + for arg_name, arg_value in ba.arguments.items(): + param = sig.parameters[arg_name] + if arg_name in partial_keywords: + # We set a new default value, because the following code + # is correct: + # + # >>> def foo(a): print(a) + # >>> print(partial(partial(foo, a=10), a=20)()) + # 20 + # >>> print(partial(partial(foo, a=10), a=20)(a=30)) + # 30 + # + # So, with 'partial' objects, passing a keyword argument is + # like setting a new default value for the corresponding + # parameter + param.default = arg_value + # We also mark this parameter with '__partial_kwarg__' + # flag. Later, in '_bind', the 'default' value of this + # parameter will be added to 'kwargs', to simulate + # the 'functools.partial' real call. + param.__partial_kwarg__ = True + + elif param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) \ + and not getattr(param, '__partial_kwarg__', False): + sig.parameters.pop(arg_name) + + return sig + + sig = None + if isinstance(obj, type): + # obj is a class or a metaclass + + # First, let's see if it has an overloaded __call__ defined + # in its metaclass + call = _get_user_defined_method(type(obj), '__call__') + if call is not None: + sig = signature(call) + else: + # Now we check if the 'obj' class has a '__new__' method + new = _get_user_defined_method(obj, '__new__') + if new is not None: + if isinstance(new, staticmethod): + new = new.__func__ + sig = signature(new) + else: + # Finally, we should have at least __init__ implemented + init = _get_user_defined_method(obj, '__init__') + if init is not None: + sig = signature(init) + elif not isinstance(obj, _NonUserDefinedCallables): + # An object with __call__ + # We also check that the 'obj' is not an instance of + # _WrapperDescriptor or _MethodWrapper to avoid + # infinite recursion (and even potential segfault) + call = _get_user_defined_method(type(obj), '__call__') + if call is not None: + sig = signature(call) + + if sig is not None: + # For classes and objects we skip the first parameter of their + # __call__, __new__, or __init__ methods + sig.parameters.popitem(last=False) + return sig + + raise ValueError('callable {!r} is not supported by signature'.format(obj)) + + +_void = object() + +class Parameter: + '''Represents a parameter in a function signature. + + Has the following public attributes: + + * name : str + The name of the parameter as a string. + * default : object + The default value for the parameter if specified. If the + parameter has no default value, this attribute is not set. + * annotation + The annotation for the parameter if specified. If the + parameter has no annotation, this attribute is not set. + * kind : str + Describes how argument values are bound to the parameter. + Possible values: `Parameter.POSITIONAL_ONLY`, + `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`, + `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`. + * implemented : bool + True if the functionality behind the parameter is + implemented on the local platform. (Almost always True.) + ''' + + POSITIONAL_ONLY = "positional_only" + POSITIONAL_OR_KEYWORD = "positional_or_keyword" + VAR_POSITIONAL = "var_positional" + KEYWORD_ONLY = "keyword_only" + VAR_KEYWORD = "var_keyword" + + def __init__(self, name, *, + kind, default=_void, annotation=_void, implemented=True): + + self.name = name + if default is not _void: + self.default = default + if annotation is not _void: + self.annotation = annotation + if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD, + _VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD): + raise ValueError("invalid value for 'Parameter.kind' attribute") + self.kind = kind + self.implemented = implemented + + def __repr__(self): + return '<{} at 0x{:x} {!r}>'.format(self.__class__.__name__, + id(self), self.name) + + def __copy__(self): + cls = type(self) + copy = cls.__new__(cls) + copy.__dict__.update(self.__dict__) + return copy + + def __eq__(self, other): + return issubclass(other.__class__, Parameter) \ + and self.name == other.name \ + and self.kind == other.kind \ + and self.implemented == other.implemented \ + and getattr(self, 'default', _void) == \ + getattr(other, 'default', _void) \ + and getattr(self, 'annotation', _void) == \ + getattr(other, 'annotation', _void) + + def __ne__(self, other): + return not self.__eq__(other) + + +_POSITIONAL_ONLY = Parameter.POSITIONAL_ONLY +_POSITIONAL_OR_KEYWORD = Parameter.POSITIONAL_OR_KEYWORD +_VAR_POSITIONAL = Parameter.VAR_POSITIONAL +_KEYWORD_ONLY = Parameter.KEYWORD_ONLY +_VAR_KEYWORD = Parameter.VAR_KEYWORD + + +class BoundArguments: + '''Result of `Signature.bind` call. Holds the mapping of arguments + to the function's parameters. + + Has the following public attributes: + + * arguments : OrderedDict + An ordered mutable mapping of parameters' names to arguments' values. + Does not contain arguments' default values. + * signature : Signature + The Signature object that created this instance. + * args : tuple + Tuple of positional arguments values. + * kwargs : dict + Dict of keyword arguments values. + ''' + + def __init__(self, signature, arguments): + self.arguments = arguments + self.signature = signature + + @property + def args(self): + args = [] + for param_name, param in self.signature.parameters.items(): + if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) \ + or getattr(param, '__partial_kwarg__', False): + continue + + try: + arg = self.arguments[param_name] + except KeyError: + continue + else: + if param.kind == _VAR_POSITIONAL: + args.extend(arg) + else: + args.append(arg) + + return tuple(args) + + @property + def kwargs(self): + kwargs = {} + for param_name, param in self.signature.parameters.items(): + if param.kind not in (_VAR_KEYWORD, _KEYWORD_ONLY) \ + and not getattr(param, '__partial_kwarg__', False): + continue + + try: + arg = self.arguments[param_name] + except KeyError: + continue + else: + if param.kind == _VAR_KEYWORD: + kwargs.update(arg) + else: + kwargs[param_name] = arg + + return kwargs + + def __eq__(self, other): + return issubclass(other.__class__, BoundArguments) \ + and self.signature == other.signature \ + and self.arguments == other.arguments + + def __ne__(self, other): + return not self.__eq__(other) + + +class Signature: + '''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. + + A Signature object has the following public attributes and methods: + + * parameters : OrderedDict + An ordered mapping of parameters' names to the corresponding + Parameter objects (keyword-only arguments are in the same order + as listed in `code.co_varnames`). + * return_annotation : object + The annotation for the return type of the function if specified. + If the function has no annotation for its return type, this + attribute is not set. + * bind(*args, **kwargs) -> BoundArguments + Creates a mapping from positional and keyword arguments to + parameters. + * bind_partial(*args, **kwargs) -> BoundArguments + Creates a partial mapping from positional and keyword arguments + to parameters (simulating 'functools.partial' behavior.) + ''' + + _parameter_cls = Parameter + _bound_arguments_cls = BoundArguments + + def __init__(self, func): + if not isinstance(func, types.FunctionType): + raise TypeError('{!r} is not a Python function'.format(func)) + + Parameter = self._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 = OrderedDict() + + # 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, _void) + parameters[name] = Parameter(name, annotation=annotation, + kind=_POSITIONAL_OR_KEYWORD) + + # ... w/ defaults. + for offset, name in enumerate(positional[non_default_count:]): + annotation = annotations.get(name, _void) + parameters[name] = Parameter(name, annotation=annotation, + kind=_POSITIONAL_OR_KEYWORD, + default=defaults[offset]) + + # *args + if func_code.co_flags & 0x04: + name = arg_names[pos_count + keyword_only_count] + annotation = annotations.get(name, _void) + parameters[name] = Parameter(name, annotation=annotation, + kind=_VAR_POSITIONAL) + + # Keyword-only parameters. + for name in keyword_only: + default = _void + if kwdefaults is not None: + default = kwdefaults.get(name, _void) + + annotation = annotations.get(name, _void) + parameters[name] = Parameter(name, annotation=annotation, + kind=_KEYWORD_ONLY, + default=default) + # **kwargs + if func_code.co_flags & 0x08: + index = pos_count + keyword_only_count + if func_code.co_flags & 0x04: + index += 1 + + name = arg_names[index] + annotation = annotations.get(name, _void) + parameters[name] = Parameter(name, annotation=annotation, + kind=_VAR_KEYWORD) + + self.parameters = parameters + + # Return annotation. + try: + self.return_annotation = annotations['return'] + except KeyError: + pass + + def __copy__(self): + cls = type(self) + sig = cls.__new__(cls) + sig.parameters = OrderedDict(self.parameters.items()) + try: + sig.return_annotation = self.return_annotation + except AttributeError: + pass + return sig + + def __deepcopy__(self, memo): + cls = type(self) + sig = cls.__new__(cls) + sig.parameters = OrderedDict((name, param.__copy__()) \ + for name, param in self.parameters.items()) + try: + sig.return_annotation = self.return_annotation + except AttributeError: + pass + return sig + + def __eq__(self, other): + return issubclass(other.__class__, Signature) \ + and self.parameters == other.parameters \ + and getattr(self, 'return_annotation', _void) == \ + getattr(other, 'return_annotation', _void) + + def __ne__(self, other): + return not self.__eq__(other) + + def _bind(self, args, kwargs, *, partial=False): + '''Private method. Don't use directly.''' + + arguments = OrderedDict() + + parameters = iter(self.parameters.values()) + parameters_ex = () + arg_vals = iter(args) + + if partial: + # Support for binding arguments to 'functools.partial' objects. + # See 'functools.partial' case in 'signature()' implementation + # for details. + for param_name, param in self.parameters.items(): + if getattr(param, '__partial_kwarg__', False) \ + and param_name not in kwargs: + # Simulating 'functools.partial' behavior + kwargs[param_name] = param.default + + while True: + # Let's iterate through the positional arguments and corresponding + # parameters + try: + arg_val = next(arg_vals) + except StopIteration: + # No more positional arguments + try: + param = next(parameters) + except StopIteration: + # No more parameters. That's it. Just need to check that + # we have no `kwargs` after this while loop + break + else: + if param.kind == _VAR_POSITIONAL: + # That's OK, just empty *args. Let's start parsing + # kwargs + break + elif param.name in kwargs: + if param.kind == _POSITIONAL_ONLY: + raise TypeError('{arg!r} parameter is positional only, ' \ + 'but was passed as a keyword'. \ + format(arg=param.name)) from None + parameters_ex = (param,) + break + elif param.kind == _VAR_KEYWORD or hasattr(param, 'default'): + # That's fine too - we have a default value for this + # parameter. So, lets start parsing `kwargs`, starting + # with the current parameter + parameters_ex = (param,) + break + else: + if partial: + parameters_ex = (param,) + break + else: + raise TypeError('{arg!r} parameter lacking default' \ + ' value'.format(arg=param.name)) from None + else: + # We have a positional argument to process + try: + param = next(parameters) + except StopIteration: + raise TypeError('too many positional arguments') from None + else: + if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + # Looks like we have no parameter for this positional + # argument + raise TypeError('too many positional arguments') + + if param.kind == _VAR_POSITIONAL: + # We have an '*args'-like argument, let's fill it with + # all positional arguments we have left and move on to + # the next phase + values = [arg_val] + values.extend(arg_vals) + arguments[param.name] = tuple(values) + break + + if param.name in kwargs: + raise TypeError('multiple values for argument ' \ + '{arg!r}'.format(arg=param.name)) + + arguments[param.name] = arg_val + + # Now, we iterate through the remaining parameters to process + # keyword arguments + kwargs_param = None + for param in itertools.chain(parameters_ex, parameters): + if param.kind == _POSITIONAL_ONLY: + raise TypeError('{arg!r} parameter is positional only, ' \ + 'but was passed as a keyword'. \ + format(arg=param.name)) + + if param.kind == _VAR_KEYWORD: + # Memorize that we have a '**kwargs'-like parameter + kwargs_param = param + continue + + param_name = param.name + try: + arg_val = kwargs.pop(param_name) + except KeyError: + # We have no value for this parameter. It's fine though, + # if it has a default value, or it is an '*args'-like + # parameter, left alone by the processing of positional + # arguments. + if not partial and param.kind != _VAR_POSITIONAL \ + and not hasattr(param, 'default'): + raise TypeError('{arg!r} parameter lacking default value'. \ + format(arg=param_name)) from None + else: + arguments[param_name] = arg_val + + if kwargs: + if kwargs_param is not None: + # Process our '**kwargs'-like parameter + arguments[kwargs_param.name] = kwargs + else: + raise TypeError('too many keyword arguments') + + return self._bound_arguments_cls(self, arguments) + + def bind(self, *args, **kwargs): + '''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 self._bind(args, kwargs) + + def bind_partial(self, *args, **kwargs): + '''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 self._bind(args, kwargs, partial=True) + + def format(self, *, format_name=str, + format_default=repr, + format_annotation=formatannotation, + format_args=(lambda param: '*' + str(param)), + format_kwargs=(lambda param: '**' + str(param)), + + token_params_separator=', ', + token_kwonly_separator='*', + token_left_paren='(', + token_right_paren=')', + token_colon=':', + token_eq='=', + token_return_annotation=' -> '): + + '''Format signature to a string. + + Arguments (all optional): + + * format_name : A function to format names of parameters. Parameter + won't be rendered if ``None`` is returned. + * format_default : A function to format default values of parameters. + Default value won't be rendered if ``None`` is returned. + * format_annotation : A function to format parameter annotations. + Annotation won't be rendered if ``None`` is returned. + * format_args : A function to render ``*args`` like parameters. + Parameter won't be rendered if ``None`` is returned. + * format_kwargs : A function to render ``**kwargs`` like parameters. + Parameter won't be rendered if ``None`` is returned. + * token_params_separator : A separator for parameters. Set to + ', ' by default. + * token_kwonly_separator : A separator for arguments and + keyword-only arguments. Defaults to '*'. + * token_left_paren : Left signature parenthesis, defaults to '('. + * token_right_paren : Left signature parenthesis, defaults to ')'. + * token_colon : Separates parameter from its annotation, + defaults to ':'. + * token_eq : Separates parameter from its default value, set to + '=' by default. + * token_return_annotation : Function return annotation, defaults + to ' -> '. + ''' + + result = [] + render_kw_only_separator = True + for param in self.parameters.values(): + formatted = format_name(param.name) + if formatted is None: + continue + + # Add annotation and default value + if format_annotation: + try: + annotatation = param.annotation + except AttributeError: + pass + else: + formatted_anno = format_annotation(annotatation) + if formatted_anno is not None: + formatted = '{}{}{}'.format(formatted, + token_colon, + formatted_anno) + + if format_default: + try: + default = param.default + except AttributeError: + pass + else: + formatted_default = format_default(default) + if formatted_default is not None: + formatted = '{}{}{}'.format(formatted, + token_eq, + formatted_default) + + # Handle *args and **kwargs -like parameters + if param.kind == _VAR_POSITIONAL: + if format_args: + formatted = format_args(formatted) + if formatted is None: + continue + elif param.kind == _VAR_KEYWORD: + if format_kwargs: + formatted = format_kwargs(formatted) + if formatted is None: + continue + + if param.kind == _VAR_POSITIONAL: + # OK, we have an '*args'-like parameter, so we won't need + # a '*' to separate keyword-only arguments + render_kw_only_separator = False + elif param.kind == _KEYWORD_ONLY and render_kw_only_separator: + # We have a keyword-only parameter to render and we haven't + # rendered an '*args'-like parameter before, so add a '*' + # separator to the parameters list ("foo(arg1, *, arg2)" case) + result.append(token_kwonly_separator) + # This condition should be only triggered once, so + # reset the flag + render_kw_only_separator = False + + result.append(formatted) + + formatted = token_params_separator.join(result) + rendered = '{lp}{formatted}{rp}'.format(lp=token_left_paren, + rp=token_right_paren, + formatted=formatted) + + if format_annotation: + try: + ra = self.return_annotation + except AttributeError: + pass + else: + formatted_anno = format_annotation(ra) + if formatted_anno is not None: + rendered += '{}{}'.format(token_return_annotation, + formatted_anno) + + return rendered + + def __str__(self): + return self.format() \ No newline at end of file diff -r 5c0ee973a39a -r 8d0364c94b10 Lib/test/test_inspect.py --- a/Lib/test/test_inspect.py Sun Jun 03 14:40:32 2012 -0700 +++ b/Lib/test/test_inspect.py Fri Jun 15 13:23:22 2012 -0400 @@ -1170,13 +1170,798 @@ self.assertIn(name, str(state)) +class TestSignatureObject(unittest.TestCase): + @staticmethod + def signature(func): + sig = inspect.signature(func) + return (tuple((param.name, + getattr(param, 'default', ...), + getattr(param, 'annotation', ...), + param.kind) for param in sig.parameters.values()), + getattr(sig, 'return_annotation', ...)) + + def test_signature_on_noarg(self): + def test(): + pass + self.assertEqual(self.signature(test), ((), ...)) + + def test_signature_on_wargs(self): + def test(a, b:'foo') -> 123: + pass + self.assertEqual(self.signature(test), ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., 'foo', "positional_or_keyword")), + 123)) + + def test_signature_on_wkwonly(self): + def test(*, a:float, b:str) -> int: + pass + self.assertEqual(self.signature(test), ((('a', ..., float, "keyword_only"), + ('b', ..., str, "keyword_only")), + int)) + + def test_signature_on_complex_args(self): + def test(a, b:'foo'=10, *args:'bar', spam:'baz', ham=123, **kwargs:int): + pass + self.assertEqual(self.signature(test), ((('a', ..., ..., "positional_or_keyword"), + ('b', 10, 'foo', "positional_or_keyword"), + ('args', ..., 'bar', "var_positional"), + ('spam', ..., 'baz', "keyword_only"), + ('ham', 123, ..., "keyword_only"), + ('kwargs', ..., int, "var_keyword")), + ...)) + + def test_signature_on_builtin_function(self): + with self.assertRaisesRegexp(ValueError, 'is not supported by signature'): + inspect.signature(type) + with self.assertRaisesRegexp(ValueError, 'is not supported by signature'): + # support for 'wrapper_descriptor' + inspect.signature(type.__call__) + with self.assertRaisesRegexp(ValueError, 'is not supported by signature'): + # support for 'method-wrapper' + inspect.signature(min.__call__) + + def test_signature_on_non_function(self): + with self.assertRaisesRegexp(TypeError, 'is not a callable object'): + inspect.signature(42) + + def test_signature_implemented_param(self): + def test(spam): + pass + sig = inspect.signature(test) + self.assertTrue(sig.parameters['spam'].implemented) + sig.parameters['spam'].implemented = False + test.__signature__ = sig + + sig = inspect.signature(test) + self.assertFalse(sig.parameters['spam'].implemented) + + def test_signature_on_c_functions(self): + self.assertTrue('__signature__' in dir(os.chmod)) + cached = os.chmod.__signature__ + value = (1, 2, "3", 4) + try: + os.chmod.__signature__ = value + self.assertEqual(inspect.signature(os.chmod), value) + finally: + os.chmod.__signature__ = cached + + def test_signature_on_method(self): + class Test: + def foo(self, arg1, arg2=1) -> int: + pass + + meth = Test().foo + + self.assertEqual(self.signature(meth), ((('arg1', ..., ..., "positional_or_keyword"), + ('arg2', 1, ..., "positional_or_keyword")), + int)) + + def test_signature_on_classmethod(self): + class Test: + @classmethod + def foo(cls, arg1, *, arg2=1): + pass + + meth = Test().foo + self.assertEqual(self.signature(meth), ((('arg1', ..., ..., "positional_or_keyword"), + ('arg2', 1, ..., "keyword_only")), + ...)) + + meth = Test.foo + self.assertEqual(self.signature(meth), ((('arg1', ..., ..., "positional_or_keyword"), + ('arg2', 1, ..., "keyword_only")), + ...)) + + def test_signature_on_staticmethod(self): + class Test: + @staticmethod + def foo(cls, *, arg): + pass + + meth = Test().foo + self.assertEqual(self.signature(meth), ((('cls', ..., ..., "positional_or_keyword"), + ('arg', ..., ..., "keyword_only")), + ...)) + + meth = Test.foo + self.assertEqual(self.signature(meth), ((('cls', ..., ..., "positional_or_keyword"), + ('arg', ..., ..., "keyword_only")), + ...)) + + def test_signature_on_partial(self): + from functools import partial + + def test(): + pass + + self.assertEqual(self.signature(partial(test)), ((), ...)) + + with self.assertRaisesRegexp(ValueError, "has incorrect arguments"): + inspect.signature(partial(test, 1)) + + with self.assertRaisesRegexp(ValueError, "has incorrect arguments"): + inspect.signature(partial(test, a=1)) + + def test(a, b, *, c, d): + pass + + self.assertEqual(self.signature(partial(test)), + ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword"), + ('c', ..., ..., "keyword_only"), + ('d', ..., ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(partial(test, 1)), + ((('b', ..., ..., "positional_or_keyword"), + ('c', ..., ..., "keyword_only"), + ('d', ..., ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(partial(test, 1, c=2)), + ((('b', ..., ..., "positional_or_keyword"), + ('c', 2, ..., "keyword_only"), + ('d', ..., ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(partial(test, b=1, c=2)), + ((('a', ..., ..., "positional_or_keyword"), + ('b', 1, ..., "positional_or_keyword"), + ('c', 2, ..., "keyword_only"), + ('d', ..., ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(partial(test, 0, b=1, c=2)), + ((('b', 1, ..., "positional_or_keyword"), + ('c', 2, ..., "keyword_only"), + ('d', ..., ..., "keyword_only"),), + ...)) + + def test(a, *args, b, **kwargs): + pass + + self.assertEqual(self.signature(partial(test, 1)), + ((('args', ..., ..., "var_positional"), + ('b', ..., ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + self.assertEqual(self.signature(partial(test, 1, 2, 3)), + ((('args', ..., ..., "var_positional"), + ('b', ..., ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + + self.assertEqual(self.signature(partial(test, 1, 2, 3, test=True)), + ((('args', ..., ..., "var_positional"), + ('b', ..., ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + self.assertEqual(self.signature(partial(test, 1, 2, 3, test=1, b=0)), + ((('args', ..., ..., "var_positional"), + ('b', 0, ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + self.assertEqual(self.signature(partial(test, b=0)), + ((('a', ..., ..., "positional_or_keyword"), + ('args', ..., ..., "var_positional"), + ('b', 0, ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + self.assertEqual(self.signature(partial(test, b=0, test=1)), + ((('a', ..., ..., "positional_or_keyword"), + ('args', ..., ..., "var_positional"), + ('b', 0, ..., "keyword_only"), + ('kwargs', ..., ..., "var_keyword")), + ...)) + + def test(a, b, c:int) -> 42: + pass + + sig = test.__signature__ = inspect.signature(test) + sig.parameters['c'].implemented = False + + self.assertEqual(self.signature(partial(partial(test, 1))), + ((('b', ..., ..., "positional_or_keyword"), + ('c', ..., int, "positional_or_keyword")), + 42)) + + self.assertEqual(self.signature(partial(partial(test, 1), 2)), + ((('c', ..., int, "positional_or_keyword"),), + 42)) + + psig = inspect.signature(partial(partial(test, 1), 2)) + self.assertFalse(psig.parameters['c'].implemented) + + def foo(a): + return a + _foo = partial(partial(foo, a=10), a=20) + self.assertEqual(self.signature(_foo), + ((('a', 20, ..., "positional_or_keyword"),), + ...)) + # check that we don't have any side-effects in signature(), + # and the partial object is still functioning + self.assertEqual(_foo(), 20) + + def foo(a, b, c): + return a, b, c + _foo = partial(partial(foo, 1, b=20), b=30) + self.assertEqual(self.signature(_foo), + ((('b', 30, ..., "positional_or_keyword"), + ('c', ..., ..., "positional_or_keyword")), + ...)) + self.assertEqual(_foo(c=10), (1, 30, 10)) + _foo = partial(_foo, 2) # now 'b' has two values - positional and keyword + with self.assertRaisesRegexp(ValueError, "has incorrect arguments"): + inspect.signature(_foo) + + def foo(a, b, c, *, d): + return a, b, c, d + _foo = partial(partial(foo, d=20, c=20), b=10, d=30) + self.assertEqual(self.signature(_foo), + ((('a', ..., ..., "positional_or_keyword"), + ('b', 10, ..., "positional_or_keyword"), + ('c', 20, ..., "positional_or_keyword"), + ('d', 30, ..., "keyword_only")), + ...)) + ba = inspect.signature(_foo).bind(a=200, b=11) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (200, 11, 20, 30)) + + def test_signature_on_decorated(self): + import functools + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs) -> int: + return func(*args, **kwargs) + return wrapper + + class Foo: + @decorator + def bar(self, a, b): + pass + + self.assertEqual(self.signature(Foo.bar), + ((('self', ..., ..., "positional_or_keyword"), + ('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword")), + ...)) + + self.assertEqual(self.signature(Foo().bar), + ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword")), + ...)) + + # Test that we handle method wrappers correctly + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs) -> int: + return func(42, *args, **kwargs) + sig = inspect.signature(func) + sig.parameters.popitem(last=False) + wrapper.__signature__ = sig + return wrapper + + class Foo: + @decorator + def __call__(self, a, b): + pass + + self.assertEqual(self.signature(Foo.__call__), + ((('a', ..., ..., "positional_or_keyword"), + ('b', ..., ..., "positional_or_keyword")), + ...)) + + self.assertEqual(self.signature(Foo().__call__), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + def test_signature_on_class(self): + class C: + def __init__(self, a): + pass + + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + class CM(type): + def __call__(cls, a): + pass + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + class CM(type): + def __new__(mcls, name, bases, dct, *, foo=1): + return super().__new__(mcls, name, bases, dct) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + self.assertEqual(self.signature(CM), + ((('name', ..., ..., "positional_or_keyword"), + ('bases', ..., ..., "positional_or_keyword"), + ('dct', ..., ..., "positional_or_keyword"), + ('foo', 1, ..., "keyword_only")), + ...)) + + class CMM(type): + def __new__(mcls, name, bases, dct, *, foo=1): + return super().__new__(mcls, name, bases, dct) + def __call__(cls, nm, bs, dt): + return type(nm, bs, dt) + class CM(type, metaclass=CMM): + def __new__(mcls, name, bases, dct, *, bar=2): + return super().__new__(mcls, name, bases, dct) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(CMM), + ((('name', ..., ..., "positional_or_keyword"), + ('bases', ..., ..., "positional_or_keyword"), + ('dct', ..., ..., "positional_or_keyword"), + ('foo', 1, ..., "keyword_only")), + ...)) + + self.assertEqual(self.signature(CM), + ((('nm', ..., ..., "positional_or_keyword"), + ('bs', ..., ..., "positional_or_keyword"), + ('dt', ..., ..., "positional_or_keyword")), + ...)) + + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + class CM(type): + def __init__(cls, name, bases, dct, *, bar=2): + return super().__init__(name, bases, dct) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(CM), + ((('name', ..., ..., "positional_or_keyword"), + ('bases', ..., ..., "positional_or_keyword"), + ('dct', ..., ..., "positional_or_keyword"), + ('bar', 2, ..., "keyword_only")), + ...)) + + def test_signature_on_callable_objects(self): + class Foo: + def __call__(self, a): + pass + + self.assertEqual(self.signature(Foo()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + class Spam: + pass + with self.assertRaisesRegexp(TypeError, "is not a callable object"): + inspect.signature(Spam()) + + class Bar(Spam, Foo): + pass + + self.assertEqual(self.signature(Bar()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + class ToFail: + __call__ = type + with self.assertRaisesRegexp(ValueError, "is not supported by signature"): + inspect.signature(ToFail()) + + + class Wrapped: + pass + Wrapped.__wrapped__ = lambda a: None + self.assertEqual(self.signature(Wrapped), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + def test_signature_on_lambdas(self): + self.assertEqual(self.signature((lambda a=10: a)), + ((('a', 10, ..., "positional_or_keyword"),), + ...)) + + def test_signature_equality(self): + def foo(a, *, b:int) -> float: pass + self.assertNotEqual(inspect.signature(foo), 42) + + def bar(a, *, b:int) -> float: pass + self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + + def bar2(a, *, b:int) -> int: pass + self.assertNotEqual(inspect.signature(foo), inspect.signature(bar2)) + + def bar3(a, *, b:int): pass + self.assertNotEqual(inspect.signature(foo), inspect.signature(bar3)) + + def bar4(a, *, b:int=42) -> float: pass + self.assertNotEqual(inspect.signature(foo), inspect.signature(bar4)) + + def bar5(a, b:int) -> float: pass + self.assertNotEqual(inspect.signature(foo), inspect.signature(bar4)) + + def test_signature_unhashable(self): + def foo(a): pass + sig = inspect.signature(foo) + with self.assertRaisesRegexp(TypeError, 'unhashable type'): + hash(sig) + + +class TestParameterObject(unittest.TestCase): + def test_signature_parameter_object(self): + p = inspect.Parameter('foo', default=10, kind=inspect.Parameter.VAR_KEYWORD) + self.assertEqual(p.name, 'foo') + self.assertEqual(p.default, 10) + self.assertIs(getattr(p, 'annotation', ...), ...) + self.assertEqual(p.kind, inspect.Parameter.VAR_KEYWORD) + self.assertTrue(p.implemented) + + with self.assertRaisesRegexp(ValueError, 'invalid value'): + inspect.Parameter('foo', default=10, kind='123') + + def test_signature_parameter_custom_attr(self): + p = inspect.Parameter('foo', kind=inspect.Parameter.POSITIONAL_OR_KEYWORD) + p.custom_attr = 42 + self.assertEqual(getattr(p, 'custom_attr', None), 42) + + def test_signature_parameter_copy(self): + import copy + p = inspect.Parameter('foo', default=int, kind=inspect.Parameter.VAR_KEYWORD) + p.custom_attr = 42 + + p2 = copy.copy(p) + self.assertFalse(p is p2) + self.assertIs(getattr(p2, 'default', None), int) + self.assertEqual(getattr(p2, 'custom_attr', None), 42) + self.assertEqual(p2.kind, inspect.Parameter.VAR_KEYWORD) + p2.custom_attr = 10 + self.assertEqual(p2.custom_attr, 10) + self.assertEqual(p.custom_attr, 42) + + def test_signature_parameter_equality(self): + p = inspect.Parameter('foo', default=42, implemented=False, + kind=inspect.Parameter.VAR_POSITIONAL) + + self.assertEqual(p, p) + self.assertNotEqual(p, 42) + + self.assertEqual(p, inspect.Parameter('foo', default=42, implemented=False, + kind=inspect.Parameter.VAR_POSITIONAL)) + self.assertNotEqual(p, inspect.Parameter('foo', default=42, + kind=inspect.Parameter.VAR_POSITIONAL)) + + import copy + p2 = copy.copy(p) + self.assertEqual(p, p2) + p.custom_attr = 42 + self.assertEqual(p, p2) + + def test_signature_parameter_unhashable(self): + p = inspect.Parameter('foo', default=42, implemented=False, + kind=inspect.Parameter.VAR_POSITIONAL) + + with self.assertRaisesRegexp(TypeError, 'unhashable type'): + hash(p) + + +class TestSignatureBind(unittest.TestCase): + @staticmethod + def call(func, *args, **kwargs): + sig = inspect.signature(func) + ba = sig.bind(*args, **kwargs) + return func(*ba.args, **ba.kwargs) + + def test_signature_bind_empty(self): + def test(): + return 42 + + self.assertEqual(self.call(test), 42) + with self.assertRaisesRegexp(TypeError, 'too many positional arguments'): + self.call(test, 1) + with self.assertRaisesRegexp(TypeError, 'too many positional arguments'): + self.call(test, 1, spam=10) + with self.assertRaisesRegexp(TypeError, 'too many keyword arguments'): + self.call(test, spam=1) + + def test_signature_bind_var(self): + def test(*args, **kwargs): + return args, kwargs + + self.assertEqual(self.call(test), ((), {})) + self.assertEqual(self.call(test, 1), ((1,), {})) + self.assertEqual(self.call(test, 1, 2), ((1, 2), {})) + self.assertEqual(self.call(test, foo='bar'), ((), {'foo': 'bar'})) + self.assertEqual(self.call(test, 1, foo='bar'), ((1,), {'foo': 'bar'})) + self.assertEqual(self.call(test, 1, 2, foo='bar'), ((1, 2), {'foo': 'bar'})) + self.assertEqual(self.call(test, args=10), ((), {'args': 10})) + + def test_signature_bind_just_args(self): + def test(a, b, c): + return a, b, c + + self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3)) + + with self.assertRaisesRegexp(TypeError, 'too many positional arguments'): + self.call(test, 1, 2, 3, 4) + + with self.assertRaisesRegexp(TypeError, "'b' parameter lacking default"): + self.call(test, 1) + + with self.assertRaisesRegexp(TypeError, "'a' parameter lacking default"): + self.call(test) + + def test(a, b, c=10): + return a, b, c + self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3)) + self.assertEqual(self.call(test, 1, 2), (1, 2, 10)) + + def test_signature_bind_varargs_order(self): + def test(*args): + return args + + self.assertEqual(self.call(test), ()) + self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3)) + + def test_signature_bind_args_and_varargs(self): + def test(a, b, c=3, *args): + return a, b, c, args + + self.assertEqual(self.call(test, 1, 2, 3, 4, 5), (1, 2, 3, (4, 5))) + self.assertEqual(self.call(test, 1, 2), (1, 2, 3, ())) + self.assertEqual(self.call(test, b=1, a=2), (2, 1, 3, ())) + self.assertEqual(self.call(test, 1, b=2), (1, 2, 3, ())) + + with self.assertRaisesRegexp(TypeError, "multiple values for argument 'c'"): + self.call(test, 1, 2, 3, c=4) + + def test_signature_bind_just_kwargs(self): + def test(**kwargs): + return kwargs + + self.assertEqual(self.call(test), {}) + self.assertEqual(self.call(test, foo='bar', spam='ham'), + {'foo': 'bar', 'spam': 'ham'}) + + def test_signature_bind_args_and_kwargs(self): + def test(a, b, c=3, **kwargs): + return a, b, c, kwargs + + self.assertEqual(self.call(test, 1, 2), (1, 2, 3, {})) + self.assertEqual(self.call(test, 1, 2, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, b=2, a=1, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, a=1, b=2, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, 1, b=2, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, 1, b=2, c=4, foo='bar', spam='ham'), + (1, 2, 4, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, 1, 2, 4, foo='bar'), + (1, 2, 4, {'foo': 'bar'})) + self.assertEqual(self.call(test, c=5, a=4, b=3), + (4, 3, 5, {})) + + def test_signature_bind_kwonly(self): + def test(*, foo): + return foo + with self.assertRaisesRegexp(TypeError, 'too many positional arguments'): + self.call(test, 1) + self.assertEqual(self.call(test, foo=1), 1) + + def test(foo, *, bar): + return foo, bar + self.assertEqual(self.call(test, 1, bar=2), (1, 2)) + self.assertEqual(self.call(test, bar=2, foo=1), (1, 2)) + + with self.assertRaisesRegexp(TypeError, 'too many keyword arguments'): + self.call(test, bar=2, foo=1, spam=10) + + with self.assertRaisesRegexp(TypeError, 'too many positional arguments'): + self.call(test, 1, 2) + + with self.assertRaisesRegexp(TypeError, 'too many positional arguments'): + self.call(test, 1, 2, bar=2) + + with self.assertRaisesRegexp(TypeError, 'too many keyword arguments'): + self.call(test, 1, bar=2, spam='ham') + + with self.assertRaisesRegexp(TypeError, "'bar' parameter lacking default value"): + self.call(test, 1) + + def test(foo, *, bar, **bin): + return foo, bar, bin + self.assertEqual(self.call(test, 1, bar=2), (1, 2, {})) + self.assertEqual(self.call(test, foo=1, bar=2), (1, 2, {})) + self.assertEqual(self.call(test, 1, bar=2, spam='ham'), (1, 2, {'spam': 'ham'})) + self.assertEqual(self.call(test, spam='ham', foo=1, bar=2), (1, 2, {'spam': 'ham'})) + with self.assertRaisesRegexp(TypeError, "'foo' parameter lacking default value"): + self.call(test, spam='ham', bar=2) + self.assertEqual(self.call(test, 1, bar=2, bin=1, spam=10), + (1, 2, {'bin': 1, 'spam': 10})) + + def test_signature_bind_arguments(self): + def test(a, *args, b, z=100, **kwargs): + pass + sig = inspect.signature(test) + ba = sig.bind(10, 20, b=30, c=40, args=50, kwargs=60) + # we won't have 'z' argument in the bound arguments object, as we didn't + # pass it to the 'bind' + self.assertEqual(tuple(ba.arguments.items()), + (('a', 10), ('args', (20,)), ('b', 30), + ('kwargs', {'c': 40, 'args': 50, 'kwargs': 60}))) + self.assertEqual(ba.args, (10, 20)) + self.assertEqual(ba.kwargs, {'b': 30, 'c': 40, 'args': 50, 'kwargs': 60}) + + def test_signature_bind_positional_only(self): + def test(a_po, b_po, c_po=3, foo=42, *, bar=50, **kwargs): + return a_po, b_po, c_po, foo, bar, kwargs + + sig = test.__signature__ = inspect.signature(test) + sig.parameters['a_po'].kind = inspect.Parameter.POSITIONAL_ONLY + sig.parameters['b_po'].kind = inspect.Parameter.POSITIONAL_ONLY + sig.parameters['c_po'].kind = inspect.Parameter.POSITIONAL_ONLY + + self.assertEqual(self.call(test, 1, 2, 4, 5, bar=6), + (1, 2, 4, 5, 6, {})) + + with self.assertRaisesRegexp(TypeError, "parameter is positional only"): + self.call(test, 1, 2, c_po=4) + + with self.assertRaisesRegexp(TypeError, "parameter is positional only"): + self.call(test, a_po=1, b_po=2) + + +class TestSignatureFormat(unittest.TestCase): + def test_signature_str(self): + def foo(a:int=1, *, b, c=None, **kwargs) -> 42: + pass + self.assertEqual(str(inspect.signature(foo)), + '(a:int=1, *, b, c=None, **kwargs) -> 42') + + def test_signature_format(self): + def foo(a:int=1, *, b, c=None, **kwargs) -> 42: + pass + sig = inspect.signature(foo) + + self.assertEqual(str(sig.format(format_name=(lambda p: None if p=='a' else p))), + '(*, b, c=None, **kwargs) -> 42') + + self.assertEqual(str(sig.format(format_default=False)), + '(a:int, *, b, c, **kwargs) -> 42') + + self.assertEqual(str(sig.format(format_annotation=False)), + '(a=1, *, b, c=None, **kwargs)') + + self.assertEqual(str(sig.format(format_annotation=False, + format_default=False)), + '(a, *, b, c, **kwargs)') + + self.assertEqual(str(sig.format(format_annotation=(lambda a: repr(a) + '!'), + format_default=False)), + "(a:!, *, b, c, **kwargs) -> 42!") + + self.assertEqual(str(sig.format(format_default=(lambda d: '!'+str(d) + '!'))), + '(a:int=!1!, *, b, c=!None!, **kwargs) -> 42') + + self.assertEqual(str(sig.format(format_kwargs=False)), + '(a:int=1, *, b, c=None, kwargs) -> 42') + + self.assertEqual(str(sig.format(token_params_separator=';', + token_colon='@', + token_eq='!', + token_return_annotation='#', + token_left_paren='[', + token_right_paren=']', + token_kwonly_separator='$')), + '[a@int!1;$;b;c!None;**kwargs]#42') + + self.assertEqual(str(sig.format(format_name=repr)), + "('a':int=1, *, 'b', 'c'=None, **'kwargs') -> 42") + + + def foo(*args, **kwargs): + pass + sig = inspect.signature(foo) + self.assertEqual(str(sig.format(format_args=False, + format_kwargs=False)), + "(args, kwargs)") + + self.assertEqual(str(sig.format(format_args=(lambda a: '!'+a+'!'), + format_kwargs=(lambda a: '@'+a+'@'))), + "(!args!, @kwargs@)") + + self.assertEqual(str(sig.format(format_args=(lambda a: None), + format_kwargs=(lambda a: None))), + "()") + + def foo(): + pass + sig = inspect.signature(foo) + self.assertEqual(str(sig.format()), "()") + + def test_signature_format_positional_only(self): + def test(a_po, *, b, **kwargs): + return a_po, kwargs + + sig = test.__signature__ = inspect.signature(test) + sig.parameters['a_po'].kind = inspect.Parameter.POSITIONAL_ONLY + self.assertEqual(str(inspect.signature(test)), '(a_po, *, b, **kwargs)') + + +class TestBoundArguments(unittest.TestCase): + def test_signature_bound_arguments_unhashable(self): + def foo(a): pass + ba = inspect.signature(foo).bind(1) + + with self.assertRaisesRegexp(TypeError, 'unhashable type'): + hash(ba) + + def test_signature_bound_arguments_equality(self): + def foo(a): pass + ba = inspect.signature(foo).bind(1) + self.assertEqual(ba, ba) + + ba2 = inspect.signature(foo).bind(1) + self.assertEqual(ba, ba2) + + ba3 = inspect.signature(foo).bind(2) + self.assertNotEqual(ba, ba3) + ba3.arguments['a'] = 1 + self.assertEqual(ba, ba3) + + def bar(b): pass + ba4 = inspect.signature(bar).bind(1) + self.assertNotEqual(ba, ba4) + + def test_main(): run_unittest( TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases, TestInterpreterStack, TestClassesAndFunctions, TestPredicates, TestGetcallargsFunctions, TestGetcallargsMethods, TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState, - TestNoEOL + TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject, + TestSignatureFormat, TestBoundArguments ) if __name__ == "__main__": diff -r 5c0ee973a39a -r 8d0364c94b10 Lib/test/test_sys.py --- a/Lib/test/test_sys.py Sun Jun 03 14:40:32 2012 -0700 +++ b/Lib/test/test_sys.py Fri Jun 15 13:23:22 2012 -0400 @@ -657,7 +657,7 @@ # buffer # XXX # builtin_function_or_method - check(len, size(h + '3P')) + check(len, size(h + '4P')) # bytearray samples = [b'', b'u'*100000] for sample in samples: diff -r 5c0ee973a39a -r 8d0364c94b10 Objects/methodobject.c --- a/Objects/methodobject.c Sun Jun 03 14:40:32 2012 -0700 +++ b/Objects/methodobject.c Fri Jun 15 13:23:22 2012 -0400 @@ -33,6 +33,7 @@ op->m_self = self; Py_XINCREF(module); op->m_module = module; + op->m_signature = NULL; _PyObject_GC_TRACK(op); return (PyObject *)op; } @@ -124,6 +125,7 @@ _PyObject_GC_UNTRACK(m); Py_XDECREF(m->m_self); Py_XDECREF(m->m_module); + Py_XDECREF(m->m_signature); if (numfree < PyCFunction_MAXFREELIST) { m->m_self = (PyObject *)free_list; free_list = m; @@ -191,6 +193,7 @@ { Py_VISIT(m->m_self); Py_VISIT(m->m_module); + Py_VISIT(m->m_signature); return 0; } @@ -218,6 +221,7 @@ static PyMemberDef meth_members[] = { {"__module__", T_OBJECT, OFF(m_module), PY_WRITE_RESTRICTED}, + {"__signature__", T_OBJECT, OFF(m_signature), 0}, {NULL} };