diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 0f7dc9ae6b82f5..dbf58c9f258bea 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1163,13 +1163,13 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, try: # In some cases fetching a signature is not possible. # But, we surely should not fail in this case. - text_sig = str(inspect.signature( - cls, - annotation_format=annotationlib.Format.FORWARDREF, - )).replace(' -> None', '') + signatures = inspect.signatures(cls, + annotation_format=annotationlib.Format.FORWARDREF) + doc = '\n'.join(cls.__name__ + str(sig).replace(' -> None', '') + for sig in signatures) except (TypeError, ValueError): - text_sig = '' - cls.__doc__ = (cls.__name__ + text_sig) + doc = cls.__name__ + cls.__doc__ = doc if match_args: # I could probably compute this once. diff --git a/Lib/idlelib/calltip.py b/Lib/idlelib/calltip.py index 40bc5a0ad798fe..74abd96b2a5141 100644 --- a/Lib/idlelib/calltip.py +++ b/Lib/idlelib/calltip.py @@ -170,20 +170,25 @@ def get_argspec(ob): # Initialize argspec and wrap it to get lines. try: - argspec = str(inspect.signature(fob)) + signatures = inspect.signatures(fob) except Exception as err: msg = str(err) if msg.startswith(_invalid_method): return _invalid_method else: - argspec = '' + signatures = [] - if isinstance(fob, type) and argspec == '()': + lines = [] + for sig in signatures: + line = str(sig) + if len(line) > _MAX_COLS: + lines.extend(textwrap.wrap(line, _MAX_COLS, subsequent_indent=_INDENT)) + else: + lines.append(line) + if isinstance(fob, type) and lines == ['()']: # If fob has no argument, use default callable argspec. - argspec = _default_callable_argspec + lines = [_default_callable_argspec] - lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT) - if len(argspec) > _MAX_COLS else [argspec] if argspec else []) # Augment lines from docstring, if any, and join to get argspec. doc = inspect.getdoc(ob) diff --git a/Lib/idlelib/idle_test/test_calltip.py b/Lib/idlelib/idle_test/test_calltip.py index 28c196a42672fc..007800bca1cb9f 100644 --- a/Lib/idlelib/idle_test/test_calltip.py +++ b/Lib/idlelib/idle_test/test_calltip.py @@ -3,6 +3,7 @@ from idlelib import calltip import unittest from unittest.mock import Mock +import builtins import textwrap import types import re @@ -151,17 +152,48 @@ def f(): pass "Signature information for builtins requires docstrings") def test_multiline_docstring(self): # Test fewer lines than max. + def range(): + """range(stop) -> range object + range(start, stop[, step]) -> range object + + Return an object that produces a sequence of integers from start + (inclusive) to stop (exclusive) by step. + """ + range.__text_signature__ = '' self.assertEqual(get_spec(range), "range(stop) -> range object\n" "range(start, stop[, step]) -> range object") + self.assertEqual(get_spec(builtins.range), + "(stop, /)\n" + "(start, stop, step=1, /)\n" + "Create a range object.") # Test max lines + def bytes(): + """bytes(iterable_of_ints) -> bytes + bytes(string, encoding[, errors]) -> bytes + bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer + bytes(int) -> bytes object of size given by the parameter initialized with null bytes + bytes() -> empty bytes object + + Construct an immutable array of bytes from: + - an iterable yielding integers in range(256) + - a text string encoded using the specified encoding + - any object implementing the buffer API. + - an integer""" + bytes.__text_signature__ = '' self.assertEqual(get_spec(bytes), '''\ bytes(iterable_of_ints) -> bytes bytes(string, encoding[, errors]) -> bytes bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer bytes(int) -> bytes object of size given by the parameter initialized with null bytes bytes() -> empty bytes object''') + self.assertEqual(get_spec(builtins.bytes), '''\ +() +(source) +(source, encoding) +(source, encoding, errors) +Construct an immutable array of bytes.''') def test_multiline_docstring_2(self): # Test more than max lines diff --git a/Lib/inspect.py b/Lib/inspect.py index fcfe3b191ab503..b35ad77f0ea997 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -67,6 +67,7 @@ "GEN_CREATED", "GEN_RUNNING", "GEN_SUSPENDED", + "MultiSignature", "Parameter", "Signature", "TPFLAGS_IS_ABSTRACT", @@ -135,6 +136,7 @@ "istraceback", "markcoroutinefunction", "signature", + "signatures", "stack", "trace", "unwrap", @@ -161,7 +163,7 @@ import functools import builtins from keyword import iskeyword -from operator import attrgetter +from operator import attrgetter, or_ from collections import namedtuple, OrderedDict from weakref import ref as make_weakref @@ -1919,28 +1921,13 @@ def _signature_get_user_defined_method(cls, method_name): return meth -def _signature_get_partial(wrapped_sig, partial, extra_args=()): - """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()) - - partial_args = partial.args or () - partial_keywords = partial.keywords or {} - - if extra_args: - partial_args = extra_args + partial_args - - try: - ba = wrapped_sig.bind_partial(*partial_args, **partial_keywords) - except TypeError as ex: - msg = 'partial object {!r} has incorrect arguments'.format(partial) - raise ValueError(msg) from ex - +def _signature_partial(sig, args, kwargs): + if isinstance(sig, MultiSignature): + return _multisignature_partial(sig, args, kwargs) + ba = sig.bind_partial(*args, **kwargs) + old_params = sig.parameters + new_params = OrderedDict(old_params) transform_to_kwonly = False for param_name, param in old_params.items(): try: @@ -1960,7 +1947,7 @@ def _signature_get_partial(wrapped_sig, partial, extra_args=()): continue if param.kind is _POSITIONAL_OR_KEYWORD: - if param_name in partial_keywords: + if param_name in kwargs: # This means that this parameter, and all parameters # after it should be keyword-only (and var-positional # should be removed). Here's why. Consider the following @@ -2007,7 +1994,43 @@ def _signature_get_partial(wrapped_sig, partial, extra_args=()): elif param.kind is _VAR_POSITIONAL: new_params.pop(param.name) - return wrapped_sig.replace(parameters=new_params.values()) + return sig.replace(parameters=new_params.values()) + + +def _multisignature_partial(sig, args, kwargs, partial=_signature_partial): + last_exc = None + signatures = [] + for s in sig: + try: + signatures.append(partial(s, args, kwargs)) + except TypeError as e: + last_exc = e + if not signatures: + raise last_exc + if len(signatures) == 1: + return signatures[0] + return sig.__class__(signatures) + + +def _signature_partialmethod(sig, args, kwargs): + if isinstance(sig, MultiSignature): + return _multisignature_partial(sig, args, kwargs, _signature_partialmethod) + + new_sig = _signature_partial(sig, (None, *args), kwargs) + first_wrapped_param = tuple(sig.parameters.values())[0] + if first_wrapped_param.kind is Parameter.VAR_POSITIONAL: + # First argument of the wrapped callable is `*args`, as in + # `partialmethod(lambda *args)`. + return new_sig + sig_params = tuple(new_sig.parameters.values()) + assert not sig_params or first_wrapped_param is not sig_params[0] + # If there were placeholders set, + # first param is transformed to positional only + if functools.Placeholder in args: + first_wrapped_param = first_wrapped_param.replace( + kind=Parameter.POSITIONAL_ONLY) + new_params = (first_wrapped_param, *sig_params) + return new_sig.replace(parameters=new_params) def _signature_bound_method(sig): @@ -2015,26 +2038,11 @@ def _signature_bound_method(sig): functions to bound methods. """ - params = tuple(sig.parameters.values()) - - if not params or params[0].kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + try: + return _signature_partial(sig, (None,), {}) + except TypeError: raise ValueError('invalid method signature') - kind = params[0].kind - if kind in (_POSITIONAL_OR_KEYWORD, _POSITIONAL_ONLY): - # Drop first parameter: - # '(p1, p2[, ...])' -> '(p2[, ...])' - params = params[1:] - else: - if kind is not _VAR_POSITIONAL: - # Unless we add a new parameter type we never - # get here - raise ValueError('invalid argument type') - # It's a var-positional parameter. - # Do nothing. '(*args[, ...])' -> '(*args[, ...])' - - return sig.replace(parameters=params) - def _signature_is_builtin(obj): """Private helper to test if `obj` is a callable that might @@ -2075,19 +2083,20 @@ def _signature_is_functionlike(obj): (isinstance(annotations, (dict)) or annotations is None) ) -def _signature_strip_non_python_syntax(signature): +def _signature_split(signature): """ Private helper function. Takes a signature in Argument Clinic's extended signature format. - Returns a tuple of two things: + Yields pairs: * that signature re-rendered in standard Python syntax, and * the index of the "self" parameter (generally 0), or None if the function does not have a "self" parameter. """ if not signature: - return signature, None + yield (signature, None) + return self_parameter = None @@ -2100,7 +2109,8 @@ def _signature_strip_non_python_syntax(signature): current_parameter = 0 OP = token.OP - ERRORTOKEN = token.ERRORTOKEN + NEWLINE = token.NEWLINE + NL = token.NL # token stream always starts with ENCODING token, skip it t = next(token_stream) @@ -2108,30 +2118,38 @@ def _signature_strip_non_python_syntax(signature): for t in token_stream: type, string = t.type, t.string - - if type == OP: + if type == NEWLINE: + yield (''.join(text), self_parameter) + text.clear() + self_parameter = None + current_parameter = 0 + continue + elif type == NL: + continue + elif type == OP: if string == ',': current_parameter += 1 - - if (type == OP) and (string == '$'): - assert self_parameter is None - self_parameter = current_parameter - continue - + string = ', ' + elif string == '$' and self_parameter is None: + self_parameter = current_parameter + continue add(string) - if (string == ','): - add(' ') - clean_signature = ''.join(text).strip().replace("\n", "") - return clean_signature, self_parameter - def _signature_fromstr(cls, obj, s, skip_bound_arg=True): """Private helper to parse content of '__text_signature__' and return a Signature based on it. """ - Parameter = cls._parameter_cls + signatures = [_signature_fromstr1(cls, obj, + clean_signature, self_parameter, + skip_bound_arg) + for clean_signature, self_parameter in _signature_split(s)] + if len(signatures) == 1: + return signatures[0] + else: + return MultiSignature(signatures) - clean_signature, self_parameter = _signature_strip_non_python_syntax(s) +def _signature_fromstr1(cls, obj, clean_signature, self_parameter, skip_bound_arg): + Parameter = cls._parameter_cls program = "def foo" + clean_signature + ": pass" @@ -2467,28 +2485,21 @@ def _signature_from_callable(obj, *, # automatically (as for boundmethods) wrapped_sig = _get_signature_of(partialmethod.func) - - sig = _signature_get_partial(wrapped_sig, partialmethod, (None,)) - first_wrapped_param = tuple(wrapped_sig.parameters.values())[0] - if first_wrapped_param.kind is Parameter.VAR_POSITIONAL: - # First argument of the wrapped callable is `*args`, as in - # `partialmethod(lambda *args)`. - return sig - else: - sig_params = tuple(sig.parameters.values()) - assert (not sig_params or - first_wrapped_param is not sig_params[0]) - # If there were placeholders set, - # first param is transformed to positional only - if partialmethod.args.count(functools.Placeholder): - first_wrapped_param = first_wrapped_param.replace( - kind=Parameter.POSITIONAL_ONLY) - new_params = (first_wrapped_param,) + sig_params - return sig.replace(parameters=new_params) + try: + return _signature_partialmethod(wrapped_sig, + partialmethod.args, + partialmethod.keywords) + except TypeError as ex: + msg = f'partial object {partialmethod!r} has incorrect arguments' + raise ValueError(msg) from ex if isinstance(obj, functools.partial): wrapped_sig = _get_signature_of(obj.func) - return _signature_get_partial(wrapped_sig, obj) + try: + return _signature_partial(wrapped_sig, obj.args, obj.keywords) + except TypeError as ex: + msg = f'partial object {obj!r} has incorrect arguments' + raise ValueError(msg) from ex if isfunction(obj) or _signature_is_functionlike(obj): # If it's a pure Python function, or an object that is duck type @@ -2730,6 +2741,8 @@ def replace(self, *, name=_void, kind=_void, return type(self)(name, kind, default=default, annotation=annotation) + __replace__ = replace + def __str__(self): return self._format() @@ -2756,8 +2769,6 @@ def _format(self, *, quote_annotation_strings=True): return formatted - __replace__ = replace - def __repr__(self): return '<{} "{}">'.format(self.__class__.__name__, self) @@ -3047,7 +3058,7 @@ def __hash__(self): def __eq__(self, other): if self is other: return True - if not isinstance(other, Signature): + if not isinstance(other, Signature) or isinstance(other, MultiSignature): return NotImplemented return self._hash_basis() == other._hash_basis() @@ -3282,13 +3293,141 @@ def format(self, *, max_width=None, quote_annotation_strings=True): return rendered -def signature(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False, - annotation_format=Format.VALUE): +class MultiSignature(Signature): + __slots__ = ('_signatures',) + + def __init__(self, signatures): + signatures = tuple(signatures) + if not signatures: + raise ValueError('No signatures') + self._signatures = signatures + + @staticmethod + def from_callable(obj, *, + follow_wrapped=True, globals=None, locals=None, + eval_str=False, annotation_format=Format.VALUE): + """Constructs MultiSignature for the given callable object.""" + signature = Signature.from_callable(obj, follow_wrapped=follow_wrapped, + globals=globals, locals=locals, + eval_str=eval_str, + annotation_format=annotation_format) + if not isinstance(signature, MultiSignature): + signature = MultiSignature((signature,)) + return signature + + @property + def parameters(self): + try: + return self._parameters + except AttributeError: + pass + params = {} + for s in self._signatures: + params.update(s.parameters) + self._parameters = types.MappingProxyType(params) + return self._parameters + + @property + def return_annotation(self): + try: + return self._return_annotation + except AttributeError: + pass + return_annotations = [] + for s in self._signatures: + ann = s.return_annotation + if ann != _empty: + if ann is None: + ann = type(ann) + elif isinstance(ann, str): + from typing import ForwardRef + ann = ForwardRef(ann) + return_annotations.append(ann) + self._return_annotation = (functools.reduce(or_, return_annotations) + if return_annotations else _empty) + return self._return_annotation + + def replace(self): + raise NotImplementedError + + __replace__ = replace + + def __iter__(self): + return iter(self._signatures) + + def __hash__(self): + if len(self._signatures) == 1: + return hash(self._signatures[0]) + return hash(self._signatures) + + def __eq__(self, other): + if self is other: + return True + if isinstance(other, MultiSignature): + return self._signatures == other._signatures + if isinstance(other, Signature): + return len(self._signatures) == 1 and self._signatures[0] == other + return NotImplemented + + def bind(self, /, *args, **kwargs): + last_exc = None + for s in self._signatures: + try: + return s.bind(*args, **kwargs) + except TypeError as e: + last_exc = e + raise last_exc + + def bind_partial(self, /, *args, **kwargs): + last_exc = None + bas = [] + for s in self._signatures: + try: + bas.append(s.bind_partial(*args, **kwargs)) + except TypeError as e: + last_exc = e + if not bas: + raise last_exc + if len(bas) == 1: + return bas[0] + arguments = bas[0].arguments + for ba in bas: + if ba.arguments != arguments: + raise TypeError('ambiguous binding') + sig = (self if len(bas) == len(self._signatures) else + self.__class__([ba.signature for ba in bas])) + return BoundArguments(sig, arguments) + + def __reduce__(self): + return type(self), (self._signatures,) + + def __repr__(self): + return '<%s %s>' % (self.__class__.__name__, + '|'.join(map(str, self._signatures))) + + def __str__(self): + return '\n'.join(map(str, self._signatures)) + + def format(self, *, max_width=None): + return '\n'.join(sig.format(max_width=max_width) + for sig in self._signatures) + + +def signature(obj, *, follow_wrapped=True, globals=None, locals=None, + eval_str=False, annotation_format=Format.VALUE): """Get a signature object for the passed callable.""" return Signature.from_callable(obj, follow_wrapped=follow_wrapped, globals=globals, locals=locals, eval_str=eval_str, annotation_format=annotation_format) +def signatures(obj, *, follow_wrapped=True, globals=None, locals=None, + eval_str=False, annotation_format=Format.VALUE): + """Get a multi-signature object for the passed callable.""" + return MultiSignature.from_callable(obj, follow_wrapped=follow_wrapped, + globals=globals, locals=locals, + eval_str=eval_str, + annotation_format=annotation_format) + class BufferFlags(enum.IntFlag): SIMPLE = 0x0 diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 1839b88fec28b1..28bff94d3de5d1 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -212,25 +212,29 @@ def splitdoc(doc): return lines[0], '\n'.join(lines[2:]) return '', '\n'.join(lines) -def _getargspec(object): +def _getargspecs(object): try: - signature = inspect.signature(object, annotation_format=Format.STRING) - if signature: + signatures = inspect.signatures(object, annotation_format=Format.STRING) + if signatures: name = getattr(object, '__name__', '') # function are always single-line and should not be formatted max_width = (80 - len(name)) if name != '' else None - return signature.format(max_width=max_width, quote_annotation_strings=False) + return [sig.format(max_width=max_width, quote_annotation_strings=False) + for sig in signatures] + return None except (ValueError, TypeError): - argspec = getattr(object, '__text_signature__', None) - if argspec: - if argspec[:2] == '($': - argspec = '(' + argspec[2:] + pass + text_signature = getattr(object, '__text_signature__', None) + if text_signature: + argspecs = [] + for argspec, _ in inspect._signature_split(text_signature): if getattr(object, '__self__', None) is not None: # Strip the bound argument. m = re.match(r'\(\w+(?:(?=\))|,\s*(?:/(?:(?=\))|,\s*))?)', argspec) if m: argspec = '(' + argspec[m.end():] - return argspec + argspecs.append(argspec) + return argspecs return None def classname(object, modname): @@ -1084,9 +1088,10 @@ def spilldata(msg, attrs, predicate): title = title + '(%s)' % ', '.join(parents) decl = '' - argspec = _getargspec(object) - if argspec and argspec != '()': - decl = name + self.escape(argspec) + '\n\n' + argspecs = _getargspecs(object) + if argspecs and argspecs != ['()']: + decl = '\n'.join(name + self.escape(argspec) + for argspec in argspecs) + '\n\n' doc = getdoc(object) if decl: @@ -1160,29 +1165,32 @@ def docroutine(self, object, name=None, mod=None, reallink = realname title = '%s = %s' % ( anchor, name, reallink) - argspec = None + argspecs = None if inspect.isroutine(object): - argspec = _getargspec(object) - if argspec and realname == '': + argspecs = _getargspecs(object) + if argspecs and realname == '': title = '%s lambda ' % name # XXX lambda's won't usually have func_annotations['return'] # since the syntax doesn't support but it is possible. # So removing parentheses isn't truly safe. if not object.__annotations__: - argspec = argspec[1:-1] # remove parentheses - if not argspec: - argspec = '(...)' + argspecs = [argspec[1:-1] for argspec in argspecs] # remove parentheses + if not argspecs: + argspecs = ['(...)'] - decl = asyncqualifier + title + self.escape(argspec) + (note and - self.grey('%s' % note)) + decl = ''.join( + '
%s%s%s%s
' % ( + asyncqualifier, title, self.escape(argspec), + (note and self.grey('%s' % note))) + for argspec in argspecs) if skipdocs: - return '
%s
\n' % decl + return '
%s
\n' % decl else: doc = self.markup( getdoc(object), self.preformat, funcs, classes, methods) doc = doc and '
%s
' % doc - return '
%s
%s
\n' % (decl, doc) + return '
%s%s
\n' % (decl, doc) def docdata(self, object, name=None, mod=None, cl=None, *ignored): """Produce html documentation for a data descriptor.""" @@ -1416,9 +1424,11 @@ def makename(c, m=object.__module__): contents = [] push = contents.append - argspec = _getargspec(object) - if argspec and argspec != '()': - push(name + argspec + '\n') + argspecs = _getargspecs(object) + if argspecs and argspecs != ['()']: + for argspec in argspecs: + push(name + argspec) + push('') doc = getdoc(object) if doc: @@ -1603,20 +1613,21 @@ def docroutine(self, object, name=None, mod=None, cl=None, homecls=None): if note.startswith(' from '): note = '' title = self.bold(name) + ' = ' + realname - argspec = None + argspecs = None if inspect.isroutine(object): - argspec = _getargspec(object) - if argspec and realname == '': + argspecs = _getargspecs(object) + if argspecs and realname == '': title = self.bold(name) + ' lambda ' # XXX lambda's won't usually have func_annotations['return'] # since the syntax doesn't support but it is possible. # So removing parentheses isn't truly safe. if not object.__annotations__: - argspec = argspec[1:-1] - if not argspec: - argspec = '(...)' - decl = asyncqualifier + title + argspec + note + argspecs = [argspec[1:-1] for argspec in argspecs] # remove parentheses + if not argspecs: + argspecs = ['(...)'] + decl = '\n'.join(asyncqualifier + title + argspec + note + for argspec in argspecs) if skipdocs: return decl + '\n' diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 82d9916e38d341..ce872d40c1f163 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1974,9 +1974,13 @@ def test_compare_bytes_to_bytearray(self): @test.support.requires_docstrings def test_doc(self): self.assertIsNotNone(bytearray.__doc__) - self.assertTrue(bytearray.__doc__.startswith("bytearray("), bytearray.__doc__) + self.assertTrue(bytearray.__doc__.startswith( + "Construct a mutable bytearray object.\n\n"), + bytearray.__doc__) self.assertIsNotNone(bytes.__doc__) - self.assertTrue(bytes.__doc__.startswith("bytes("), bytes.__doc__) + self.assertTrue(bytes.__doc__.startswith( + "Construct an immutable array of bytes.\n\n"), + bytes.__doc__) def test_from_bytearray(self): sample = bytes(b"Hello world\n\x80\x81\xfe\xff") diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 869a043211b0a1..a1918df310e420 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -2369,7 +2369,7 @@ def __init__(self, x: X, num: int) -> None: ... self.assertDocStrEqual(ns['C'].__doc__, "C(x:X,num:int)") - def test_docstring_with_no_signature(self): + def test_docstring_with_multi_signature(self): # See https://github.com/python/cpython/issues/103449 class Meta(type): __call__ = dict @@ -2380,6 +2380,23 @@ class Base(metaclass=Meta): class C(Base): pass + self.assertDocStrEqual(C.__doc__, + "C(**kwargs)\n" + "C(mapping_or_iterable, /, **kwargs)") + + def test_docstring_with_no_signature(self): + # See https://github.com/python/cpython/issues/103449 + class Meta(type): + def __call__(self, x): + pass + __call__.__text_signature__ = '' + class Base(metaclass=Meta): + pass + + @dataclass + class C(Base): + pass + self.assertDocStrEqual(C.__doc__, "C") diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 2b49615178f136..021542ff7979ef 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -841,7 +841,7 @@ def wrapper(): pass functools.update_wrapper(wrapper, max) self.assertEqual(wrapper.__name__, 'max') - self.assertStartsWith(wrapper.__doc__, 'max(') + self.assertEqual(wrapper.__doc__, max.__doc__) self.assertEqual(wrapper.__annotations__, {}) def test_update_type_wrapper(self): diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index daae990458d708..6fc18bf2281f6f 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -21,6 +21,7 @@ import subprocess import time import types +import typing import tempfile import textwrap import unicodedata @@ -2924,19 +2925,27 @@ class MyParameter(inspect.Parameter): pass +def _conv_signature(sig): + return (tuple((param.name, + (... if param.default is param.empty else param.default), + (... if param.annotation is param.empty + else param.annotation), + str(param.kind).lower()) + for param in sig.parameters.values()), + (... if sig.return_annotation is sig.empty + else sig.return_annotation)) + class TestSignatureObject(unittest.TestCase): @staticmethod def signature(func, **kw): sig = inspect.signature(func, **kw) - return (tuple((param.name, - (... if param.default is param.empty else param.default), - (... if param.annotation is param.empty - else param.annotation), - str(param.kind).lower()) - for param in sig.parameters.values()), - (... if sig.return_annotation is sig.empty - else sig.return_annotation)) + return _conv_signature(sig) + + @staticmethod + def signatures(func, **kw): + sigs = inspect.signatures(func, **kw) + return [_conv_signature(sig) for sig in sigs] def test_signature_object(self): S = inspect.Signature @@ -3025,6 +3034,69 @@ def test2(pod=42, /): with self.assertRaisesRegex(ValueError, 'more than one variadic keyword parameter'): S((kwargs, second_kwargs)) + def test_multisignature_object(self): + def test(stop): + pass + def test2(start, stop, step=1): + pass + sig = inspect.signature(test) + sig2 = inspect.signature(test2) + + self.assertRaises(ValueError, inspect.MultiSignature, []) + + msig = inspect.MultiSignature([sig2]) + self.assertEqual(str(msig), '(start, stop, step=1)') + self.assertEqual(repr(msig), '') + self.assertEqual(list(msig), [sig2]) + self.assertEqual(msig, sig2) + self.assertEqual(sig2, msig) + self.assertEqual(hash(msig), hash(sig2)) + self.assertEqual(msig.parameters, sig2.parameters) + + msig = inspect.MultiSignature([sig, sig2]) + self.assertEqual(str(msig), '(stop)\n(start, stop, step=1)') + self.assertEqual(repr(msig), '') + self.assertEqual(list(msig), [sig, sig2]) + self.assertNotEqual(msig, sig) + self.assertNotEqual(sig, msig) + self.assertNotEqual(msig, sig2) + self.assertNotEqual(sig2, msig) + self.assertEqual(list(msig.parameters), ['stop', 'start', 'step']) + self.assertEqual(msig.parameters['stop'], sig.parameters['stop']) + self.assertEqual(msig.parameters['start'], sig2.parameters['start']) + self.assertEqual(msig.parameters['step'], sig2.parameters['step']) + self.assertEqual(msig.return_annotation, inspect.Signature.empty) + + msig2 = inspect.MultiSignature([inspect.signature(test), + inspect.signature(test2)]) + self.assertEqual(msig, msig2) + self.assertEqual(hash(msig), hash(msig2)) + + def test_multisignature_return_annotation(self): + @inspect.signature + def sig(stop) -> 'x': + pass + @inspect.signature + def sig2(start, stop) -> list: + pass + @inspect.signature + def sig3(start, stop, step) -> list: + pass + @inspect.signature + def sig4(*args): + pass + + msig = inspect.MultiSignature([sig2, sig3]) + self.assertEqual(msig.return_annotation, list) + + msig = inspect.MultiSignature([sig, sig2, sig3]) + self.assertEqual(msig.return_annotation, + typing.Union[typing.ForwardRef('x'), list]) + + msig = inspect.MultiSignature([sig, sig2, sig3, sig4]) + self.assertEqual(msig.return_annotation, + typing.Union[typing.ForwardRef('x'), list]) + def test_signature_object_pickle(self): def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass foo_partial = functools.partial(foo, a=1) @@ -3053,6 +3125,19 @@ def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass self.assertTrue(isinstance(sig_pickled.parameters['z'], MyParameter)) + def test_multisignature_object_pickle(self): + @inspect.signature + def sig(stop): + pass + @inspect.signature + def sig2(start, stop, step=1): + pass + msig = inspect.MultiSignature([sig, sig2]) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + msig2 = pickle.loads(pickle.dumps(msig, proto)) + self.assertEqual(msig2, msig) + def test_signature_immutability(self): def test(a): pass @@ -3243,10 +3328,6 @@ def test_signature_on_builtins_no_signature(self): 'no signature found for builtin'): inspect.signature(_testcapi.docstring_no_signature) - with self.assertRaisesRegex(ValueError, - 'no signature found for builtin'): - inspect.signature(str) - cls = _testcapi.DocStringNoSignatureTest obj = _testcapi.DocStringNoSignatureTest() tests = [ @@ -3718,6 +3799,79 @@ def foo(a=0, b=1, /, c=2, d=3): ('c', ..., ..., "positional_only")), ...)) + def test_multisignature_on_partial(self): + from functools import partial + + @inspect.signature + def sig(stop): + pass + @inspect.signature + def sig2(start, stop, step=1): + pass + def test(*args, **kwargs): + pass + test.__signature__ = inspect.MultiSignature([sig, sig2]) + + self.assertEqual(str(inspect.signature(partial(test))), + '(stop)\n(start, stop, step=1)') + self.assertEqual(self.signature(partial(test)), + ((('stop', ..., ..., 'positional_or_keyword'), + ('start', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)) + self.assertEqual(self.signatures(partial(test)), [ + ((('stop', ..., ..., 'positional_or_keyword'),), ...), + ((('start', ..., ..., 'positional_or_keyword'), + ('stop', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)]) + + self.assertEqual(str(inspect.signature(partial(test, 5))), + '()\n(stop, step=1)') + self.assertEqual(self.signature(partial(test, 5)), + ((('stop', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)) + self.assertEqual(self.signatures(partial(test, 5)), [ + ((), ...), + ((('stop', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)]) + + self.assertEqual(str(inspect.signature(partial(test, 5, 10))), + '(step=1)') + self.assertEqual(self.signature(partial(test, 5, 10)), + ((('step', 1, ..., 'positional_or_keyword'),), + ...)) + self.assertEqual(self.signatures(partial(test, 5, 10)), [ + ((('step', 1, ..., 'positional_or_keyword'),), + ...)]) + + self.assertEqual(str(inspect.signature(partial(test, stop=5))), + '(*, stop=5)\n(start, *, stop=5, step=1)') + self.assertEqual(self.signature(partial(test, stop=5)), + ((('stop', 5, ..., 'keyword_only'), + ('start', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'keyword_only')), + ...)) + self.assertEqual(self.signatures(partial(test, stop=5)), [ + ((('stop', 5, ..., 'keyword_only'),), ...), + ((('start', ..., ..., 'positional_or_keyword'), + ('stop', 5, ..., 'keyword_only'), + ('step', 1, ..., 'keyword_only')), + ...)]) + + self.assertEqual(str(inspect.signature(partial(partial(test, stop=5), 2))), + '(*, stop=5, step=1)') + self.assertEqual(self.signature(partial(partial(test, stop=5), 2)), + ((('stop', 5, ..., 'keyword_only'), + ('step', 1, ..., 'keyword_only')), + ...)) + self.assertEqual(self.signatures(partial(partial(test, stop=5), 2)), [ + ((('stop', 5, ..., 'keyword_only'), + ('step', 1, ..., 'keyword_only')), + ...)]) + def test_signature_on_partialmethod(self): from functools import partialmethod @@ -3769,6 +3923,228 @@ def test(self: 'anno', x): ((('self', ..., 'anno', 'positional_or_keyword'),), ...)) + def test_multisignature_on_partialmethod(self): + from functools import partialmethod + + class A: + @inspect.signature + def sig1(self, stop): + pass + @inspect.signature + def sig2(self, start, stop, step=1): + pass + + def test(*args, **kwargs): + pass + test.__signature__ = inspect.MultiSignature([sig1, sig2]) + + part1 = partialmethod(test, 5) + part2 = partialmethod(test, stop=5) + + self.assertEqual(str(inspect.signature(A.part1)), + '(self)\n(self, stop, step=1)') + self.assertEqual(str(inspect.signature(A().part1)), + '()\n(stop, step=1)') + self.assertEqual(self.signature(A.part1), + ((('self', ..., ..., 'positional_or_keyword'), + ('stop', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)) + self.assertEqual(self.signature(A().part1), + ((('stop', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)) + self.assertEqual(self.signatures(A.part1), [ + ((('self', ..., ..., 'positional_or_keyword'),), ...), + ((('self', ..., ..., 'positional_or_keyword'), + ('stop', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)]) + self.assertEqual(self.signatures(A().part1), [ + ((), ...), + ((('stop', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)]) + + self.assertEqual(str(inspect.signature(A.part2)), + '(self, *, stop=5)\n(self, start, *, stop=5, step=1)') + self.assertEqual(str(inspect.signature(A().part2)), + '(*, stop=5)\n(start, *, stop=5, step=1)') + self.assertEqual(self.signature(A.part2), + ((('self', ..., ..., 'positional_or_keyword'), + ('stop', 5, ..., 'keyword_only'), + ('start', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'keyword_only')), + ...)) + self.assertEqual(self.signature(A().part2), + ((('stop', 5, ..., 'keyword_only'), + ('start', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'keyword_only')), + ...)) + self.assertEqual(self.signatures(A.part2), [ + ((('self', ..., ..., 'positional_or_keyword'), + ('stop', 5, ..., 'keyword_only'),), + ...), + ((('self', ..., ..., 'positional_or_keyword'), + ('start', ..., ..., 'positional_or_keyword'), + ('stop', 5, ..., 'keyword_only'), + ('step', 1, ..., 'keyword_only')), + ...)]) + self.assertEqual(self.signatures(A().part2), [ + ((('stop', 5, ..., 'keyword_only'),), ...), + ((('start', ..., ..., 'positional_or_keyword'), + ('stop', 5, ..., 'keyword_only'), + ('step', 1, ..., 'keyword_only')), + ...)]) + + def test_multisignature_on_partialmethod_different_self(self): + from functools import partialmethod + + class A: + @inspect.signature + def sig1(self, stop): + pass + @inspect.signature + def sig2(self2, start, stop, step=1): + pass + + def test(*args, **kwargs): + pass + test.__signature__ = inspect.MultiSignature([sig1, sig2]) + + part = partialmethod(test, 5) + + self.assertEqual(str(inspect.signature(A.part)), + '(self)\n(self2, stop, step=1)') + self.assertEqual(str(inspect.signature(A().part)), + '()\n(stop, step=1)') + self.assertEqual(self.signature(A.part), + ((('self', ..., ..., 'positional_or_keyword'), + ('self2', ..., ..., 'positional_or_keyword'), + ('stop', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)) + self.assertEqual(self.signature(A().part), + ((('stop', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)) + self.assertEqual(self.signatures(A.part), [ + ((('self', ..., ..., 'positional_or_keyword'),), ...), + ((('self2', ..., ..., 'positional_or_keyword'), + ('stop', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)]) + self.assertEqual(self.signatures(A().part), [ + ((), ...), + ((('stop', ..., ..., 'positional_or_keyword'), + ('step', 1, ..., 'positional_or_keyword')), + ...)]) + + def test_multisignature_on_partialmethod_no_self(self): + from functools import partialmethod + + class A: + @inspect.signature + def sig1(self, stop): + pass + @inspect.signature + def sig2(*, step=1): + pass + + def test(*args, **kwargs): + pass + test.__signature__ = inspect.MultiSignature([sig1, sig2]) + + part = partialmethod(test) + + self.assertEqual(str(inspect.signature(A.part)), + '(self, stop)') + self.assertEqual(str(inspect.signature(A().part)), + '(stop)') + self.assertEqual(self.signature(A.part), + ((('self', ..., ..., 'positional_or_keyword'), + ('stop', ..., ..., 'positional_or_keyword')), + ...)) + self.assertEqual(self.signature(A().part), + ((('stop', ..., ..., 'positional_or_keyword'),), ...)) + self.assertEqual(self.signatures(A.part), [ + ((('self', ..., ..., 'positional_or_keyword'), + ('stop', ..., ..., 'positional_or_keyword')), + ...)]) + self.assertEqual(self.signatures(A().part), [ + ((('stop', ..., ..., 'positional_or_keyword'),), ...)]) + + def test_multisignature_on_partialmethod_varpositional_self(self): + from functools import partialmethod + + class A: + @inspect.signature + def sig1(self, stop): + pass + @inspect.signature + def sig2(*args, step=1): + pass + + def test1(*args, **kwargs): + pass + test1.__signature__ = inspect.MultiSignature([sig1, sig2]) + + part1 = partialmethod(test1, 5) + + def test2(*args, **kwargs): + pass + test2.__signature__ = inspect.MultiSignature([sig2, sig1]) + + part2 = partialmethod(test2, 5) + + self.assertEqual(str(inspect.signature(A.part1)), + '(self)\n(*args, step=1)') + self.assertEqual(str(inspect.signature(A().part1)), + '()\n(*args, step=1)') + self.assertEqual(self.signature(A.part1), + ((('self', ..., ..., 'positional_or_keyword'), + ('args', ..., ..., 'var_positional'), + ('step', 1, ..., 'keyword_only')), + ...)) + self.assertEqual(self.signature(A().part1), + ((('args', ..., ..., 'var_positional'), + ('step', 1, ..., 'keyword_only')), + ...)) + self.assertEqual(self.signatures(A.part1), [ + ((('self', ..., ..., 'positional_or_keyword'),), ...), + ((('args', ..., ..., 'var_positional'), + ('step', 1, ..., 'keyword_only')), + ...)]) + self.assertEqual(self.signatures(A().part1), [ + ((), ...), + ((('args', ..., ..., 'var_positional'), + ('step', 1, ..., 'keyword_only')), + ...)]) + + self.assertEqual(str(inspect.signature(A.part2)), + '(*args, step=1)\n(self)') + self.assertEqual(str(inspect.signature(A().part2)), + '(*args, step=1)\n()') + self.assertEqual(self.signature(A.part2), + ((('args', ..., ..., 'var_positional'), + ('step', 1, ..., 'keyword_only'), + ('self', ..., ..., 'positional_or_keyword')), + ...)) + self.assertEqual(self.signature(A().part2), + ((('args', ..., ..., 'var_positional'), + ('step', 1, ..., 'keyword_only')), + ...)) + self.assertEqual(self.signatures(A.part2), [ + ((('args', ..., ..., 'var_positional'), + ('step', 1, ..., 'keyword_only')), + ...), + ((('self', ..., ..., 'positional_or_keyword'),), ...)]) + self.assertEqual(self.signatures(A().part2), [ + ((('args', ..., ..., 'var_positional'), + ('step', 1, ..., 'keyword_only')), + ...), + ((), ...)]) + def test_signature_on_fake_partialmethod(self): def foo(a): pass foo.__partialmethod__ = 'spam' @@ -5550,14 +5926,132 @@ def foo(a): pass ba = inspect.signature(foo).bind(1) self.assertIs(type(ba.arguments), dict) + def test_multisignature_bind(self): + @inspect.signature + def sig(stop): + pass + @inspect.signature + def sig2(start, stop, step=1): + pass + msig = inspect.MultiSignature([sig, sig2]) + + ba = msig.bind(5) + self.assertEqual(ba.args, (5,)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'stop': 5}) + self.assertEqual(ba.signature, sig) + + ba = msig.bind(5, 10) + self.assertEqual(ba.args, (5, 10)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'start': 5, 'stop': 10}) + self.assertEqual(ba.signature, sig2) + + ba = msig.bind(5, 10, 2) + self.assertEqual(ba.args, (5, 10, 2)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'start': 5, 'stop': 10, 'step': 2}) + self.assertEqual(ba.signature, sig2) + + ba = msig.bind(stop=5) + self.assertEqual(ba.args, (5,)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'stop': 5}) + self.assertEqual(ba.signature, sig) + + ba = msig.bind(start=5, stop=10) + self.assertEqual(ba.args, (5, 10)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'start': 5, 'stop': 10}) + self.assertEqual(ba.signature, sig2) + + ba = msig.bind(start=5, stop=10, step=2) + self.assertEqual(ba.args, (5, 10, 2)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'start': 5, 'stop': 10, 'step': 2}) + self.assertEqual(ba.signature, sig2) + + self.assertRaises(TypeError, msig.bind) + self.assertRaises(TypeError, msig.bind, start=5) + self.assertRaises(TypeError, msig.bind, step=2) + self.assertRaises(TypeError, msig.bind, 5, 10, 2, 3) + + def test_multisignature_bind_partial(self): + @inspect.signature + def sig(stop): + pass + @inspect.signature + def sig2(start, stop, step=1): + pass + msig = inspect.MultiSignature([sig, sig2]) + + ba = msig.bind_partial(5, 10) + self.assertEqual(ba.args, (5, 10)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'start': 5, 'stop': 10}) + self.assertEqual(ba.signature, sig2) + + ba = msig.bind_partial(5, 10, 2) + self.assertEqual(ba.args, (5, 10, 2)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'start': 5, 'stop': 10, 'step': 2}) + self.assertEqual(ba.signature, sig2) + + ba = msig.bind_partial(stop=5) + self.assertEqual(ba.args, (5,)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'stop': 5}) + self.assertEqual(ba.signature, msig) + + ba = msig.bind_partial(start=5, stop=10) + self.assertEqual(ba.args, (5, 10)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'start': 5, 'stop': 10}) + self.assertEqual(ba.signature, sig2) + + ba = msig.bind_partial(start=5, stop=10, step=2) + self.assertEqual(ba.args, (5, 10, 2)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'start': 5, 'stop': 10, 'step': 2}) + self.assertEqual(ba.signature, sig2) + + ba = msig.bind_partial() + self.assertEqual(ba.args, ()) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {}) + self.assertEqual(ba.signature, msig) + ba.apply_defaults() + self.assertEqual(ba.args, ()) + self.assertEqual(ba.kwargs, {'step': 1}) + self.assertEqual(ba.arguments, {'step': 1}) + + ba = msig.bind_partial(start=5) + self.assertEqual(ba.args, (5,)) + self.assertEqual(ba.kwargs, {}) + self.assertEqual(ba.arguments, {'start': 5}) + self.assertEqual(ba.signature, sig2) + ba.apply_defaults() + self.assertEqual(ba.args, (5,)) + self.assertEqual(ba.kwargs, {'step': 1}) + self.assertEqual(ba.arguments, {'start': 5, 'step': 1}) + + ba = msig.bind_partial(step=2) + self.assertEqual(ba.args, ()) + self.assertEqual(ba.kwargs, {'step': 2}) + self.assertEqual(ba.arguments, {'step': 2}) + ba.apply_defaults() + self.assertEqual(ba.args, ()) + self.assertEqual(ba.kwargs, {'step': 2}) + self.assertEqual(ba.arguments, {'step': 2}) + + self.assertRaises(TypeError, msig.bind_partial, 5) + self.assertRaises(TypeError, msig.bind_partial, 5, 10, 2, 3) + + class TestSignaturePrivateHelpers(unittest.TestCase): - def _strip_non_python_syntax(self, input, - clean_signature, self_parameter): - computed_clean_signature, \ - computed_self_parameter = \ - inspect._signature_strip_non_python_syntax(input) - self.assertEqual(computed_clean_signature, clean_signature) - self.assertEqual(computed_self_parameter, self_parameter) + def _strip_non_python_syntax(self, input, clean_signature, self_parameter): + self.assertEqual(list(inspect._signature_split(input)), + [(clean_signature, self_parameter)]) def test_signature_strip_non_python_syntax(self): self._strip_non_python_syntax( @@ -5678,48 +6172,25 @@ def _test_builtin_methods_have_signatures(self, cls, no_signature, unsupported_s self.assertRaises(ValueError, inspect.signature, getattr(cls, name)) def test_builtins_have_signatures(self): - no_signature = {'type', 'super', 'bytearray', 'bytes', 'dict', 'int', 'str'} - # These need PEP 457 groups - needs_groups = {"range", "slice", "dir", "getattr", - "next", "iter", "vars"} - no_signature |= needs_groups - # These have unrepresentable parameter default values of NULL - unsupported_signature = {"anext"} - # These need *args support in Argument Clinic - needs_varargs = {"min", "max", "__build_class__"} - no_signature |= needs_varargs + no_signature = {'type', 'super'} methods_no_signature = { - 'dict': {'update'}, 'object': {'__class__'}, } methods_unsupported_signature = { - 'bytearray': {'count', 'endswith', 'find', 'hex', 'index', 'rfind', 'rindex', 'startswith'}, - 'bytes': {'count', 'endswith', 'find', 'hex', 'index', 'rfind', 'rindex', 'startswith'}, - 'dict': {'pop'}, - 'memoryview': {'cast', 'hex'}, - 'str': {'count', 'endswith', 'find', 'index', 'maketrans', 'rfind', 'rindex', 'startswith'}, + 'bytearray': {'count', 'endswith', 'find', 'index', 'rfind', 'rindex', 'startswith'}, + 'bytes': {'count', 'endswith', 'find', 'index', 'rfind', 'rindex', 'startswith'}, + 'str': {'count', 'endswith', 'find', 'index', 'rfind', 'rindex', 'startswith'}, } self._test_module_has_signatures(builtins, - no_signature, unsupported_signature, + no_signature, (), methods_no_signature, methods_unsupported_signature) def test_types_module_has_signatures(self): - unsupported_signature = {'CellType'} - methods_no_signature = { - 'AsyncGeneratorType': {'athrow'}, - 'CoroutineType': {'throw'}, - 'GeneratorType': {'throw'}, - } - self._test_module_has_signatures(types, - unsupported_signature=unsupported_signature, - methods_no_signature=methods_no_signature) + self._test_module_has_signatures(types) def test_sys_module_has_signatures(self): - no_signature = {'getsizeof', 'set_asyncgen_hooks'} - no_signature |= {name for name in ['getobjects'] - if hasattr(sys, name)} - self._test_module_has_signatures(sys, no_signature) + self._test_module_has_signatures(sys) def test_abc_module_has_signatures(self): import abc @@ -5731,12 +6202,10 @@ def test_atexit_module_has_signatures(self): def test_codecs_module_has_signatures(self): import codecs - methods_no_signature = {'StreamReader': {'charbuffertype'}} - self._test_module_has_signatures(codecs, - methods_no_signature=methods_no_signature) + self._test_module_has_signatures(codecs) def test_collections_module_has_signatures(self): - no_signature = {'OrderedDict', 'defaultdict'} + no_signature = () unsupported_signature = {'deque'} methods_no_signature = { 'OrderedDict': {'update'}, @@ -5744,7 +6213,6 @@ def test_collections_module_has_signatures(self): methods_unsupported_signature = { 'deque': {'index'}, 'OrderedDict': {'pop'}, - 'UserString': {'maketrans'}, } self._test_module_has_signatures(collections, no_signature, unsupported_signature, @@ -5868,12 +6336,12 @@ def test_tracemalloc_module_has_signatures(self): def test_typing_module_has_signatures(self): import typing no_signature = {'ParamSpec', 'ParamSpecArgs', 'ParamSpecKwargs', - 'Text', 'TypeAliasType', 'TypeVar', 'TypeVarTuple'} + 'TypeAliasType', 'TypeVar', 'TypeVarTuple'} methods_no_signature = { 'Generic': {'__class_getitem__', '__init_subclass__'}, } methods_unsupported_signature = { - 'Text': {'count', 'find', 'index', 'rfind', 'rindex', 'startswith', 'endswith', 'maketrans'}, + 'Text': {'count', 'find', 'index', 'rfind', 'rindex', 'startswith', 'endswith'}, } self._test_module_has_signatures(typing, no_signature, methods_no_signature=methods_no_signature, diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 2b1a4484c680fc..2d31d19873ad0d 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -1505,7 +1505,10 @@ def _get_summary_line(o): text = pydoc.plain(pydoc.render_doc(o)) lines = text.split('\n') assert len(lines) >= 2 - return lines[2] + for i in range(3, len(lines)): + if lines[i].startswith(' '): + break + return '\n'.join(lines[2:i]) @staticmethod def _get_summary_lines(o): @@ -1618,12 +1621,15 @@ def test_module_level_callable_unrepresentable_default(self): builtin = _testcapi.func_with_unrepresentable_signature self.assertEqual(self._get_summary_line(builtin), "func_with_unrepresentable_signature(a, b=)") + builtin = _testcapi.func_with_unrepresentable_multisignature + self.assertEqual(self._get_summary_lines(builtin), + "func_with_unrepresentable_multisignature()\n" + "func_with_unrepresentable_multisignature(a, b=)\n" + " This docstring has a multisignature with unrepresentable default.\n") @support.cpython_only @requires_docstrings def test_builtin_staticmethod_unrepresentable_default(self): - self.assertEqual(self._get_summary_line(str.maketrans), - "maketrans(x, y=, z=, /)") _testcapi = import_helper.import_module("_testcapi") cls = _testcapi.DocStringUnrepresentableSignatureTest self.assertEqual(self._get_summary_line(cls.staticmeth), @@ -1632,9 +1638,6 @@ def test_builtin_staticmethod_unrepresentable_default(self): @support.cpython_only @requires_docstrings def test_unbound_builtin_method_unrepresentable_default(self): - self.assertEqual(self._get_summary_line(dict.pop), - "pop(self, key, default=, /) " - "unbound builtins.dict method") _testcapi = import_helper.import_module("_testcapi") cls = _testcapi.DocStringUnrepresentableSignatureTest self.assertEqual(self._get_summary_line(cls.meth), @@ -1644,15 +1647,34 @@ def test_unbound_builtin_method_unrepresentable_default(self): @support.cpython_only @requires_docstrings def test_bound_builtin_method_unrepresentable_default(self): - self.assertEqual(self._get_summary_line({}.pop), - "pop(key, default=, /) " - "method of builtins.dict instance") _testcapi = import_helper.import_module("_testcapi") obj = _testcapi.DocStringUnrepresentableSignatureTest() self.assertEqual(self._get_summary_line(obj.meth), "meth(a, b=) " "method of _testcapi.DocStringUnrepresentableSignatureTest instance") + @support.cpython_only + @requires_docstrings + def test_unbound_builtin_method_multisig_unrepresentable_default(self): + _testcapi = import_helper.import_module("_testcapi") + cls = _testcapi.DocStringUnrepresentableSignatureTest + self.assertEqual(self._get_summary_line(cls.meth_multi), + "meth_multi(self, /) unbound " + "_testcapi.DocStringUnrepresentableSignatureTest method\n" + "meth_multi(self, /, a, b=) unbound " + "_testcapi.DocStringUnrepresentableSignatureTest method") + + @support.cpython_only + @requires_docstrings + def test_bound_builtin_method_multisig_unrepresentable_default(self): + _testcapi = import_helper.import_module("_testcapi") + obj = _testcapi.DocStringUnrepresentableSignatureTest() + self.assertEqual(self._get_summary_line(obj.meth_multi), + "meth_multi() " + "method of _testcapi.DocStringUnrepresentableSignatureTest instance\n" + "meth_multi(a, b=) " + "method of _testcapi.DocStringUnrepresentableSignatureTest instance") + @support.cpython_only @requires_docstrings def test_unbound_builtin_classmethod_unrepresentable_default(self): diff --git a/Modules/_testcapi/docstring.c b/Modules/_testcapi/docstring.c index efb889cba8796e..75e14865b34793 100644 --- a/Modules/_testcapi/docstring.c +++ b/Modules/_testcapi/docstring.c @@ -107,6 +107,14 @@ static PyMethodDef test_methods[] = { "--\n\n" "This docstring has a signature with unrepresentable default." )}, + {"func_with_unrepresentable_multisignature", + (PyCFunction)test_with_docstring, METH_VARARGS, + PyDoc_STR( + "func_with_unrepresentable_multisignature($module, /)\n" + "($module, /, a, b=)\n" + "--\n\n" + "This docstring has a multisignature with unrepresentable default." + )}, {NULL}, }; @@ -176,6 +184,14 @@ static PyMethodDef DocStringUnrepresentableSignatureTest_methods[] = { "--\n\n" "This instance method has a default parameter value from the module scope." )}, + {"meth_multi", + (PyCFunction)test_with_docstring, METH_VARARGS, + PyDoc_STR( + "meth_multi($self, /)\n" + "($self, /, a, b=)\n" + "--\n\n" + "This docstring has a multisignature with unrepresentable default." + )}, {NULL}, }; diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index b5d5ca9178ebdb..d1b064f8706a3e 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -878,18 +878,30 @@ bytearray_ass_subscript(PyObject *op, PyObject *index, PyObject *values) } /*[clinic input] +@text_signature "()" +@text_signature "(source)" +@text_signature "(source, encoding)" +@text_signature "(source, encoding, errors)" bytearray.__init__ source as arg: object = NULL encoding: str = NULL errors: str = NULL +Construct a mutable bytearray object. + +If there are no arguments, create an empty bytearray object. If the first +argument is an integer, create a zero-filled bytearray object of the +specified size. If it is an iterable of integers, fill the new bytearray +object with them. If it is an object implementing the buffer protocol, +copy its content in the new bytearray object. If it is a text string, +encode it with the specified encoding and errors handler. [clinic start generated code]*/ static int bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, const char *encoding, const char *errors) -/*[clinic end generated code: output=4ce1304649c2f8b3 input=1141a7122eefd7b9]*/ +/*[clinic end generated code: output=4ce1304649c2f8b3 input=d2ed9273070b926c]*/ { Py_ssize_t count; PyObject *it; @@ -2554,6 +2566,8 @@ bytearray_fromhex_impl(PyTypeObject *type, PyObject *string) } /*[clinic input] +@text_signature "($self, /, *, bytes_per_sep=1)" +@text_signature "($self, /, sep, bytes_per_sep=1)" @critical_section bytearray.hex @@ -2579,7 +2593,7 @@ Create a string of hexadecimal numbers from a bytearray object. static PyObject * bytearray_hex_impl(PyByteArrayObject *self, PyObject *sep, int bytes_per_sep) -/*[clinic end generated code: output=29c4e5ef72c565a0 input=7784107de7048873]*/ +/*[clinic end generated code: output=29c4e5ef72c565a0 input=a801b04ba003842a]*/ { char* argbuf = PyByteArray_AS_STRING(self); Py_ssize_t arglen = PyByteArray_GET_SIZE(self); @@ -2778,20 +2792,6 @@ static PyNumberMethods bytearray_as_number = { bytearray_mod, /*nb_remainder*/ }; -PyDoc_STRVAR(bytearray_doc, -"bytearray(iterable_of_ints) -> bytearray\n\ -bytearray(string, encoding[, errors]) -> bytearray\n\ -bytearray(bytes_or_buffer) -> mutable copy of bytes_or_buffer\n\ -bytearray(int) -> bytes array of size given by the parameter initialized with null bytes\n\ -bytearray() -> empty bytes array\n\ -\n\ -Construct a mutable bytearray object from:\n\ - - an iterable yielding integers in range(256)\n\ - - a text string encoded using the specified encoding\n\ - - a bytes or a buffer object\n\ - - any object implementing the buffer API.\n\ - - an integer"); - static PyObject *bytearray_iter(PyObject *seq); @@ -2817,7 +2817,7 @@ PyTypeObject PyByteArray_Type = { &bytearray_as_buffer, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | _Py_TPFLAGS_MATCH_SELF, /* tp_flags */ - bytearray_doc, /* tp_doc */ + bytearray___init____doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ bytearray_richcompare, /* tp_richcompare */ diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index fc407ec6bf99d6..a919a3fd32c052 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -2615,6 +2615,8 @@ _PyBytes_FromHex(PyObject *string, int use_bytearray) } /*[clinic input] +@text_signature "($self, /, *, bytes_per_sep=1)" +@text_signature "($self, /, sep, bytes_per_sep=1)" bytes.hex sep: object = NULL @@ -2639,7 +2641,7 @@ Create a string of hexadecimal numbers from a bytes object. static PyObject * bytes_hex_impl(PyBytesObject *self, PyObject *sep, int bytes_per_sep) -/*[clinic end generated code: output=1f134da504064139 input=1a21282b1f1ae595]*/ +/*[clinic end generated code: output=1f134da504064139 input=a896de1af0991eda]*/ { const char *argbuf = PyBytes_AS_STRING(self); Py_ssize_t arglen = PyBytes_GET_SIZE(self); @@ -2734,6 +2736,10 @@ static PyObject * bytes_subtype_new(PyTypeObject *, PyObject *); /*[clinic input] +@text_signature "()" +@text_signature "(source)" +@text_signature "(source, encoding)" +@text_signature "(source, encoding, errors)" @classmethod bytes.__new__ as bytes_new @@ -2741,12 +2747,20 @@ bytes.__new__ as bytes_new encoding: str = NULL errors: str = NULL +Construct an immutable array of bytes. + +If there are no arguments, create an empty bytes object. If the first +argument is an integer, create a zero-filled bytes object of the +specified size. If it is an iterable of integers, fill the new bytes +object with them. If it is an object implementing the buffer protocol, +copy its content in the new bytes object. If it is a text string, +encode it with the specified encoding and errors handler. [clinic start generated code]*/ static PyObject * bytes_new_impl(PyTypeObject *type, PyObject *x, const char *encoding, const char *errors) -/*[clinic end generated code: output=1e0c471be311a425 input=f0a966d19b7262b4]*/ +/*[clinic end generated code: output=1e0c471be311a425 input=22b618010386f6c2]*/ { PyObject *bytes; PyObject *func; @@ -3071,19 +3085,6 @@ bytes_subtype_new(PyTypeObject *type, PyObject *tmp) return pnew; } -PyDoc_STRVAR(bytes_doc, -"bytes(iterable_of_ints) -> bytes\n\ -bytes(string, encoding[, errors]) -> bytes\n\ -bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n\ -bytes(int) -> bytes object of size given by the parameter initialized with null bytes\n\ -bytes() -> empty bytes object\n\ -\n\ -Construct an immutable array of bytes from:\n\ - - an iterable yielding integers in range(256)\n\ - - a text string encoded using the specified encoding\n\ - - any object implementing the buffer API.\n\ - - an integer"); - static PyObject *bytes_iter(PyObject *seq); PyTypeObject PyBytes_Type = { @@ -3109,7 +3110,7 @@ PyTypeObject PyBytes_Type = { Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_BYTES_SUBCLASS | _Py_TPFLAGS_MATCH_SELF, /* tp_flags */ - bytes_doc, /* tp_doc */ + bytes_new__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ bytes_richcompare, /* tp_richcompare */ diff --git a/Objects/cellobject.c b/Objects/cellobject.c index ec2eeb1a855b63..d5a91d971f0d7c 100644 --- a/Objects/cellobject.c +++ b/Objects/cellobject.c @@ -22,7 +22,8 @@ PyCell_New(PyObject *obj) } PyDoc_STRVAR(cell_new_doc, -"cell([contents])\n" +"cell()\n" +"cell(contents)\n" "--\n" "\n" "Create a new cell object.\n" diff --git a/Objects/clinic/bytearrayobject.c.h b/Objects/clinic/bytearrayobject.c.h index ffb45ade11f6dc..7a2135326485f6 100644 --- a/Objects/clinic/bytearrayobject.c.h +++ b/Objects/clinic/bytearrayobject.c.h @@ -10,6 +10,22 @@ preserve #include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() +PyDoc_STRVAR(bytearray___init____doc__, +"bytearray()\n" +"(source)\n" +"(source, encoding)\n" +"(source, encoding, errors)\n" +"--\n" +"\n" +"Construct a mutable bytearray object.\n" +"\n" +"If there are no arguments, create an empty bytearray object. If the first\n" +"argument is an integer, create a zero-filled bytearray object of the\n" +"specified size. If it is an iterable of integers, fill the new bytearray\n" +"object with them. If it is an object implementing the buffer protocol,\n" +"copy its content in the new bytearray object. If it is a text string,\n" +"encode it with the specified encoding and errors handler."); + static int bytearray___init___impl(PyByteArrayObject *self, PyObject *arg, const char *encoding, const char *errors); @@ -1627,7 +1643,8 @@ bytearray_fromhex(PyObject *type, PyObject *string) } PyDoc_STRVAR(bytearray_hex__doc__, -"hex($self, /, sep=, bytes_per_sep=1)\n" +"hex($self, /, *, bytes_per_sep=1)\n" +"($self, /, sep, bytes_per_sep=1)\n" "--\n" "\n" "Create a string of hexadecimal numbers from a bytearray object.\n" @@ -1796,4 +1813,4 @@ bytearray_sizeof(PyObject *self, PyObject *Py_UNUSED(ignored)) { return bytearray_sizeof_impl((PyByteArrayObject *)self); } -/*[clinic end generated code: output=be6d28193bc96a2c input=a9049054013a1b77]*/ +/*[clinic end generated code: output=00551208af76a274 input=a9049054013a1b77]*/ diff --git a/Objects/clinic/bytesobject.c.h b/Objects/clinic/bytesobject.c.h index 00cf13d422d900..7b04fd4d6844d5 100644 --- a/Objects/clinic/bytesobject.c.h +++ b/Objects/clinic/bytesobject.c.h @@ -1228,7 +1228,8 @@ bytes_fromhex(PyObject *type, PyObject *string) } PyDoc_STRVAR(bytes_hex__doc__, -"hex($self, /, sep=, bytes_per_sep=1)\n" +"hex($self, /, *, bytes_per_sep=1)\n" +"($self, /, sep, bytes_per_sep=1)\n" "--\n" "\n" "Create a string of hexadecimal numbers from a bytes object.\n" @@ -1317,6 +1318,22 @@ bytes_hex(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwn return return_value; } +PyDoc_STRVAR(bytes_new__doc__, +"bytes()\n" +"(source)\n" +"(source, encoding)\n" +"(source, encoding, errors)\n" +"--\n" +"\n" +"Construct an immutable array of bytes.\n" +"\n" +"If there are no arguments, create an empty bytes object. If the first\n" +"argument is an integer, create a zero-filled bytes object of the\n" +"specified size. If it is an iterable of integers, fill the new bytes\n" +"object with them. If it is an object implementing the buffer protocol,\n" +"copy its content in the new bytes object. If it is a text string,\n" +"encode it with the specified encoding and errors handler."); + static PyObject * bytes_new_impl(PyTypeObject *type, PyObject *x, const char *encoding, const char *errors); @@ -1411,4 +1428,4 @@ bytes_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=08b9507244f73638 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=16a50a937cb9ee69 input=a9049054013a1b77]*/ diff --git a/Objects/clinic/dictobject.c.h b/Objects/clinic/dictobject.c.h index abf6b38449fcb0..1534193e93b358 100644 --- a/Objects/clinic/dictobject.c.h +++ b/Objects/clinic/dictobject.c.h @@ -171,10 +171,11 @@ dict_clear(PyObject *self, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(dict_pop__doc__, -"pop($self, key, default=, /)\n" +"pop($self, key, /)\n" +"($self, key, default, /)\n" "--\n" "\n" -"D.pop(k[,d]) -> v, remove specified key and return the corresponding value.\n" +"Remove specified key and return the corresponding value.\n" "\n" "If the key is not found, return the default if given; otherwise,\n" "raise a KeyError."); @@ -323,4 +324,4 @@ dict_values(PyObject *self, PyObject *Py_UNUSED(ignored)) { return dict_values_impl((PyDictObject *)self); } -/*[clinic end generated code: output=9007b74432217017 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=80ac1f2081a3e3a9 input=a9049054013a1b77]*/ diff --git a/Objects/clinic/longobject.c.h b/Objects/clinic/longobject.c.h index a236a32c091c4c..1089a41a0ddb09 100644 --- a/Objects/clinic/longobject.c.h +++ b/Objects/clinic/longobject.c.h @@ -119,7 +119,8 @@ int___format__(PyObject *self, PyObject *arg) } PyDoc_STRVAR(int___round____doc__, -"__round__($self, ndigits=None, /)\n" +"__round__($self, /)\n" +"($self, ndigits, /)\n" "--\n" "\n" "Rounding an Integral returns itself.\n" @@ -485,4 +486,4 @@ int_is_integer(PyObject *self, PyObject *Py_UNUSED(ignored)) { return int_is_integer_impl(self); } -/*[clinic end generated code: output=d23f8ce5bdf08a30 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=5692a57d8ca4a46a input=a9049054013a1b77]*/ diff --git a/Objects/clinic/memoryobject.c.h b/Objects/clinic/memoryobject.c.h index 28cfd1a22080c9..b0ca1779103e69 100644 --- a/Objects/clinic/memoryobject.c.h +++ b/Objects/clinic/memoryobject.c.h @@ -147,7 +147,8 @@ memoryview_release(PyObject *self, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(memoryview_cast__doc__, -"cast($self, /, format, shape=)\n" +"cast($self, format, /)\n" +"($self, format, shape, /)\n" "--\n" "\n" "Cast a memoryview to a new format or shape."); @@ -339,7 +340,8 @@ memoryview_tobytes(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyOb } PyDoc_STRVAR(memoryview_hex__doc__, -"hex($self, /, sep=, bytes_per_sep=1)\n" +"hex($self, /, *, bytes_per_sep=1)\n" +"($self, /, sep, bytes_per_sep=1)\n" "--\n" "\n" "Return the data in the buffer as a str of hexadecimal numbers.\n" @@ -496,4 +498,4 @@ memoryview_index(PyObject *self, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=154f4c04263ccb24 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0b7bb36e002a2ab8 input=a9049054013a1b77]*/ diff --git a/Objects/clinic/unicodeobject.c.h b/Objects/clinic/unicodeobject.c.h index 1819fbaea220a3..d07b912e6281ca 100644 --- a/Objects/clinic/unicodeobject.c.h +++ b/Objects/clinic/unicodeobject.c.h @@ -1538,7 +1538,8 @@ unicode_swapcase(PyObject *self, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(unicode_maketrans__doc__, -"maketrans(x, y=, z=, /)\n" +"maketrans($self, x, /)\n" +"($self, x, y, z=\'\', /)\n" "--\n" "\n" "Return a translation table usable for str.translate().\n" @@ -1908,4 +1909,4 @@ unicode_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=238917fe66120bde input=a9049054013a1b77]*/ +/*[clinic end generated code: output=6fea158fdc9e1edf input=a9049054013a1b77]*/ diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 792a34cc569fe8..40c1bd6e2bfcff 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -4465,13 +4465,15 @@ dict_clear_impl(PyDictObject *self) } /*[clinic input] +@text_signature "($self, key, /)" +@text_signature "($self, key, default, /)" dict.pop key: object default: object = NULL / -D.pop(k[,d]) -> v, remove specified key and return the corresponding value. +Remove specified key and return the corresponding value. If the key is not found, return the default if given; otherwise, raise a KeyError. @@ -4479,7 +4481,7 @@ raise a KeyError. static PyObject * dict_pop_impl(PyDictObject *self, PyObject *key, PyObject *default_value) -/*[clinic end generated code: output=3abb47b89f24c21c input=e221baa01044c44c]*/ +/*[clinic end generated code: output=3abb47b89f24c21c input=30abdf0727d0f247]*/ { return dict_pop_default((PyObject*)self, key, default_value); } @@ -4703,10 +4705,19 @@ PyDoc_STRVAR(getitem__doc__, "__getitem__($self, key, /)\n--\n\nReturn self[key]."); PyDoc_STRVAR(update__doc__, -"D.update([E, ]**F) -> None. Update D from mapping/iterable E and F.\n\ -If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k]\n\ -If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v\n\ -In either case, this is followed by: for k in F: D[k] = F[k]"); +"update($self, /, **kwargs)\n\ +($self, mapping_or_iterable, /, **kwargs)\n\ +--\n\ +\n\ +Update a dict from a mapping or iterable and keyword arguments.\n\ +\n\ +If the positional argument is present and has a .keys() method,\n\ +then does:\n\ + for k in mapping_or_iterable.keys(): self[k] = mapping_or_iterable[k]\n\ +If it is present and lacks a .keys() method, then does:\n\ + for k, v in mapping_or_iterable: self[k] = v\n\ +In either case, this is followed by:\n\ + for k in kwargs: self[k] = kwargs[k]"); /* Forward */ @@ -4881,15 +4892,23 @@ dict_iter(PyObject *self) } PyDoc_STRVAR(dictionary_doc, -"dict() -> new empty dictionary\n" -"dict(mapping) -> new dictionary initialized from a mapping object's\n" -" (key, value) pairs\n" -"dict(iterable) -> new dictionary initialized as if via:\n" -" d = {}\n" -" for k, v in iterable:\n" -" d[k] = v\n" -"dict(**kwargs) -> new dictionary initialized with the name=value pairs\n" -" in the keyword argument list. For example: dict(one=1, two=2)"); +"dict(**kwargs)\n" +"(mapping_or_iterable, /, **kwargs)\n" +"--\n" +"\n" +"Create a new dictionary.\n" +"\n" +"If no arguments are supplied, create an empty dictionary:\n" +" dict() -> {}\n" +"If the positional argument is a mapping object, a new dictionary will\n" +"be initialized from its (key, value) pairs.\n" +"If it is an iterable of pairs, a new dictionary will\n" +"be initialized from these pairs.\n" +" dict([('one', 1), ('two', 2)]) -> {'one': 1, 'two': 2}\n" +"If keyword arguments are supplied, new items will be added using the\n" +"keyword name as a key and the argument value as a value:\n" +" dict(one=1, two=2) -> {'one': 1, 'two': 2}\n" +"Positional and keyword arguments can be combined."); PyTypeObject PyDict_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) diff --git a/Objects/genobject.c b/Objects/genobject.c index 98b2c5004df8ac..2bcaac0f8e15ac 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -452,8 +452,11 @@ gen_close(PyObject *self, PyObject *args) PyDoc_STRVAR(throw_doc, -"throw(value)\n\ -throw(type[,value[,tb]])\n\ +"throw(value, /)\n\ +(type, /)\n\ +(type, value, /)\n\ +(type, value, traceback, /)\n\ +--\n\ \n\ Raise exception in generator, return next yielded value or raise\n\ StopIteration.\n\ @@ -1187,8 +1190,11 @@ PyDoc_STRVAR(coro_send_doc, return next iterated value or raise StopIteration."); PyDoc_STRVAR(coro_throw_doc, -"throw(value)\n\ -throw(type[,value[,traceback]])\n\ +"throw(value, /)\n\ +(type, /)\n\ +(type, value, /)\n\ +(type, value, traceback, /)\n\ +--\n\ \n\ Raise exception in coroutine, return next iterated value or raise\n\ StopIteration.\n\ @@ -1623,8 +1629,11 @@ PyDoc_STRVAR(async_asend_doc, "asend(v) -> send 'v' in generator."); PyDoc_STRVAR(async_athrow_doc, -"athrow(value)\n\ -athrow(type[,value[,tb]])\n\ +"athrow(value, /)\n\ +(type, /)\n\ +(type, value, /)\n\ +(type, value, traceback, /)\n\ +--\n\ \n\ raise exception in generator.\n\ the (type, val, tb) signature is deprecated, \n\ diff --git a/Objects/iterobject.c b/Objects/iterobject.c index 5712e02ae828ab..8b1c300a13ac93 100644 --- a/Objects/iterobject.c +++ b/Objects/iterobject.c @@ -466,8 +466,11 @@ return next yielded value or raise StopIteration."); PyDoc_STRVAR(throw_doc, -"throw(value)\n\ -throw(typ[,val[,tb]])\n\ +"throw(value, /)\n\ +(type, /)\n\ +(type, value, /)\n\ +(type, value, traceback, /)\n\ +--\n\ \n\ raise exception in the wrapped iterator, return next yielded value\n\ or raise StopIteration.\n\ diff --git a/Objects/longobject.c b/Objects/longobject.c index 692312c1ad976c..77407913501bed 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -6171,6 +6171,8 @@ _PyLong_DivmodNear(PyObject *a, PyObject *b) } /*[clinic input] +@text_signature "($self, /)" +@text_signature "($self, ndigits, /)" int.__round__ ndigits as o_ndigits: object = None @@ -6183,7 +6185,7 @@ Rounding with an ndigits argument also returns an integer. static PyObject * int___round___impl(PyObject *self, PyObject *o_ndigits) -/*[clinic end generated code: output=954fda6b18875998 input=30c2aec788263144]*/ +/*[clinic end generated code: output=954fda6b18875998 input=148a9aa959f112a8]*/ { /* To round an integer m to the nearest 10**n (n positive), we make use of * the divmod_near operation, defined by: @@ -6559,8 +6561,9 @@ static PyGetSetDef long_getset[] = { }; PyDoc_STRVAR(long_doc, -"int([x]) -> integer\n\ -int(x, base=10) -> integer\n\ +"int(x=0, /)\n\ +(x, /, base=10)\n\ +--\n\ \n\ Convert a number or string to an integer, or return 0 if no arguments\n\ are given. If x is a number, return x.__int__(). For floating-point\n\ diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index cf673fb379edcd..84528809d9c9e2 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -1436,6 +1436,8 @@ zero_in_shape(PyMemoryViewObject *mv) size of the original input. Otherwise, an error is raised. */ /*[clinic input] +@text_signature "($self, format, /)" +@text_signature "($self, format, shape, /)" memoryview.cast format: unicode @@ -1447,7 +1449,7 @@ Cast a memoryview to a new format or shape. static PyObject * memoryview_cast_impl(PyMemoryViewObject *self, PyObject *format, PyObject *shape) -/*[clinic end generated code: output=bae520b3a389cbab input=138936cc9041b1a3]*/ +/*[clinic end generated code: output=bae520b3a389cbab input=7a8da62fc30e35e4]*/ { PyMemoryViewObject *mv = NULL; Py_ssize_t ndim = 1; @@ -2315,6 +2317,8 @@ memoryview_tobytes_impl(PyMemoryViewObject *self, const char *order) } /*[clinic input] +@text_signature "($self, /, *, bytes_per_sep=1)" +@text_signature "($self, /, sep, bytes_per_sep=1)" memoryview.hex sep: object = NULL @@ -2340,7 +2344,7 @@ Return the data in the buffer as a str of hexadecimal numbers. static PyObject * memoryview_hex_impl(PyMemoryViewObject *self, PyObject *sep, int bytes_per_sep) -/*[clinic end generated code: output=430ca760f94f3ca7 input=539f6a3a5fb56946]*/ +/*[clinic end generated code: output=430ca760f94f3ca7 input=fdf8251033d71260]*/ { Py_buffer *src = VIEW_ADDR(self); PyObject *bytes; diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index f8cdfe68a6435e..38ef084c02d989 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -157,8 +157,11 @@ range_vectorcall(PyObject *rangetype, PyObject *const *args, } PyDoc_STRVAR(range_doc, -"range(stop) -> range object\n\ -range(start, stop[, step]) -> range object\n\ +"range(stop, /)\n\ +(start, stop, step=1, /)\n\ +--\n\ +\n\ +Create a range object.\n\ \n\ Return an object that produces a sequence of integers from start (inclusive)\n\ to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.\n\ diff --git a/Objects/sliceobject.c b/Objects/sliceobject.c index 5186ff4f6f0cf5..1bc3c5459d5d33 100644 --- a/Objects/sliceobject.c +++ b/Objects/sliceobject.c @@ -338,8 +338,9 @@ slice_new(PyTypeObject *type, PyObject *args, PyObject *kw) } PyDoc_STRVAR(slice_doc, -"slice(stop)\n\ -slice(start, stop[, step])\n\ +"slice(stop, /)\n\ +(start, stop, step=1, /)\n\ +--\n\ \n\ Create a slice object. This is used for extended slicing (e.g. a[0:10:2])."); diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 7c735685e89389..c75d4c64d36713 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -13353,6 +13353,8 @@ unicode_swapcase_impl(PyObject *self) /*[clinic input] +@text_signature "($self, x, /)" +@text_signature "($self, x, y, z='', /)" @staticmethod str.maketrans as unicode_maketrans @@ -13377,7 +13379,7 @@ must be a string, whose characters will be mapped to None in the result. static PyObject * unicode_maketrans_impl(PyObject *x, PyObject *y, PyObject *z) -/*[clinic end generated code: output=a925c89452bd5881 input=7bfbf529a293c6c5]*/ +/*[clinic end generated code: output=a925c89452bd5881 input=39cc0e989ac451fd]*/ { PyObject *new = NULL, *key, *value; Py_ssize_t i = 0; @@ -15547,8 +15549,12 @@ _PyUnicode_ExactDealloc(PyObject *op) } PyDoc_STRVAR(unicode_doc, -"str(object='') -> str\n\ -str(bytes_or_buffer[, encoding[, errors]]) -> str\n\ +"str()\n\ +(object)\n\ +(object, encoding)\n\ +(object, encoding, errors)\n\ +(object, *, errors)\n\ +--\n\ \n\ Create a new string object from the given object. If encoding or\n\ errors is specified, then the object must expose a data buffer\n\ diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 10e415fa052f64..c6930bd61acc3a 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -247,7 +247,9 @@ builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs, } PyDoc_STRVAR(build_class_doc, -"__build_class__(func, name, /, *bases, [metaclass], **kwds) -> class\n\ +"__build_class__($module, func, name, /, *bases, **kwds)\n\ +($module, func, name, /, *bases, metaclass, **kwds)\n\ +--\n\ \n\ Internal helper function used by the class statement."); @@ -907,7 +909,9 @@ builtin_dir(PyObject *self, PyObject *args) } PyDoc_STRVAR(dir_doc, -"dir([object]) -> list of strings\n" +"dir($module, /)\n" +"($module, object, /)\n" +"--\n" "\n" "If called without an argument, return the names in the current scope.\n" "Else, return an alphabetized list of names comprising (some of) the attributes\n" @@ -1222,7 +1226,9 @@ builtin_getattr(PyObject *self, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(getattr_doc, -"getattr(object, name[, default]) -> value\n\ +"getattr($module, object, name, /)\n\ +($module, object, name, default, /)\n\ +--\n\ \n\ Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.\n\ When a default argument is given, it is returned when the attribute doesn't\n\ @@ -1662,7 +1668,9 @@ builtin_next(PyObject *self, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(next_doc, -"next(iterator[, default])\n\ +"next($module, iterator, /)\n\ +next($module, iterator, default, /)\n\ +--\n\ \n\ Return the next item from the iterator. If default is given and the iterator\n\ is exhausted, it is returned instead of raising StopIteration."); @@ -1781,8 +1789,9 @@ builtin_iter(PyObject *self, PyObject *const *args, Py_ssize_t nargs) } PyDoc_STRVAR(iter_doc, -"iter(iterable) -> iterator\n\ -iter(callable, sentinel) -> iterator\n\ +"iter($module, iterable, /)\n\ +($module, callable, sentinel)\n\ +--\n\ \n\ Get an iterator from an object. In the first form, the argument must\n\ supply its own iterator, or be a sequence.\n\ @@ -1808,6 +1817,8 @@ builtin_aiter(PyObject *module, PyObject *async_iterable) PyObject *PyAnextAwaitable_New(PyObject *, PyObject *); /*[clinic input] +@text_signature "($module, aiterator, /)" +@text_signature "($module, aiterator, default, /)" anext as builtin_anext aiterator: object @@ -1823,7 +1834,7 @@ it is returned instead of raising StopAsyncIteration. static PyObject * builtin_anext_impl(PyObject *module, PyObject *aiterator, PyObject *default_value) -/*[clinic end generated code: output=f02c060c163a81fa input=2900e4a370d39550]*/ +/*[clinic end generated code: output=f02c060c163a81fa input=0a1ca4f26c511583]*/ { PyTypeObject *t; PyObject *awaitable; @@ -2019,8 +2030,10 @@ builtin_min(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *k } PyDoc_STRVAR(min_doc, -"min(iterable, *[, default=obj, key=func]) -> value\n\ -min(arg1, arg2, *args, *[, key=func]) -> value\n\ +"min($module, iterable, /, *, key=None)\n\ +($module, iterable, /, *, default, key=None)\n\ +($module, arg1, arg2, /, *args, key=None)\n\ +--\n\ \n\ With a single iterable argument, return its smallest item. The\n\ default keyword-only argument specifies an object to return if\n\ @@ -2036,8 +2049,10 @@ builtin_max(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *k } PyDoc_STRVAR(max_doc, -"max(iterable, *[, default=obj, key=func]) -> value\n\ -max(arg1, arg2, *args, *[, key=func]) -> value\n\ +"max($module, iterable, /, *, key=None)\n\ +($module, iterable, /, *, default, key=None)\n\ +($module, arg1, arg2, /, *args, key=None)\n\ +--\n\ \n\ With a single iterable argument, return its biggest item. The\n\ default keyword-only argument specifies an object to return if\n\ @@ -2637,7 +2652,9 @@ builtin_vars(PyObject *self, PyObject *args) } PyDoc_STRVAR(vars_doc, -"vars([object]) -> dictionary\n\ +"vars($module, /)\n\ +vars($module, object, /)\n\ +--\n\ \n\ Without arguments, equivalent to locals().\n\ With an argument, equivalent to object.__dict__."); diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h index c826a5724f769c..e85edfab6e9790 100644 --- a/Python/clinic/bltinmodule.c.h +++ b/Python/clinic/bltinmodule.c.h @@ -750,7 +750,8 @@ PyDoc_STRVAR(builtin_aiter__doc__, {"aiter", (PyCFunction)builtin_aiter, METH_O, builtin_aiter__doc__}, PyDoc_STRVAR(builtin_anext__doc__, -"anext($module, aiterator, default=, /)\n" +"anext($module, aiterator, /)\n" +"($module, aiterator, default, /)\n" "--\n" "\n" "Return the next item from the async iterator.\n" @@ -1268,4 +1269,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=e7a5d0851d7f2cfb input=a9049054013a1b77]*/ +/*[clinic end generated code: output=25f309ae2be751c1 input=a9049054013a1b77]*/ diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 2a28fab2f51ea3..f036c7f65feb3c 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1488,7 +1488,11 @@ sys_set_asyncgen_hooks(PyObject *self, PyObject *args, PyObject *kw) } PyDoc_STRVAR(set_asyncgen_hooks_doc, -"set_asyncgen_hooks([firstiter] [, finalizer])\n\ +"set_asyncgen_hooks($module, /)\n\ +($module, /, *, finalizer)\n\ +($module, /, firstiter)\n\ +($module, /, firstiter, finalizer)\n\ +--\n\ \n\ Set a finalizer for async generators objects." ); @@ -1995,7 +1999,9 @@ sys_getsizeof(PyObject *self, PyObject *args, PyObject *kwds) } PyDoc_STRVAR(getsizeof_doc, -"getsizeof(object [, default]) -> int\n\ +"getsizeof($module, object)\n\ +($module, object, default)\n\ +--\n\ \n\ Return the size of object in bytes."); diff --git a/Tools/clinic/libclinic/dsl_parser.py b/Tools/clinic/libclinic/dsl_parser.py index 4b4a8b9969d142..a363cf665596ac 100644 --- a/Tools/clinic/libclinic/dsl_parser.py +++ b/Tools/clinic/libclinic/dsl_parser.py @@ -453,8 +453,9 @@ def at_coexist(self) -> None: def at_text_signature(self, text_signature: str) -> None: if self.forced_text_signature: - fail("Called @text_signature twice!") - self.forced_text_signature = text_signature + self.forced_text_signature += '\n' + text_signature + else: + self.forced_text_signature = text_signature def parse(self, block: Block) -> None: self.reset()