[Python-checkins] cpython: Issue #20144: Argument Clinic now supports simple constants as parameter

larry.hastings python-checkins at python.org
Tue Jan 7 20:53:55 CET 2014


http://hg.python.org/cpython/rev/c96dba33f019
changeset:   88341:c96dba33f019
user:        Larry Hastings <larry at hastings.org>
date:        Tue Jan 07 11:53:01 2014 -0800
summary:
  Issue #20144: Argument Clinic now supports simple constants as parameter
default values.  inspect.Signature correspondingly supports them in
__text_signature__ fields for builtins.

files:
  Lib/inspect.py            |  56 ++++++++++++++++++--
  Lib/test/test_inspect.py  |  13 ++++-
  Misc/NEWS                 |   6 ++
  Modules/_sre.c            |  70 +++++++++++++++++++-------
  Modules/_testcapimodule.c |  12 ++++
  Tools/clinic/clinic.py    |  28 ++++++++-
  6 files changed, 153 insertions(+), 32 deletions(-)


diff --git a/Lib/inspect.py b/Lib/inspect.py
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -1974,18 +1974,60 @@
 
         parameters = []
         empty = Parameter.empty
+        invalid = object()
+
+        def parse_attribute(node):
+            if not isinstance(node.ctx, ast.Load):
+                return None
+
+            value = node.value
+            o = parse_node(value)
+            if o is invalid:
+                return invalid
+
+            if isinstance(value, ast.Name):
+                name = o
+                if name not in sys.modules:
+                    return invalid
+                o = sys.modules[name]
+
+            return getattr(o, node.attr, invalid)
+
+        def parse_node(node):
+            if isinstance(node, ast.arg):
+                if node.annotation != None:
+                    raise ValueError("Annotations are not currently supported")
+                return node.arg
+            if isinstance(node, ast.Num):
+                return node.n
+            if isinstance(node, ast.Str):
+                return node.s
+            if isinstance(node, ast.NameConstant):
+                return node.value
+            if isinstance(node, ast.Attribute):
+                return parse_attribute(node)
+            if isinstance(node, ast.Name):
+                if not isinstance(node.ctx, ast.Load):
+                    return invalid
+                return node.id
+            return invalid
 
         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
+            name = parse_node(name_node)
+            if name is invalid:
+                return None
+            if default_node:
+                o = parse_node(default_node)
+                if o is invalid:
+                    return None
+                default = o if o is not invalid else default
             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))):
+        args = reversed(f.args.args)
+        defaults = reversed(f.args.defaults)
+        iter = itertools.zip_longest(args, defaults, fillvalue=None)
+        for name, default in reversed(list(iter)):
             p(name, default)
 
         # *args
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
@@ -15,6 +15,7 @@
     from concurrent.futures import ThreadPoolExecutor
 except ImportError:
     ThreadPoolExecutor = None
+import _testcapi
 
 from test.support import run_unittest, TESTFN, DirsOnSysPath
 from test.support import MISSING_C_DOCSTRINGS
@@ -1593,9 +1594,19 @@
     @unittest.skipIf(MISSING_C_DOCSTRINGS,
                      "Signature information for builtins requires docstrings")
     def test_signature_on_builtins(self):
+        # min doesn't have a signature (yet)
         self.assertEqual(inspect.signature(min), None)
-        signature = inspect.signature(os.stat)
+
+        signature = inspect.signature(_testcapi.docstring_with_signature_with_defaults)
         self.assertTrue(isinstance(signature, inspect.Signature))
+        def p(name): return signature.parameters[name].default
+        self.assertEqual(p('s'), 'avocado')
+        self.assertEqual(p('d'), 3.14)
+        self.assertEqual(p('i'), 35)
+        self.assertEqual(p('c'), sys.maxsize)
+        self.assertEqual(p('n'), None)
+        self.assertEqual(p('t'), True)
+        self.assertEqual(p('f'), False)
 
     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
@@ -13,11 +13,17 @@
 Library
 -------
 
+- Issue #20144: inspect.Signature now supports parsing simple symbolic
+  constants as parameter default values in __text_signature__.
+
 - Issue #20072: Fixed multiple errors in tkinter with wantobjects is False.
 
 Tools/Demos
 -----------
 
+- Issue #20144: Argument Clinic now supports simple symbolic constants
+  as parameter default values.
+
 - Issue #20143: The line numbers reported in Argument Clinic errors are
   now more accurate.
 
diff --git a/Modules/_sre.c b/Modules/_sre.c
--- a/Modules/_sre.c
+++ b/Modules/_sre.c
@@ -526,21 +526,58 @@
     return sre_ucs4_search(state, pattern);
 }
 
-static PyObject*
-pattern_match(PatternObject* self, PyObject* args, PyObject* kw)
+/*[clinic]
+module _sre
+class _sre.SRE_Pattern
+
+_sre.SRE_Pattern.match as pattern_match
+
+    self: self(type="PatternObject *")
+    pattern: object
+    pos: Py_ssize_t = 0
+    endpos: Py_ssize_t(c_default="PY_SSIZE_T_MAX") = sys.maxsize
+
+Matches zero or more characters at the beginning of the string.
+[clinic]*/
+
+PyDoc_STRVAR(pattern_match__doc__,
+"match(pattern, pos=0, endpos=sys.maxsize)\n"
+"Matches zero or more characters at the beginning of the string.");
+
+#define PATTERN_MATCH_METHODDEF    \
+    {"match", (PyCFunction)pattern_match, METH_VARARGS|METH_KEYWORDS, pattern_match__doc__},
+
+static PyObject *
+pattern_match_impl(PatternObject *self, PyObject *pattern, Py_ssize_t pos, Py_ssize_t endpos);
+
+static PyObject *
+pattern_match(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    PyObject *return_value = NULL;
+    static char *_keywords[] = {"pattern", "pos", "endpos", NULL};
+    PyObject *pattern;
+    Py_ssize_t pos = 0;
+    Py_ssize_t endpos = PY_SSIZE_T_MAX;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+        "O|nn:match", _keywords,
+        &pattern, &pos, &endpos))
+        goto exit;
+    return_value = pattern_match_impl((PatternObject *)self, pattern, pos, endpos);
+
+exit:
+    return return_value;
+}
+
+static PyObject *
+pattern_match_impl(PatternObject *self, PyObject *pattern, Py_ssize_t pos, Py_ssize_t endpos)
+/*[clinic checksum: 63e59c5f3019efe6c1f3acdec42b2d3595e14a09]*/
 {
     SRE_STATE state;
     Py_ssize_t status;
-
-    PyObject* string;
-    Py_ssize_t start = 0;
-    Py_ssize_t end = PY_SSIZE_T_MAX;
-    static char* kwlist[] = { "pattern", "pos", "endpos", NULL };
-    if (!PyArg_ParseTupleAndKeywords(args, kw, "O|nn:match", kwlist,
-                                     &string, &start, &end))
-        return NULL;
-
-    string = state_init(&state, self, string, start, end);
+    PyObject *string;
+
+    string = state_init(&state, (PatternObject *)self, pattern, pos, endpos);
     if (!string)
         return NULL;
 
@@ -556,7 +593,7 @@
 
     state_fini(&state);
 
-    return pattern_new_match(self, &state, status);
+    return (PyObject *)pattern_new_match(self, &state, status);
 }
 
 static PyObject*
@@ -1254,10 +1291,6 @@
     return result;
 }
 
-PyDoc_STRVAR(pattern_match_doc,
-"match(string[, pos[, endpos]]) -> match object or None.\n\
-    Matches zero or more characters at the beginning of the string");
-
 PyDoc_STRVAR(pattern_fullmatch_doc,
 "fullmatch(string[, pos[, endpos]]) -> match object or None.\n\
     Matches against all of the string");
@@ -1295,8 +1328,7 @@
 PyDoc_STRVAR(pattern_doc, "Compiled regular expression objects");
 
 static PyMethodDef pattern_methods[] = {
-    {"match", (PyCFunction) pattern_match, METH_VARARGS|METH_KEYWORDS,
-        pattern_match_doc},
+    PATTERN_MATCH_METHODDEF
     {"fullmatch", (PyCFunction) pattern_fullmatch, METH_VARARGS|METH_KEYWORDS,
         pattern_fullmatch_doc},
     {"search", (PyCFunction) pattern_search, METH_VARARGS|METH_KEYWORDS,
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2869,6 +2869,15 @@
 "This docstring has a valid signature and some extra newlines."
 );
 
+PyDoc_STRVAR(docstring_with_signature_with_defaults,
+"docstring_with_signature_with_defaults(s='avocado', d=3.14, i=35, c=sys.maxsize, n=None, t=True, f=False)\n"
+"\n"
+"\n"
+"\n"
+"This docstring has a valid signature with parameters,\n"
+"and the parameters take defaults of varying types."
+);
+
 #ifdef WITH_THREAD
 typedef struct {
     PyThread_type_lock start_event;
@@ -3087,6 +3096,9 @@
     {"docstring_with_signature_and_extra_newlines",
         (PyCFunction)test_with_docstring, METH_NOARGS,
         docstring_with_signature_and_extra_newlines},
+    {"docstring_with_signature_with_defaults",
+        (PyCFunction)test_with_docstring, METH_NOARGS,
+        docstring_with_signature_with_defaults},
 #ifdef WITH_THREAD
     {"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_O,
      PyDoc_STR("set_error_class(error_class) -> None")},
diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py
--- a/Tools/clinic/clinic.py
+++ b/Tools/clinic/clinic.py
@@ -1362,15 +1362,15 @@
     # Only used by format units ending with '#'.
     length = False
 
-    def __init__(self, name, function, default=unspecified, *, doc_default=None, required=False, annotation=unspecified, **kwargs):
+    def __init__(self, name, function, default=unspecified, *, doc_default=None, c_default=None, py_default=None, required=False, annotation=unspecified, **kwargs):
         self.function = function
         self.name = name
 
         if default is not unspecified:
             self.default = default
-            self.py_default = py_repr(default)
+            self.py_default = py_default if py_default is not None else py_repr(default)
             self.doc_default = doc_default if doc_default is not None else self.py_default
-            self.c_default = c_repr(default)
+            self.c_default = c_default if c_default is not None else c_repr(default)
         elif doc_default is not None:
             fail(function.fullname + " argument " + name + " specified a 'doc_default' without having a 'default'")
         if annotation != unspecified:
@@ -2315,18 +2315,36 @@
         function_args = module.body[0].args
         parameter = function_args.args[0]
 
+        py_default = None
+
+        parameter_name = parameter.arg
+        name, legacy, kwargs = self.parse_converter(parameter.annotation)
+
         if function_args.defaults:
             expr = function_args.defaults[0]
             # mild hack: explicitly support NULL as a default value
             if isinstance(expr, ast.Name) and expr.id == 'NULL':
                 value = NULL
+            elif isinstance(expr, ast.Attribute):
+                a = []
+                n = expr
+                while isinstance(n, ast.Attribute):
+                    a.append(n.attr)
+                    n = n.value
+                if not isinstance(n, ast.Name):
+                    fail("Malformed default value (looked like a Python constant)")
+                a.append(n.id)
+                py_default = ".".join(reversed(a))
+                value = None
+                c_default = kwargs.get("c_default")
+                if not (isinstance(c_default, str) and c_default):
+                    fail("When you specify a named constant (" + repr(py_default) + ") as your default value,\nyou MUST specify a valid c_default.")
+                kwargs["py_default"] = py_default
             else:
                 value = ast.literal_eval(expr)
         else:
             value = unspecified
 
-        parameter_name = parameter.arg
-        name, legacy, kwargs = self.parse_converter(parameter.annotation)
         dict = legacy_converters if legacy else converters
         legacy_str = "legacy " if legacy else ""
         if name not in dict:

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


More information about the Python-checkins mailing list