diff --git a/Lib/inspect.py b/Lib/inspect.py --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -31,6 +31,7 @@ __author__ = ('Ka-Ping Yee ', 'Yury Selivanov ') +import ast import importlib.machinery import itertools import linecache @@ -519,8 +520,7 @@ if hasattr(object, '__file__'): return object.__file__ raise TypeError('{!r} is a built-in class'.format(object)) - if ismethod(object): - object = object.__func__ + if ismethod(object):efobject = object.__func__ if isfunction(object): object = object.__code__ if istraceback(object): @@ -1461,6 +1461,9 @@ if isinstance(obj, types.FunctionType): return Signature.from_function(obj) + if isinstance(obj, types.BuiltinFunctionType): + return Signature.from_builtin(obj) + if isinstance(obj, functools.partial): sig = signature(obj.func) @@ -1942,6 +1945,61 @@ return_annotation=annotations.get('return', _empty), __validate_parameters__=False) + @classmethod + def from_builtin(cls, func): + s = getattr(func, "__textsig__", None) + if not s: + return None + + if s.endswith("/)"): + kind = Parameter.POSITIONAL_ONLY + s = s[:-2] + ')' + else: + kind = Parameter.POSITIONAL_OR_KEYWORD + + s = "def foo" + s + ": pass" + + module = ast.parse(s) + if not isinstance(module, ast.Module): + return None + + # ast.FunctionDef + f = module.body[0] + + parameters = [] + empty = Parameter.empty + + def p(name_node, default_node, default=empty): + name = name_node.arg + + if isinstance(default_node, ast.Num): + default = default.n + elif isinstance(default_node, ast.NameConstant): + default = default_node.value + parameters.append(Parameter(name, kind, default=default, annotation=empty)) + + # non-keyword-only parameters + for name, default in reversed(list(itertools.zip_longest(reversed(f.args.args), reversed(f.args.defaults), fillvalue=None))): + p(name, default) + + # *args + if f.args.vararg: + kind = Parameter.VAR_POSITIONAL + p(f.args.vararg, empty) + + # keyword-only arguments + kind = Parameter.KEYWORD_ONLY + for name, default in zip(f.args.kwonlyargs, f.args.kw_defaults): + p(name, default) + + # **kwargs + if f.args.kwarg: + kind = Parameter.VAR_KEYWORD + p(f.args.kwarg, empty) + + return cls(parameters, return_annotation=cls.empty) + + @property def parameters(self): return self._parameters diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -580,9 +580,9 @@ [clinic]*/ PyDoc_STRVAR(curses_window_addch__doc__, +"def ([x, y,] ch, [attr])\n" "Paint character ch at (y, x) with attributes attr.\n" "\n" -"curses.window.addch([x, y,] ch, [attr])\n" " x\n" " X-coordinate.\n" " y\n" @@ -592,6 +592,7 @@ " attr\n" " Attributes for the character.\n" "\n" +"\n" "Paint character ch at (y, x) with attributes attr,\n" "overwriting any character previously painted at that location.\n" "By default, the character position and attributes are the\n" @@ -646,7 +647,7 @@ static PyObject * curses_window_addch_impl(PyObject *self, int group_left_1, int x, int y, PyObject *ch, int group_right_1, long attr) -/*[clinic checksum: 094d012af1019387c0219a9c0bc76e90729c833f]*/ +/*[clinic checksum: 37cb15b8fbb8523571f5ba376b1ef27ed65b3e52]*/ { PyCursesWindowObject *cwself = (PyCursesWindowObject *)self; int coordinates_group = group_left_1; diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -4155,12 +4155,13 @@ [clinic]*/ PyDoc_STRVAR(datetime_datetime_now__doc__, +"def (tz=None)\n" "Returns new datetime object representing current time local to tz.\n" "\n" -"datetime.datetime.now(tz=None)\n" " tz\n" " Timezone object.\n" "\n" +"\n" "If no tz is specified, uses local timezone."); #define DATETIME_DATETIME_NOW_METHODDEF \ @@ -4188,7 +4189,7 @@ static PyObject * datetime_datetime_now_impl(PyObject *cls, PyObject *tz) -/*[clinic checksum: cde1daca68c9b7dca6df51759db2de1d43a39774]*/ +/*[clinic checksum: 225613c3c989a09d2f2ced461477db7182c0e58c]*/ { PyObject *self; diff --git a/Modules/_dbmmodule.c b/Modules/_dbmmodule.c --- a/Modules/_dbmmodule.c +++ b/Modules/_dbmmodule.c @@ -400,9 +400,9 @@ [clinic]*/ PyDoc_STRVAR(dbmopen__doc__, +"def (filename, flags=\'r\', mode=0o666)\n" "Return a database object.\n" "\n" -"dbm.open(filename, flags=\'r\', mode=0o666)\n" " filename\n" " The filename to open.\n" " flags\n" @@ -437,7 +437,7 @@ static PyObject * dbmopen_impl(PyObject *module, const char *filename, const char *flags, int mode) -/*[clinic checksum: 2b0ec9e3c6ecd19e06d16c9f0ba33848245cb1ab]*/ +/*[clinic checksum: d58c6626ae75fead67feb8c1d0f5864e82a56c2e]*/ { int iflags; diff --git a/Modules/_weakref.c b/Modules/_weakref.c --- a/Modules/_weakref.c +++ b/Modules/_weakref.c @@ -17,9 +17,8 @@ [clinic]*/ PyDoc_STRVAR(_weakref_getweakrefcount__doc__, -"Return the number of weak references to \'object\'.\n" -"\n" -"_weakref.getweakrefcount(object)"); +"def (object)\n" +"Return the number of weak references to \'object\'."); #define _WEAKREF_GETWEAKREFCOUNT_METHODDEF \ {"getweakrefcount", (PyCFunction)_weakref_getweakrefcount, METH_O, _weakref_getweakrefcount__doc__}, @@ -43,7 +42,7 @@ static Py_ssize_t _weakref_getweakrefcount_impl(PyObject *module, PyObject *object) -/*[clinic checksum: 05cffbc3a4b193a0b7e645da81be281748704f69]*/ +/*[clinic checksum: 888890d7b20d992f31389d02d1c1e96a77e238d7]*/ { PyWeakReference **list; diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -2435,9 +2435,9 @@ [clinic]*/ PyDoc_STRVAR(os_stat__doc__, +"def (path, *, dir_fd=None, follow_symlinks=True)\n" "Perform a stat system call on the given path.\n" "\n" -"os.stat(path, *, dir_fd=None, follow_symlinks=True) -> stat_result\n" " path\n" " Path to be examined; can be string, bytes, or open-file-descriptor int.\n" " dir_fd\n" @@ -2449,6 +2449,7 @@ " stat will examine the symbolic link itself instead of the file\n" " the link points to.\n" "\n" +"\n" "dir_fd and follow_symlinks may not be implemented\n" " on your platform. If they are unavailable, using them will raise a\n" " NotImplementedError.\n" @@ -2486,7 +2487,7 @@ static PyObject * os_stat_impl(PyObject *module, path_t *path, int dir_fd, int follow_symlinks) -/*[clinic checksum: 89390f78327e3f045a81974d758d3996e2a71f68]*/ +/*[clinic checksum: 54ee014161bd045c48dbc9324f2de4a215c86357]*/ { return posix_do_stat("stat", path, dir_fd, follow_symlinks); } @@ -2567,9 +2568,10 @@ [clinic]*/ PyDoc_STRVAR(os_access__doc__, +"def (path, mode, *, dir_fd=None, effective_ids=False, follow_symlinks=True)\n" "Use the real uid/gid to test for access to a path.\n" "\n" -"os.access(path, mode, *, dir_fd=None, effective_ids=False, follow_symlinks=True) -> True if granted, False otherwise\n" +"\n" " path\n" " Path to be tested; can be string, bytes, or open-file-descriptor int.\n" " mode\n" @@ -2586,8 +2588,6 @@ " If False, and the last element of the path is a symbolic link,\n" " access will examine the symbolic link itself instead of the file\n" " the link points to.\n" -"\n" -"{parameters}\n" "dir_fd, effective_ids, and follow_symlinks may not be implemented\n" " on your platform. If they are unavailable, using them will raise a\n" " NotImplementedError.\n" @@ -2628,7 +2628,7 @@ static PyObject * os_access_impl(PyObject *module, path_t *path, int mode, int dir_fd, int effective_ids, int follow_symlinks) -/*[clinic checksum: aa3e145816a748172e62df8e44af74169c7e1247]*/ +/*[clinic checksum: 67a01aff197fed4050f1939d14b3a3872a924c84]*/ { PyObject *return_value = NULL; @@ -2724,9 +2724,9 @@ [clinic]*/ PyDoc_STRVAR(os_ttyname__doc__, +"def (fd)\n" "Return the name of the terminal device connected to \'fd\'.\n" "\n" -"os.ttyname(fd)\n" " fd\n" " Integer file descriptor handle."); @@ -2758,7 +2758,7 @@ static char * os_ttyname_impl(PyObject *module, int fd) -/*[clinic checksum: c742dd621ec98d0f81d37d264e1d3c89c7a5fb1a]*/ +/*[clinic checksum: df251f6c56239b99d45d35273e09a655f5ad5022]*/ { char *ret; diff --git a/Modules/unicodedata.c b/Modules/unicodedata.c --- a/Modules/unicodedata.c +++ b/Modules/unicodedata.c @@ -124,9 +124,9 @@ [clinic]*/ PyDoc_STRVAR(unicodedata_UCD_decimal__doc__, +"def (unichr, default=None)\n" "Converts a Unicode character into its equivalent decimal value.\n" "\n" -"unicodedata.UCD.decimal(unichr, default=None)\n" "\n" "Returns the decimal value assigned to the Unicode character unichr\n" "as integer. If no such value is defined, default is returned, or, if\n" @@ -157,7 +157,7 @@ static PyObject * unicodedata_UCD_decimal_impl(PyObject *self, PyObject *unichr, PyObject *default_value) -/*[clinic checksum: a0980c387387287e2ac230c37d95b26f6903e0d2]*/ +/*[clinic checksum: dba532cffac5852faf34be85867ac79386e06fce]*/ { PyUnicodeObject *v = (PyUnicodeObject *)unichr; int have_old = 0; diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -650,9 +650,9 @@ [clinic]*/ PyDoc_STRVAR(zlib_Decompress_decompress__doc__, +"def (data, max_length=0)\n" "Return a string containing the decompressed version of the data.\n" "\n" -"zlib.Decompress.decompress(data, max_length=0)\n" " data\n" " The binary data to decompress.\n" " max_length\n" @@ -660,6 +660,7 @@ " Unconsumed input data will be stored in\n" " the unconsumed_tail attribute.\n" "\n" +"\n" "After calling this function, some of the input data may still be stored in\n" "internal buffers for later processing.\n" "Call the flush() method to clear these buffers."); @@ -692,7 +693,7 @@ static PyObject * zlib_Decompress_decompress_impl(PyObject *self, Py_buffer *data, int max_length) -/*[clinic checksum: bfac7a0f07e891869d87c665a76dc2611014420f]*/ +/*[clinic checksum: 51d0e7b50652bf5ce7503a593eceefe887cc156f]*/ { compobject *zself = (compobject *)self; int err; @@ -915,16 +916,15 @@ [clinic]*/ PyDoc_STRVAR(zlib_Compress_copy__doc__, -"Return a copy of the compression object.\n" -"\n" -"zlib.Compress.copy()"); +"def ()\n" +"Return a copy of the compression object."); #define ZLIB_COMPRESS_COPY_METHODDEF \ {"copy", (PyCFunction)zlib_Compress_copy, METH_NOARGS, zlib_Compress_copy__doc__}, static PyObject * zlib_Compress_copy(PyObject *self) -/*[clinic checksum: 2551952e72329f0f2beb48a1dde3780e485a220b]*/ +/*[clinic checksum: d77e4da0cca5198dc9c16e4c7f57b6b87d76d93a]*/ { compobject *zself = (compobject *)self; compobject *retval = NULL; diff --git a/Objects/dictobject.c b/Objects/dictobject.c --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -2172,16 +2172,15 @@ [clinic]*/ PyDoc_STRVAR(dict___contains____doc__, -"True if D has a key k, else False\"\n" -"\n" -"dict.__contains__(key)"); +"def (key)\n" +"True if D has a key k, else False\""); #define DICT___CONTAINS___METHODDEF \ {"__contains__", (PyCFunction)dict___contains__, METH_O|METH_COEXIST, dict___contains____doc__}, static PyObject * dict___contains__(PyObject *self, PyObject *key) -/*[clinic checksum: 61c5c802ea1d35699a1a754f1f3538ea9b259cf4]*/ +/*[clinic checksum: 705468360c8d017acb7c17e25c65bc354a0b7c7f]*/ { register PyDictObject *mp = (PyDictObject *)self; Py_hash_t hash; diff --git a/Objects/methodobject.c b/Objects/methodobject.c --- a/Objects/methodobject.c +++ b/Objects/methodobject.c @@ -159,11 +159,39 @@ } } + +static PyObject * +meth_get__textsig__(PyCFunctionObject *m, void *closure) +{ + const char *trace = m->m_ml->ml_doc; + const char *start; + + if (strncmp(trace, "def (", 5)) { + Py_INCREF(Py_None); + return Py_None; + } + + trace += 4; + start = trace; + while (*trace && *trace != '\n') + trace++; + + return PyUnicode_FromStringAndSize(start, trace - start); +} + static PyObject * meth_get__doc__(PyCFunctionObject *m, void *closure) { const char *doc = m->m_ml->ml_doc; + if (!strncmp(doc, "def (", 5)) { + doc += 5; + while (*doc && *doc != '\n') + doc++; + if (*doc) + doc++; + } + if (doc != NULL) return PyUnicode_FromString(doc); Py_INCREF(Py_None); @@ -236,6 +264,7 @@ {"__name__", (getter)meth_get__name__, NULL, NULL}, {"__qualname__", (getter)meth_get__qualname__, NULL, NULL}, {"__self__", (getter)meth_get__self__, NULL, NULL}, + {"__textsig__", (getter)meth_get__textsig__, NULL, NULL}, {0} }; diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -12908,9 +12908,9 @@ [clinic]*/ PyDoc_STRVAR(unicode_maketrans__doc__, +"def (x, y=None, z=None)\n" "Return a translation table usable for str.translate().\n" "\n" -"str.maketrans(x, y=None, z=None)\n" "\n" "If there is only one argument, it must be a dictionary mapping Unicode\n" "ordinals (integers) or characters to Unicode ordinals, strings or None.\n" @@ -12946,7 +12946,7 @@ static PyObject * unicode_maketrans_impl(PyObject *x, PyObject *y, PyObject *z) -/*[clinic checksum: 137db9c3199e7906b7967009f511c24fa3235b5f]*/ +/*[clinic checksum: 1c661e9d28ed93201e935cf59cbb5152c0f5cea5]*/ { PyObject *new = NULL, *key, *value; Py_ssize_t i = 0; diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -2251,8 +2251,7 @@ ## docstring first line ## - add(f.full_name) - add('(') + add('def (') # populate "right_bracket_count" field for every parameter if parameters: @@ -2308,19 +2307,16 @@ add(fix_right_bracket_count(0)) add(')') - if f.return_converter.doc_default: - add(' -> ') - add(f.return_converter.doc_default) + # if f.return_converter.doc_default: + # add(' -> ') + # add(f.return_converter.doc_default) docstring_first_line = output() # now fix up the places where the brackets look wrong docstring_first_line = docstring_first_line.replace(', ]', ',] ') - # okay. now we're officially building the - # "prototype" section. - add(docstring_first_line) - + # okay. now we're officially building the "parameters" section. # create substitution text for {parameters} for p in parameters: if not p.docstring.strip(): @@ -2330,7 +2326,7 @@ add(p.name) add('\n') add(textwrap.indent(rstrip_lines(p.docstring.rstrip()), " ")) - prototype = output() + parameters = output() ## ## docstring body @@ -2359,21 +2355,26 @@ elif len(lines) == 1: # the docstring is only one line right now--the summary line. # add an empty line after the summary line so we have space - # between it and the {prototype} we're about to add. + # between it and the {parameters} we're about to add. lines.append('') - prototype_marker_count = len(docstring.split('{prototype}')) - 1 - if prototype_marker_count: - fail('You may not specify {prototype} in a docstring!') - # insert *after* the summary line - lines.insert(2, '{prototype}\n') + parameters_marker_count = len(docstring.split('{parameters}')) - 1 + if parameters_marker_count > 1: + fail('You may not specify {parameters} more than once in a docstring!') + + # insert at front of docstring + lines.insert(0, docstring_first_line) + + if not parameters_marker_count: + # insert after summary line + lines.insert(2, '{parameters}\n') docstring = "\n".join(lines) add(docstring) docstring = output() - docstring = linear_format(docstring, prototype=prototype) + docstring = linear_format(docstring, parameters=parameters) docstring = docstring.rstrip() return docstring