[Python-checkins] cpython: Issue #19674: inspect.signature() now produces a correct signature

larry.hastings python-checkins at python.org
Sun Nov 24 00:38:34 CET 2013


http://hg.python.org/cpython/rev/78ec18f5cb45
changeset:   87470:78ec18f5cb45
user:        Larry Hastings <larry at hastings.org>
date:        Sat Nov 23 15:37:55 2013 -0800
summary:
  Issue #19674: inspect.signature() now produces a correct signature
for some builtins.

files:
  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
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -31,6 +31,7 @@
 __author__ = ('Ka-Ping Yee <ping at lfw.org>',
               'Yury Selivanov <yselivanov at sprymix.com>')
 
+import ast
 import importlib.machinery
 import itertools
 import linecache
@@ -1461,6 +1462,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 +1946,64 @@
                    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
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -916,20 +916,18 @@
                 reallink = realname
             title = '<a name="%s"><strong>%s</strong></a> = %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 == '<lambda>':
-                title = '<strong>%s</strong> <em>lambda</em> ' % 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 == '<lambda>':
+                    title = '<strong>%s</strong> <em>lambda</em> ' % 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 @@
                 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 == '<lambda>':
-                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 == '<lambda>':
+                    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
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -109,6 +109,35 @@
         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
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -1588,10 +1588,9 @@
         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
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -68,6 +68,9 @@
 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
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -134,6 +134,12 @@
 #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 @@
 /* Addch, Addstr, Addnstr */
 
 /*[clinic]
-module curses
-class curses.window
 
 curses.window.addch
 
@@ -580,9 +584,9 @@
 [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 @@
 
 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
--- 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 @@
 }
 
 /*[clinic]
-module datetime
-class datetime.datetime
 
 @classmethod
 datetime.datetime.now
@@ -4155,9 +4159,9 @@
 [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 @@
 
 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
--- a/Modules/_dbmmodule.c
+++ b/Modules/_dbmmodule.c
@@ -28,6 +28,12 @@
 #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 PyObject *DbmError;
 
-/*[clinic]
-module dbm
-class dbm.dbm
-[clinic]*/
-/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
-
 /*[python]
 class dbmobject_converter(self_converter):
     type = "dbmobject *"
@@ -278,9 +278,8 @@
 [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 @@
 
 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 @@
 [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 @@
 
 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
--- a/Modules/_opcode.c
+++ b/Modules/_opcode.c
@@ -1,11 +1,13 @@
 #include "Python.h"
 #include "opcode.h"
 
+/*[clinic]
+module _opcode
+[clinic]*/
+/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
 
 /*[clinic]
 
-module _opcode
-
 _opcode.stack_effect -> int
 
   opcode: int
@@ -19,18 +21,17 @@
 [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 @@
 }
 
 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
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2842,6 +2842,33 @@
     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 @@
      (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
--- 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]*/
 
-module _weakref
+/*[clinic]
 
 _weakref.getweakrefcount -> Py_ssize_t
 
@@ -17,9 +20,8 @@
 [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 @@
 
 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
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -190,7 +190,10 @@
 #endif  /* ! __WATCOMC__ || __QNX__ */
 
 
-
+/*[clinic]
+module os
+[clinic]*/
+/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
 
 #ifndef _MSC_VER
 
@@ -2404,7 +2407,6 @@
 /*[python checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
 
 /*[clinic]
-module os
 
 os.stat -> object(doc_default='stat_result')
 
@@ -2435,9 +2437,9 @@
 [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 @@
 
 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 @@
 [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 @@
 "    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 @@
 
 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 @@
 [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 @@
 
 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
--- 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 @@
 /* --- Module API --------------------------------------------------------- */
 
 /*[clinic]
-module unicodedata
-class unicodedata.UCD
+
 unicodedata.UCD.decimal
 
     unichr: object(type='str')
@@ -124,10 +129,9 @@
 [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 @@
 
 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
--- a/Modules/zlibmodule.c
+++ b/Modules/zlibmodule.c
@@ -165,6 +165,7 @@
 }
 
 /*[clinic]
+
 zlib.compress
     bytes: Py_buffer
         Binary data to be compressed.
@@ -179,9 +180,9 @@
 [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 @@
 
 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 @@
 }
 
 /*[clinic]
+
 zlib.Decompress.decompress
 
     data: Py_buffer
@@ -760,9 +762,9 @@
 [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 @@
 
 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 @@
 [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
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -69,6 +69,11 @@
 #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 @@
 }
 
 /*[clinic]
-class dict
 
 @coexist
 dict.__contains__
@@ -2172,16 +2176,15 @@
 [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
--- a/Objects/methodobject.c
+++ b/Objects/methodobject.c
@@ -159,15 +159,75 @@
     }
 }
 
+/*
+ * 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 != NULL)
-        return PyUnicode_FromString(doc);
-    Py_INCREF(Py_None);
-    return Py_None;
+    if (doc)
+        doc = skip_eols(skip_signature(doc));
+    else
+        doc = m->m_ml->ml_doc;
+    
+    if (!doc) {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+
+    return PyUnicode_FromString(doc);
 }
 
 static PyObject *
@@ -236,6 +296,7 @@
     {"__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
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -47,6 +47,11 @@
 #include <windows.h>
 #endif
 
+/*[clinic]
+class str
+[clinic]*/
+/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
+
 /* --- Globals ------------------------------------------------------------
 
 NOTE: In the interpreter's initialization phase, some globals are currently
@@ -12883,7 +12888,6 @@
 }
 
 /*[clinic]
-class str
 
 @staticmethod
 str.maketrans as unicode_maketrans
@@ -12908,10 +12912,9 @@
 [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 @@
 
 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
--- a/Tools/clinic/clinic.py
+++ b/Tools/clinic/clinic.py
@@ -24,17 +24,6 @@
 import textwrap
 
 # TODO:
-# converters for
-#
-#       es
-#       es#
-#       et
-#       et#
-#       s#
-#       u#
-#       y#
-#       z#
-#       Z#
 #
 # soon:
 #
@@ -44,12 +33,6 @@
 #       * 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 @@
         ## docstring first line
         ##
 
-        add(f.full_name)
+        add(f.name)
         add('(')
 
         # populate "right_bracket_count" field for every parameter
@@ -2498,29 +2481,32 @@
         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 @@
         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

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list