cpython: c96dba33f019 (original) (raw)
Mercurial > cpython
changeset 88341:c96dba33f019
Issue #20144: Argument Clinic now supports simple constants as parameter default values. inspect.Signature correspondingly supports them in __text_signature__ fields for builtins. [#20144]
Larry Hastings larry@hastings.org | |
---|---|
date | Tue, 07 Jan 2014 11:53:01 -0800 |
parents | e2494137b118 |
children | ddb5cd3e0860 |
files | Lib/inspect.py Lib/test/test_inspect.py Misc/NEWS Modules/_sre.c Modules/_testcapimodule.c Tools/clinic/clinic.py |
diffstat | 6 files changed, 153 insertions(+), 32 deletions(-)[+] [-] 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 |
line wrap: on
line diff
--- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1974,18 +1974,60 @@ class Signature: parameters = [] empty = Parameter.empty
invalid = object()[](#l1.7)
def parse_attribute(node):[](#l1.9)
if not isinstance(node.ctx, ast.Load):[](#l1.10)
return None[](#l1.11)
value = node.value[](#l1.13)
o = parse_node(value)[](#l1.14)
if o is invalid:[](#l1.15)
return invalid[](#l1.16)
if isinstance(value, ast.Name):[](#l1.18)
name = o[](#l1.19)
if name not in sys.modules:[](#l1.20)
return invalid[](#l1.21)
o = sys.modules[name][](#l1.22)
return getattr(o, node.attr, invalid)[](#l1.24)
def parse_node(node):[](#l1.26)
if isinstance(node, ast.arg):[](#l1.27)
if node.annotation != None:[](#l1.28)
raise ValueError("Annotations are not currently supported")[](#l1.29)
return node.arg[](#l1.30)
if isinstance(node, ast.Num):[](#l1.31)
return node.n[](#l1.32)
if isinstance(node, ast.Str):[](#l1.33)
return node.s[](#l1.34)
if isinstance(node, ast.NameConstant):[](#l1.35)
return node.value[](#l1.36)
if isinstance(node, ast.Attribute):[](#l1.37)
return parse_attribute(node)[](#l1.38)
if isinstance(node, ast.Name):[](#l1.39)
if not isinstance(node.ctx, ast.Load):[](#l1.40)
return invalid[](#l1.41)
return node.id[](#l1.42)
return invalid[](#l1.43)
def p(name_node, default_node, default=empty):
name = name_node.arg[](#l1.46)
if isinstance(default_node, ast.Num):[](#l1.48)
default = default.n[](#l1.49)
elif isinstance(default_node, ast.NameConstant):[](#l1.50)
default = default_node.value[](#l1.51)
name = parse_node(name_node)[](#l1.52)
if name is invalid:[](#l1.53)
return None[](#l1.54)
if default_node:[](#l1.55)
o = parse_node(default_node)[](#l1.56)
if o is invalid:[](#l1.57)
return None[](#l1.58)
default = o if o is not invalid else default[](#l1.59) parameters.append(Parameter(name, kind, default=default, annotation=empty))[](#l1.60)
for name, default in reversed(list(itertools.zip_longest(reversed(f.args.args), reversed(f.args.defaults), fillvalue=None))):[](#l1.63)
args = reversed(f.args.args)[](#l1.64)
defaults = reversed(f.args.defaults)[](#l1.65)
iter = itertools.zip_longest(args, defaults, fillvalue=None)[](#l1.66)
for name, default in reversed(list(iter)):[](#l1.67) p(name, default)[](#l1.68)
--- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -15,6 +15,7 @@ try: 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 @@ class TestSignatureObject(unittest.TestC @unittest.skipIf(MISSING_C_DOCSTRINGS, "Signature information for builtins requires docstrings") def test_signature_on_builtins(self):
# min doesn't have a signature (yet)[](#l2.15) self.assertEqual(inspect.signature(min), None)[](#l2.16)
signature = inspect.signature(os.stat)[](#l2.17)
signature = inspect.signature(_testcapi.docstring_with_signature_with_defaults)[](#l2.19) self.assertTrue(isinstance(signature, inspect.Signature))[](#l2.20)
def p(name): return signature.parameters[name].default[](#l2.21)
self.assertEqual(p('s'), 'avocado')[](#l2.22)
self.assertEqual(p('d'), 3.14)[](#l2.23)
self.assertEqual(p('i'), 35)[](#l2.24)
self.assertEqual(p('c'), sys.maxsize)[](#l2.25)
self.assertEqual(p('n'), None)[](#l2.26)
self.assertEqual(p('t'), True)[](#l2.27)
self.assertEqual(p('f'), False)[](#l2.28)
def test_signature_on_non_function(self): with self.assertRaisesRegex(TypeError, 'is not a callable object'):
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,11 +13,17 @@ Core and Builtins Library ------- +- Issue #20144: inspect.Signature now supports parsing simple symbolic
- Issue #20072: Fixed multiple errors in tkinter with wantobjects is False. Tools/Demos ----------- +- Issue #20144: Argument Clinic now supports simple symbolic constants
--- a/Modules/_sre.c +++ b/Modules/_sre.c @@ -526,21 +526,58 @@ sre_search(SRE_STATE* state, SRE_CODE* p 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 [](#l4.27)
+ +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,[](#l4.43)
&pattern, &pos, &endpos))[](#l4.44)
goto exit;[](#l4.45)
- return_value = pattern_match_impl((PatternObject *)self, pattern, pos, endpos);
+} + +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))[](#l4.64)
return NULL;[](#l4.65)
@@ -556,7 +593,7 @@ pattern_match(PatternObject* self, PyObj state_fini(&state);
} static PyObject* @@ -1254,10 +1291,6 @@ done: return result; } -PyDoc_STRVAR(pattern_match_doc, -"match(string[, pos[, endpos]]) -> match object or None.\n[](#l4.88)
- PyDoc_STRVAR(pattern_fullmatch_doc, "fullmatch(string[, pos[, endpos]]) -> match object or None.\n[](#l4.92) Matches against all of the string"); @@ -1295,8 +1328,7 @@ PyDoc_STRVAR(pattern_subn_doc, PyDoc_STRVAR(pattern_doc, "Compiled regular expression objects"); static PyMethodDef pattern_methods[] = {
- PATTERN_MATCH_METHODDEF {"fullmatch", (PyCFunction) pattern_fullmatch, METH_VARARGS|METH_KEYWORDS, pattern_fullmatch_doc}, {"search", (PyCFunction) pattern_search, METH_VARARGS|METH_KEYWORDS,
--- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2869,6 +2869,15 @@ PyDoc_STRVAR(docstring_with_signature_an "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 @@ static PyMethodDef TestMethods[] = { {"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,[](#l5.24)
docstring_with_signature_with_defaults},[](#l5.25)
#ifdef WITH_THREAD {"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_O, PyDoc_STR("set_error_class(error_class) -> None")},
--- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -1362,15 +1362,15 @@ class CConverter(metaclass=CConverterAut # 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)[](#l6.14)
self.py_default = py_default if py_default is not None else py_repr(default)[](#l6.15) self.doc_default = doc_default if doc_default is not None else self.py_default[](#l6.16)
self.c_default = c_repr(default)[](#l6.17)
self.c_default = c_default if c_default is not None else c_repr(default)[](#l6.18) elif doc_default is not None:[](#l6.19) fail(function.fullname + " argument " + name + " specified a 'doc_default' without having a 'default'")[](#l6.20) if annotation != unspecified:[](#l6.21)
@@ -2315,18 +2315,36 @@ class DSLParser: function_args = module.body[0].args parameter = function_args.args[0]
py_default = None[](#l6.26)
parameter_name = parameter.arg[](#l6.28)
name, legacy, kwargs = self.parse_converter(parameter.annotation)[](#l6.29)
+ 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):[](#l6.36)
a = [][](#l6.37)
n = expr[](#l6.38)
while isinstance(n, ast.Attribute):[](#l6.39)
a.append(n.attr)[](#l6.40)
n = n.value[](#l6.41)
if not isinstance(n, ast.Name):[](#l6.42)
fail("Malformed default value (looked like a Python constant)")[](#l6.43)
a.append(n.id)[](#l6.44)
py_default = ".".join(reversed(a))[](#l6.45)
value = None[](#l6.46)
c_default = kwargs.get("c_default")[](#l6.47)
if not (isinstance(c_default, str) and c_default):[](#l6.48)
fail("When you specify a named constant (" + repr(py_default) + ") as your default value,\nyou MUST specify a valid c_default.")[](#l6.49)
kwargs["py_default"] = py_default[](#l6.50) else:[](#l6.51) value = ast.literal_eval(expr)[](#l6.52) else:[](#l6.53) value = unspecified[](#l6.54)
parameter_name = parameter.arg[](#l6.56)
name, legacy, kwargs = self.parse_converter(parameter.annotation)[](#l6.57) dict = legacy_converters if legacy else converters[](#l6.58) legacy_str = "legacy " if legacy else ""[](#l6.59) if name not in dict:[](#l6.60)