From 44e2eaab5491881120aab43e2838da8afe7ab70e Mon Sep 17 00:00:00 2001 From: Larry Hastings Date: Sat, 23 Nov 2013 15:37:55 -0800 Subject: Issue #19674: inspect.signature() now produces a correct signature for some builtins. --- Lib/inspect.py | 62 +++++++++++++++++++++++++++++++++++++++++ Lib/pydoc.py | 52 ++++++++++++++++------------------ Lib/test/test_capi.py | 29 +++++++++++++++++++ Lib/test/test_inspect.py | 7 ++--- Misc/NEWS | 3 ++ Modules/_cursesmodule.c | 12 +++++--- Modules/_datetimemodule.c | 12 +++++--- Modules/_dbmmodule.c | 23 ++++++++------- Modules/_opcode.c | 19 +++++++------ Modules/_testcapimodule.c | 44 +++++++++++++++++++++++++++++ Modules/_weakref.c | 12 ++++---- Modules/posixmodule.c | 19 +++++++------ Modules/unicodedata.c | 14 ++++++---- Modules/zlibmodule.c | 17 ++++++------ Objects/dictobject.c | 13 +++++---- Objects/methodobject.c | 71 +++++++++++++++++++++++++++++++++++++++++++---- Objects/unicodeobject.c | 11 +++++--- Tools/clinic/clinic.py | 59 +++++++++++++++++---------------------- 18 files changed, 343 insertions(+), 136 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index edbf927..dc94e44 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -31,6 +31,7 @@ Here are some of the useful functions provided by this module: __author__ = ('Ka-Ping Yee ', 'Yury Selivanov ') +import ast import importlib.machinery import itertools import linecache @@ -1461,6 +1462,9 @@ def signature(obj): 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 +1946,64 @@ class Signature: return_annotation=annotations.get('return', _empty), __validate_parameters__=False) + @classmethod + def from_builtin(cls, func): + s = getattr(func, "__text_signature__", 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" + + try: + module = ast.parse(s) + except SyntaxError: + return None + 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/Lib/pydoc.py b/Lib/pydoc.py index e8127a2..2e63226 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -916,20 +916,18 @@ class HTMLDoc(Doc): reallink = realname title = '%s = %s' % ( anchor, name, reallink) - if inspect.isfunction(object): - args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann = \ - inspect.getfullargspec(object) - argspec = inspect.formatargspec( - args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann, - formatvalue=self.formatvalue, - formatannotation=inspect.formatannotationrelativeto(object)) - if 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. - argspec = argspec[1:-1] # remove parentheses - else: + argspec = None + if inspect.isfunction(object) or inspect.isbuiltin(object): + signature = inspect.signature(object) + if signature: + argspec = str(signature) + if 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. + argspec = argspec[1:-1] # remove parentheses + if not argspec: argspec = '(...)' decl = title + argspec + (note and self.grey( @@ -1313,20 +1311,18 @@ location listed above. cl.__dict__[realname] is object): skipdocs = 1 title = self.bold(name) + ' = ' + realname - if inspect.isfunction(object): - args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann = \ - inspect.getfullargspec(object) - argspec = inspect.formatargspec( - args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann, - formatvalue=self.formatvalue, - formatannotation=inspect.formatannotationrelativeto(object)) - if 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. - argspec = argspec[1:-1] # remove parentheses - else: + argspec = None + if inspect.isfunction(object) or inspect.isbuiltin(object): + signature = inspect.signature(object) + if signature: + argspec = str(signature) + if 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. + argspec = argspec[1:-1] # remove parentheses + if not argspec: argspec = '(...)' decl = title + argspec + note diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index 000079e..6f75b77 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -109,6 +109,35 @@ class CAPITest(unittest.TestCase): self.assertRaises(TypeError, _posixsubprocess.fork_exec, Z(),[b'1'],3,[1, 2],5,6,7,8,9,10,11,12,13,14,15,16,17) + def test_docstring_signature_parsing(self): + + self.assertEqual(_testcapi.no_docstring.__doc__, None) + self.assertEqual(_testcapi.no_docstring.__text_signature__, None) + + self.assertEqual(_testcapi.docstring_empty.__doc__, "") + self.assertEqual(_testcapi.docstring_empty.__text_signature__, None) + + self.assertEqual(_testcapi.docstring_no_signature.__doc__, + "This docstring has no signature.") + self.assertEqual(_testcapi.docstring_no_signature.__text_signature__, None) + + self.assertEqual(_testcapi.docstring_with_invalid_signature.__doc__, + "docstring_with_invalid_signature (boo)\n" + "\n" + "This docstring has an invalid signature." + ) + self.assertEqual(_testcapi.docstring_with_invalid_signature.__text_signature__, None) + + self.assertEqual(_testcapi.docstring_with_signature.__doc__, + "This docstring has a valid signature.") + self.assertEqual(_testcapi.docstring_with_signature.__text_signature__, "(sig)") + + self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__doc__, + "This docstring has a valid signature and some extra newlines.") + self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__text_signature__, + "(parameter)") + + @unittest.skipUnless(threading, 'Threading required for this test.') class TestPendingCalls(unittest.TestCase): diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 9d34904..0258d3df 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -1588,10 +1588,9 @@ class TestSignatureObject(unittest.TestCase): with self.assertRaisesRegex(ValueError, 'not supported by signature'): # support for 'method-wrapper' inspect.signature(min.__call__) - with self.assertRaisesRegex(ValueError, - 'no signature found for builtin function'): - # support for 'method-wrapper' - inspect.signature(min) + self.assertEqual(inspect.signature(min), None) + signature = inspect.signature(os.stat) + self.assertTrue(isinstance(signature, inspect.Signature)) def test_signature_on_non_function(self): with self.assertRaisesRegex(TypeError, 'is not a callable object'): diff --git a/Misc/NEWS b/Misc/NEWS index 1782312..cf6cc0e 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -68,6 +68,9 @@ Core and Builtins Library ------- +- Issue #19674: inspect.signature() now produces a correct signature + for some builtins. + - Issue #19722: Added opcode.stack_effect(), which computes the stack effect of bytecode instructions. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 6e48693..98b81fe 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -134,6 +134,12 @@ typedef chtype attr_t; /* No attr_t type is available */ #define STRICT_SYSV_CURSES #endif +/*[clinic] +module curses +class curses.window +[clinic]*/ +/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ + /* Definition of exception curses.error */ static PyObject *PyCursesError; @@ -550,8 +556,6 @@ PyCursesWindow_Dealloc(PyCursesWindowObject *wo) /* Addch, Addstr, Addnstr */ /*[clinic] -module curses -class curses.window curses.window.addch @@ -580,9 +584,9 @@ current settings for the window object. [clinic]*/ PyDoc_STRVAR(curses_window_addch__doc__, +"addch([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" @@ -646,7 +650,7 @@ curses_window_addch(PyObject *self, PyObject *args) 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: 44ed958b891cde91205e584c766e048f3999714f]*/ { PyCursesWindowObject *cwself = (PyCursesWindowObject *)self; int coordinates_group = group_left_1; diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 13c4ecc..e4dc1bf 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -16,6 +16,12 @@ #include "datetime.h" #undef Py_BUILD_CORE +/*[clinic] +module datetime +class datetime.datetime +[clinic]*/ +/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ + /* We require that C int be at least 32 bits, and use int virtually * everywhere. In just a few cases we use a temp long, where a Python * API returns a C long. In such cases, we have to ensure that the @@ -4140,8 +4146,6 @@ datetime_best_possible(PyObject *cls, TM_FUNC f, PyObject *tzinfo) } /*[clinic] -module datetime -class datetime.datetime @classmethod datetime.datetime.now @@ -4155,9 +4159,9 @@ If no tz is specified, uses local timezone. [clinic]*/ PyDoc_STRVAR(datetime_datetime_now__doc__, +"now(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" @@ -4188,7 +4192,7 @@ exit: static PyObject * datetime_datetime_now_impl(PyTypeObject *cls, PyObject *tz) -/*[clinic checksum: 5e61647d5d1feaf1ab096c5406ccea17bb7b061c]*/ +/*[clinic checksum: ca3d26a423b3f633b260c7622e303f0915a96f7c]*/ { PyObject *self; diff --git a/Modules/_dbmmodule.c b/Modules/_dbmmodule.c index 10f872d..10f8fb9 100644 --- a/Modules/_dbmmodule.c +++ b/Modules/_dbmmodule.c @@ -28,6 +28,12 @@ static char *which_dbm = "Berkeley DB"; #error "No ndbm.h available!" #endif +/*[clinic] +module dbm +class dbm.dbm +[clinic]*/ +/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ + typedef struct { PyObject_HEAD int di_size; /* -1 means recompute */ @@ -43,12 +49,6 @@ static PyTypeObject Dbmtype; static PyObject *DbmError; -/*[clinic] -module dbm -class dbm.dbm -[clinic]*/ -/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ - /*[python] class dbmobject_converter(self_converter): type = "dbmobject *" @@ -278,9 +278,8 @@ Return the value for key if present, otherwise default. [clinic]*/ PyDoc_STRVAR(dbm_dbm_get__doc__, -"Return the value for key if present, otherwise default.\n" -"\n" -"dbm.dbm.get(key, [default])"); +"get(key, [default])\n" +"Return the value for key if present, otherwise default."); #define DBM_DBM_GET_METHODDEF \ {"get", (PyCFunction)dbm_dbm_get, METH_VARARGS, dbm_dbm_get__doc__}, @@ -318,7 +317,7 @@ dbm_dbm_get(PyObject *self, PyObject *args) static PyObject * dbm_dbm_get_impl(dbmobject *dp, const char *key, Py_ssize_clean_t key_length, int group_right_1, PyObject *default_value) -/*[clinic checksum: 5b4265e66568f163ef0fc7efec09410eaf793508]*/ +/*[clinic checksum: 28cf8928811bde51e535d67ae98ea039d79df717]*/ { datum dbm_key, val; @@ -461,9 +460,9 @@ Return a database object. [clinic]*/ PyDoc_STRVAR(dbmopen__doc__, +"open(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" @@ -498,7 +497,7 @@ exit: static PyObject * dbmopen_impl(PyModuleDef *module, const char *filename, const char *flags, int mode) -/*[clinic checksum: c1f2036017ec36a43ac6f59893732751e67c19d5]*/ +/*[clinic checksum: fb265f75641553ccd963f84c143b35c11f9121fc]*/ { int iflags; diff --git a/Modules/_opcode.c b/Modules/_opcode.c index c53f0e0..55cffe1 100644 --- a/Modules/_opcode.c +++ b/Modules/_opcode.c @@ -1,10 +1,12 @@ #include "Python.h" #include "opcode.h" - /*[clinic] - module _opcode +[clinic]*/ +/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ + +/*[clinic] _opcode.stack_effect -> int @@ -19,18 +21,17 @@ Compute the stack effect of the opcode. [clinic]*/ PyDoc_STRVAR(_opcode_stack_effect__doc__, -"Compute the stack effect of the opcode.\n" -"\n" -"_opcode.stack_effect(opcode, [oparg])"); +"stack_effect(opcode, [oparg])\n" +"Compute the stack effect of the opcode."); #define _OPCODE_STACK_EFFECT_METHODDEF \ {"stack_effect", (PyCFunction)_opcode_stack_effect, METH_VARARGS, _opcode_stack_effect__doc__}, static int -_opcode_stack_effect_impl(PyObject *module, int opcode, int group_right_1, int oparg); +_opcode_stack_effect_impl(PyModuleDef *module, int opcode, int group_right_1, int oparg); static PyObject * -_opcode_stack_effect(PyObject *module, PyObject *args) +_opcode_stack_effect(PyModuleDef *module, PyObject *args) { PyObject *return_value = NULL; int opcode; @@ -62,8 +63,8 @@ exit: } static int -_opcode_stack_effect_impl(PyObject *module, int opcode, int group_right_1, int oparg) -/*[clinic checksum: 2312ded40abc9bcbce718942de21f53e61a2dfd3]*/ +_opcode_stack_effect_impl(PyModuleDef *module, int opcode, int group_right_1, int oparg) +/*[clinic checksum: e880e62dc7b0de73403026eaf4f8074aa106358b]*/ { int effect; if (HAS_ARG(opcode)) { diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 3362454..9662eb8 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2842,6 +2842,33 @@ test_pyobject_setallocators(PyObject *self) return test_setallocators(PYMEM_DOMAIN_OBJ); } +PyDoc_STRVAR(docstring_empty, +"" +); + +PyDoc_STRVAR(docstring_no_signature, +"This docstring has no signature." +); + +PyDoc_STRVAR(docstring_with_invalid_signature, +"docstring_with_invalid_signature (boo)\n" +"\n" +"This docstring has an invalid signature." +); + +PyDoc_STRVAR(docstring_with_signature, +"docstring_with_signature(sig)\n" +"This docstring has a valid signature." +); + +PyDoc_STRVAR(docstring_with_signature_and_extra_newlines, +"docstring_with_signature_and_extra_newlines(parameter)\n" +"\n" +"\n" +"\n" +"This docstring has a valid signature and some extra newlines." +); + static PyMethodDef TestMethods[] = { {"raise_exception", raise_exception, METH_VARARGS}, {"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS}, @@ -2953,6 +2980,23 @@ static PyMethodDef TestMethods[] = { (PyCFunction)test_pymem_setallocators, METH_NOARGS}, {"test_pyobject_setallocators", (PyCFunction)test_pyobject_setallocators, METH_NOARGS}, + {"no_docstring", + (PyCFunction)test_with_docstring, METH_NOARGS}, + {"docstring_empty", + (PyCFunction)test_with_docstring, METH_NOARGS, + docstring_empty}, + {"docstring_no_signature", + (PyCFunction)test_with_docstring, METH_NOARGS, + docstring_no_signature}, + {"docstring_with_invalid_signature", + (PyCFunction)test_with_docstring, METH_NOARGS, + docstring_with_invalid_signature}, + {"docstring_with_signature", + (PyCFunction)test_with_docstring, METH_NOARGS, + docstring_with_signature}, + {"docstring_with_signature_and_extra_newlines", + (PyCFunction)test_with_docstring, METH_NOARGS, + docstring_with_signature_and_extra_newlines}, {NULL, NULL} /* sentinel */ }; diff --git a/Modules/_weakref.c b/Modules/_weakref.c index af845ff..80de5da4 100644 --- a/Modules/_weakref.c +++ b/Modules/_weakref.c @@ -5,8 +5,11 @@ ((PyWeakReference **) PyObject_GET_WEAKREFS_LISTPTR(o)) /*[clinic] - module _weakref +[clinic]*/ +/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ + +/*[clinic] _weakref.getweakrefcount -> Py_ssize_t @@ -17,9 +20,8 @@ Return the number of weak references to 'object'. [clinic]*/ PyDoc_STRVAR(_weakref_getweakrefcount__doc__, -"Return the number of weak references to \'object\'.\n" -"\n" -"_weakref.getweakrefcount(object)"); +"getweakrefcount(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 +45,7 @@ exit: static Py_ssize_t _weakref_getweakrefcount_impl(PyModuleDef *module, PyObject *object) -/*[clinic checksum: 015113be0c9a0a8672d35df10c63e3642cc23da4]*/ +/*[clinic checksum: 436e8fbe0297434375f039d8c2d9fc3a9bbe773c]*/ { PyWeakReference **list; diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 9143fea..d0bd2d9 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -190,7 +190,10 @@ corresponding Unix manual entries for more information on calls."); #endif /* ! __WATCOMC__ || __QNX__ */ - +/*[clinic] +module os +[clinic]*/ +/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ #ifndef _MSC_VER @@ -2404,7 +2407,6 @@ class dir_fd_converter(CConverter): /*[python checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ /*[clinic] -module os os.stat -> object(doc_default='stat_result') @@ -2435,9 +2437,9 @@ It's an error to use dir_fd or follow_symlinks when specifying path as [clinic]*/ PyDoc_STRVAR(os_stat__doc__, +"stat(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" @@ -2486,7 +2488,7 @@ exit: static PyObject * os_stat_impl(PyModuleDef *module, path_t *path, int dir_fd, int follow_symlinks) -/*[clinic checksum: b08112eff0ceab3ec2c72352da95ce73f245d104]*/ +/*[clinic checksum: 85a71ad602e89f8e280118da976f70cd2f9abdf1]*/ { return posix_do_stat("stat", path, dir_fd, follow_symlinks); } @@ -2567,9 +2569,9 @@ Note that most operations will use the effective uid/gid, therefore this [clinic]*/ PyDoc_STRVAR(os_access__doc__, +"access(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" " path\n" " Path to be tested; can be string, bytes, or open-file-descriptor int.\n" " mode\n" @@ -2587,7 +2589,6 @@ PyDoc_STRVAR(os_access__doc__, " 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 +2629,7 @@ exit: static PyObject * os_access_impl(PyModuleDef *module, path_t *path, int mode, int dir_fd, int effective_ids, int follow_symlinks) -/*[clinic checksum: b9f8ececb061d31b64220c29526bfee642d1b602]*/ +/*[clinic checksum: 636e835c36562a2fc11acab75314634127fdf769]*/ { PyObject *return_value = NULL; @@ -2724,9 +2725,9 @@ Return the name of the terminal device connected to 'fd'. [clinic]*/ PyDoc_STRVAR(os_ttyname__doc__, +"ttyname(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 +2759,7 @@ exit: static char * os_ttyname_impl(PyModuleDef *module, int fd) -/*[clinic checksum: 61e4e525984cb293f949ccae6ae393c0011dfe8e]*/ +/*[clinic checksum: 0f368134dc0a7f21f25185e2e6bacf7675fb473a]*/ { char *ret; diff --git a/Modules/unicodedata.c b/Modules/unicodedata.c index 62ab957..eca0054 100644 --- a/Modules/unicodedata.c +++ b/Modules/unicodedata.c @@ -17,6 +17,12 @@ #include "ucnhash.h" #include "structmember.h" +/*[clinic] +module unicodedata +class unicodedata.UCD +[clinic]*/ +/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ + /* character properties */ typedef struct { @@ -108,8 +114,7 @@ static Py_UCS4 getuchar(PyUnicodeObject *obj) /* --- Module API --------------------------------------------------------- */ /*[clinic] -module unicodedata -class unicodedata.UCD + unicodedata.UCD.decimal unichr: object(type='str') @@ -124,10 +129,9 @@ not given, ValueError is raised. [clinic]*/ PyDoc_STRVAR(unicodedata_UCD_decimal__doc__, +"decimal(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" "not given, ValueError is raised."); @@ -157,7 +161,7 @@ exit: static PyObject * unicodedata_UCD_decimal_impl(PyObject *self, PyObject *unichr, PyObject *default_value) -/*[clinic checksum: a0980c387387287e2ac230c37d95b26f6903e0d2]*/ +/*[clinic checksum: 9576fa55f4ea0be82968af39dc9d0283e634beeb]*/ { PyUnicodeObject *v = (PyUnicodeObject *)unichr; int have_old = 0; diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index b223aa7..425de98 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -165,6 +165,7 @@ PyZlib_Free(voidpf ctx, void *ptr) } /*[clinic] + zlib.compress bytes: Py_buffer Binary data to be compressed. @@ -179,9 +180,9 @@ Returns compressed string. [clinic]*/ PyDoc_STRVAR(zlib_compress__doc__, +"compress(bytes, [level])\n" "Returns compressed string.\n" "\n" -"zlib.compress(bytes, [level])\n" " bytes\n" " Binary data to be compressed.\n" " level\n" @@ -226,7 +227,7 @@ zlib_compress(PyModuleDef *module, PyObject *args) static PyObject * zlib_compress_impl(PyModuleDef *module, Py_buffer *bytes, int group_right_1, int level) -/*[clinic checksum: 03e857836db25448d4d572da537eb7faf7695d71]*/ +/*[clinic checksum: f490708eff84be652b5ebe7fe622ab73ac12c888]*/ { PyObject *ReturnVal = NULL; Byte *input, *output = NULL; @@ -742,6 +743,7 @@ save_unconsumed_input(compobject *self, int err) } /*[clinic] + zlib.Decompress.decompress data: Py_buffer @@ -760,9 +762,9 @@ Call the flush() method to clear these buffers. [clinic]*/ PyDoc_STRVAR(zlib_Decompress_decompress__doc__, +"decompress(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" @@ -803,7 +805,7 @@ exit: static PyObject * zlib_Decompress_decompress_impl(PyObject *self, Py_buffer *data, unsigned int max_length) -/*[clinic checksum: f83e91728d327462d7ccbee95299514f26b92253]*/ +/*[clinic checksum: 4683928665a1fa6987f5c57cada4a22807a78fbb]*/ { compobject *zself = (compobject *)self; int err; @@ -1029,16 +1031,15 @@ Return a copy of the compression object. [clinic]*/ PyDoc_STRVAR(zlib_Compress_copy__doc__, -"Return a copy of the compression object.\n" -"\n" -"zlib.Compress.copy()"); +"copy()\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: 8d30351f05defbc2b335c2a78d18f07aa367bb1d]*/ { compobject *zself = (compobject *)self; compobject *retval = NULL; diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 3c1b3bb..bfc730b 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -69,6 +69,11 @@ to the combined-table form. #include "Python.h" #include "stringlib/eq.h" +/*[clinic] +class dict +[clinic]*/ +/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ + typedef struct { /* Cached hash code of me_key. */ Py_hash_t me_hash; @@ -2160,7 +2165,6 @@ dict_richcompare(PyObject *v, PyObject *w, int op) } /*[clinic] -class dict @coexist dict.__contains__ @@ -2172,16 +2176,15 @@ True if D has a key k, else False" [clinic]*/ PyDoc_STRVAR(dict___contains____doc__, -"True if D has a key k, else False\"\n" -"\n" -"dict.__contains__(key)"); +"__contains__(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: 3bbac5ce898ae630d9668fa1c8b3afb645ff22e8]*/ { register PyDictObject *mp = (PyDictObject *)self; Py_hash_t hash; diff --git a/Objects/methodobject.c b/Objects/methodobject.c index 11c8b6e..ca21a68 100644 --- a/Objects/methodobject.c +++ b/Objects/methodobject.c @@ -159,15 +159,75 @@ meth_dealloc(PyCFunctionObject *m) } } +/* + * finds the docstring's introspection signature. + * if present, returns a pointer pointing to the first '('. + * otherwise returns NULL. + */ +static const char *find_signature(PyCFunctionObject *m) +{ + const char *trace = m->m_ml->ml_doc; + const char *name = m->m_ml->ml_name; + size_t length; + if (!trace || !name) + return NULL; + length = strlen(name); + if (strncmp(trace, name, length)) + return NULL; + trace += length; + if (*trace != '(') + return NULL; + return trace; +} + +/* + * skips to the end of the docstring's instrospection signature. + */ +static const char *skip_signature(const char *trace) +{ + while (*trace && *trace != '\n') + trace++; + return trace; +} + +static const char *skip_eols(const char *trace) +{ + while (*trace == '\n') + trace++; + return trace; +} + +static PyObject * +meth_get__text_signature__(PyCFunctionObject *m, void *closure) +{ + const char *start = find_signature(m); + const char *trace; + + if (!start) { + Py_INCREF(Py_None); + return Py_None; + } + + trace = skip_signature(start); + return PyUnicode_FromStringAndSize(start, trace - start); +} + static PyObject * meth_get__doc__(PyCFunctionObject *m, void *closure) { - const char *doc = m->m_ml->ml_doc; + const char *doc = find_signature(m); + + if (doc) + doc = skip_eols(skip_signature(doc)); + else + doc = m->m_ml->ml_doc; + + if (!doc) { + Py_INCREF(Py_None); + return Py_None; + } - if (doc != NULL) - return PyUnicode_FromString(doc); - Py_INCREF(Py_None); - return Py_None; + return PyUnicode_FromString(doc); } static PyObject * @@ -236,6 +296,7 @@ static PyGetSetDef meth_getsets [] = { {"__name__", (getter)meth_get__name__, NULL, NULL}, {"__qualname__", (getter)meth_get__qualname__, NULL, NULL}, {"__self__", (getter)meth_get__self__, NULL, NULL}, + {"__text_signature__", (getter)meth_get__text_signature__, NULL, NULL}, {0} }; diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 1f3164c..34d51e4 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -47,6 +47,11 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #endif +/*[clinic] +class str +[clinic]*/ +/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ + /* --- Globals ------------------------------------------------------------ NOTE: In the interpreter's initialization phase, some globals are currently @@ -12883,7 +12888,6 @@ unicode_swapcase(PyObject *self) } /*[clinic] -class str @staticmethod str.maketrans as unicode_maketrans @@ -12908,10 +12912,9 @@ must be a string, whose characters will be mapped to None in the result. [clinic]*/ PyDoc_STRVAR(unicode_maketrans__doc__, +"maketrans(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" "Character keys will be then converted to ordinals.\n" @@ -12946,7 +12949,7 @@ exit: static PyObject * unicode_maketrans_impl(void *null, PyObject *x, PyObject *y, PyObject *z) -/*[clinic checksum: 6d522e3aea2f2e123da3c5d367132a99d803f9b9]*/ +/*[clinic checksum: 7f76f414a0dfd0c614e0d4717872eeb520516da7]*/ { PyObject *new = NULL, *key, *value; Py_ssize_t i = 0; diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index d34c3da..3c40e06 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -24,17 +24,6 @@ import tempfile import textwrap # TODO: -# converters for -# -# es -# es# -# et -# et# -# s# -# u# -# y# -# z# -# Z# # # soon: # @@ -44,12 +33,6 @@ import textwrap # * max and min use positional only with an optional group # and keyword-only # -# * Generate forward slash for docstring first line -# (if I get positional-only syntax pep accepted) -# -# * Add "version" directive, so we can complain if the file -# is too new for us. -# version = '1' @@ -2441,7 +2424,7 @@ class DSLParser: ## docstring first line ## - add(f.full_name) + add(f.name) add('(') # populate "right_bracket_count" field for every parameter @@ -2498,29 +2481,32 @@ class DSLParser: 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} + spacer_line = False for p in parameters: if not p.docstring.strip(): continue - add('\n') + if spacer_line: + add('\n') + else: + spacer_line = True add(" ") add(p.name) add('\n') add(textwrap.indent(rstrip_lines(p.docstring.rstrip()), " ")) - prototype = output() + parameters = output() + if parameters: + parameters += '\n' ## ## docstring body @@ -2549,21 +2535,26 @@ class DSLParser: 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!') + + if not parameters_marker_count: + # insert after summary line + lines.insert(2, '{parameters}') + + # insert at front of docstring + lines.insert(0, docstring_first_line) 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 -- cgit v0.12