Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 16c5191

Browse files
committed
Issue #20144: Argument Clinic now supports simple constants as parameter
default values. inspect.Signature correspondingly supports them in __text_signature__ fields for builtins.
1 parent 0bce6e7 commit 16c5191

6 files changed

Lines changed: 152 additions & 31 deletions

File tree

Lib/inspect.py

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,18 +1974,60 @@ def from_builtin(cls, func):
19741974

19751975
parameters = []
19761976
empty = Parameter.empty
1977+
invalid = object()
1978+
1979+
def parse_attribute(node):
1980+
if not isinstance(node.ctx, ast.Load):
1981+
return None
1982+
1983+
value = node.value
1984+
o = parse_node(value)
1985+
if o is invalid:
1986+
return invalid
1987+
1988+
if isinstance(value, ast.Name):
1989+
name = o
1990+
if name not in sys.modules:
1991+
return invalid
1992+
o = sys.modules[name]
1993+
1994+
return getattr(o, node.attr, invalid)
1995+
1996+
def parse_node(node):
1997+
if isinstance(node, ast.arg):
1998+
if node.annotation != None:
1999+
raise ValueError("Annotations are not currently supported")
2000+
return node.arg
2001+
if isinstance(node, ast.Num):
2002+
return node.n
2003+
if isinstance(node, ast.Str):
2004+
return node.s
2005+
if isinstance(node, ast.NameConstant):
2006+
return node.value
2007+
if isinstance(node, ast.Attribute):
2008+
return parse_attribute(node)
2009+
if isinstance(node, ast.Name):
2010+
if not isinstance(node.ctx, ast.Load):
2011+
return invalid
2012+
return node.id
2013+
return invalid
19772014

19782015
def p(name_node, default_node, default=empty):
1979-
name = name_node.arg
1980-
1981-
if isinstance(default_node, ast.Num):
1982-
default = default.n
1983-
elif isinstance(default_node, ast.NameConstant):
1984-
default = default_node.value
2016+
name = parse_node(name_node)
2017+
if name is invalid:
2018+
return None
2019+
if default_node:
2020+
o = parse_node(default_node)
2021+
if o is invalid:
2022+
return None
2023+
default = o if o is not invalid else default
19852024
parameters.append(Parameter(name, kind, default=default, annotation=empty))
19862025

19872026
# non-keyword-only parameters
1988-
for name, default in reversed(list(itertools.zip_longest(reversed(f.args.args), reversed(f.args.defaults), fillvalue=None))):
2027+
args = reversed(f.args.args)
2028+
defaults = reversed(f.args.defaults)
2029+
iter = itertools.zip_longest(args, defaults, fillvalue=None)
2030+
for name, default in reversed(list(iter)):
19892031
p(name, default)
19902032

19912033
# *args

Lib/test/test_inspect.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from concurrent.futures import ThreadPoolExecutor
1616
except ImportError:
1717
ThreadPoolExecutor = None
18+
import _testcapi
1819

1920
from test.support import run_unittest, TESTFN, DirsOnSysPath
2021
from test.support import MISSING_C_DOCSTRINGS
@@ -1593,9 +1594,19 @@ def test_signature_on_unsupported_builtins(self):
15931594
@unittest.skipIf(MISSING_C_DOCSTRINGS,
15941595
"Signature information for builtins requires docstrings")
15951596
def test_signature_on_builtins(self):
1597+
# min doesn't have a signature (yet)
15961598
self.assertEqual(inspect.signature(min), None)
1597-
signature = inspect.signature(os.stat)
1599+
1600+
signature = inspect.signature(_testcapi.docstring_with_signature_with_defaults)
15981601
self.assertTrue(isinstance(signature, inspect.Signature))
1602+
def p(name): return signature.parameters[name].default
1603+
self.assertEqual(p('s'), 'avocado')
1604+
self.assertEqual(p('d'), 3.14)
1605+
self.assertEqual(p('i'), 35)
1606+
self.assertEqual(p('c'), sys.maxsize)
1607+
self.assertEqual(p('n'), None)
1608+
self.assertEqual(p('t'), True)
1609+
self.assertEqual(p('f'), False)
15991610

16001611
def test_signature_on_non_function(self):
16011612
with self.assertRaisesRegex(TypeError, 'is not a callable object'):

Misc/NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@ Core and Builtins
1313
Library
1414
-------
1515

16+
- Issue #20144: inspect.Signature now supports parsing simple symbolic
17+
constants as parameter default values in __text_signature__.
18+
1619
- Issue #20072: Fixed multiple errors in tkinter with wantobjects is False.
1720

1821
Tools/Demos
1922
-----------
2023

24+
- Issue #20144: Argument Clinic now supports simple symbolic constants
25+
as parameter default values.
26+
2127
- Issue #20143: The line numbers reported in Argument Clinic errors are
2228
now more accurate.
2329

Modules/_sre.c

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -526,21 +526,58 @@ sre_search(SRE_STATE* state, SRE_CODE* pattern)
526526
return sre_ucs4_search(state, pattern);
527527
}
528528

529-
static PyObject*
530-
pattern_match(PatternObject* self, PyObject* args, PyObject* kw)
529+
/*[clinic]
530+
module _sre
531+
class _sre.SRE_Pattern
532+
533+
_sre.SRE_Pattern.match as pattern_match
534+
535+
self: self(type="PatternObject *")
536+
pattern: object
537+
pos: Py_ssize_t = 0
538+
endpos: Py_ssize_t(c_default="PY_SSIZE_T_MAX") = sys.maxsize
539+
540+
Matches zero or more characters at the beginning of the string.
541+
[clinic]*/
542+
543+
PyDoc_STRVAR(pattern_match__doc__,
544+
"match(pattern, pos=0, endpos=sys.maxsize)\n"
545+
"Matches zero or more characters at the beginning of the string.");
546+
547+
#define PATTERN_MATCH_METHODDEF \
548+
{"match", (PyCFunction)pattern_match, METH_VARARGS|METH_KEYWORDS, pattern_match__doc__},
549+
550+
static PyObject *
551+
pattern_match_impl(PatternObject *self, PyObject *pattern, Py_ssize_t pos, Py_ssize_t endpos);
552+
553+
static PyObject *
554+
pattern_match(PyObject *self, PyObject *args, PyObject *kwargs)
555+
{
556+
PyObject *return_value = NULL;
557+
static char *_keywords[] = {"pattern", "pos", "endpos", NULL};
558+
PyObject *pattern;
559+
Py_ssize_t pos = 0;
560+
Py_ssize_t endpos = PY_SSIZE_T_MAX;
561+
562+
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
563+
"O|nn:match", _keywords,
564+
&pattern, &pos, &endpos))
565+
goto exit;
566+
return_value = pattern_match_impl((PatternObject *)self, pattern, pos, endpos);
567+
568+
exit:
569+
return return_value;
570+
}
571+
572+
static PyObject *
573+
pattern_match_impl(PatternObject *self, PyObject *pattern, Py_ssize_t pos, Py_ssize_t endpos)
574+
/*[clinic checksum: 63e59c5f3019efe6c1f3acdec42b2d3595e14a09]*/
531575
{
532576
SRE_STATE state;
533577
Py_ssize_t status;
578+
PyObject *string;
534579

535-
PyObject* string;
536-
Py_ssize_t start = 0;
537-
Py_ssize_t end = PY_SSIZE_T_MAX;
538-
static char* kwlist[] = { "pattern", "pos", "endpos", NULL };
539-
if (!PyArg_ParseTupleAndKeywords(args, kw, "O|nn:match", kwlist,
540-
&string, &start, &end))
541-
return NULL;
542-
543-
string = state_init(&state, self, string, start, end);
580+
string = state_init(&state, (PatternObject *)self, pattern, pos, endpos);
544581
if (!string)
545582
return NULL;
546583

@@ -556,7 +593,7 @@ pattern_match(PatternObject* self, PyObject* args, PyObject* kw)
556593

557594
state_fini(&state);
558595

559-
return pattern_new_match(self, &state, status);
596+
return (PyObject *)pattern_new_match(self, &state, status);
560597
}
561598

562599
static PyObject*
@@ -1254,10 +1291,6 @@ pattern_repr(PatternObject *obj)
12541291
return result;
12551292
}
12561293

1257-
PyDoc_STRVAR(pattern_match_doc,
1258-
"match(string[, pos[, endpos]]) -> match object or None.\n\
1259-
Matches zero or more characters at the beginning of the string");
1260-
12611294
PyDoc_STRVAR(pattern_fullmatch_doc,
12621295
"fullmatch(string[, pos[, endpos]]) -> match object or None.\n\
12631296
Matches against all of the string");
@@ -1295,8 +1328,7 @@ PyDoc_STRVAR(pattern_subn_doc,
12951328
PyDoc_STRVAR(pattern_doc, "Compiled regular expression objects");
12961329

12971330
static PyMethodDef pattern_methods[] = {
1298-
{"match", (PyCFunction) pattern_match, METH_VARARGS|METH_KEYWORDS,
1299-
pattern_match_doc},
1331+
PATTERN_MATCH_METHODDEF
13001332
{"fullmatch", (PyCFunction) pattern_fullmatch, METH_VARARGS|METH_KEYWORDS,
13011333
pattern_fullmatch_doc},
13021334
{"search", (PyCFunction) pattern_search, METH_VARARGS|METH_KEYWORDS,

Modules/_testcapimodule.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2869,6 +2869,15 @@ PyDoc_STRVAR(docstring_with_signature_and_extra_newlines,
28692869
"This docstring has a valid signature and some extra newlines."
28702870
);
28712871

2872+
PyDoc_STRVAR(docstring_with_signature_with_defaults,
2873+
"docstring_with_signature_with_defaults(s='avocado', d=3.14, i=35, c=sys.maxsize, n=None, t=True, f=False)\n"
2874+
"\n"
2875+
"\n"
2876+
"\n"
2877+
"This docstring has a valid signature with parameters,\n"
2878+
"and the parameters take defaults of varying types."
2879+
);
2880+
28722881
#ifdef WITH_THREAD
28732882
typedef struct {
28742883
PyThread_type_lock start_event;
@@ -3087,6 +3096,9 @@ static PyMethodDef TestMethods[] = {
30873096
{"docstring_with_signature_and_extra_newlines",
30883097
(PyCFunction)test_with_docstring, METH_NOARGS,
30893098
docstring_with_signature_and_extra_newlines},
3099+
{"docstring_with_signature_with_defaults",
3100+
(PyCFunction)test_with_docstring, METH_NOARGS,
3101+
docstring_with_signature_with_defaults},
30903102
#ifdef WITH_THREAD
30913103
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_O,
30923104
PyDoc_STR("set_error_class(error_class) -> None")},

Tools/clinic/clinic.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,15 +1362,15 @@ class CConverter(metaclass=CConverterAutoRegister):
13621362
# Only used by format units ending with '#'.
13631363
length = False
13641364

1365-
def __init__(self, name, function, default=unspecified, *, doc_default=None, required=False, annotation=unspecified, **kwargs):
1365+
def __init__(self, name, function, default=unspecified, *, doc_default=None, c_default=None, py_default=None, required=False, annotation=unspecified, **kwargs):
13661366
self.function = function
13671367
self.name = name
13681368

13691369
if default is not unspecified:
13701370
self.default = default
1371-
self.py_default = py_repr(default)
1371+
self.py_default = py_default if py_default is not None else py_repr(default)
13721372
self.doc_default = doc_default if doc_default is not None else self.py_default
1373-
self.c_default = c_repr(default)
1373+
self.c_default = c_default if c_default is not None else c_repr(default)
13741374
elif doc_default is not None:
13751375
fail(function.fullname + " argument " + name + " specified a 'doc_default' without having a 'default'")
13761376
if annotation != unspecified:
@@ -2315,18 +2315,36 @@ def state_parameter(self, line):
23152315
function_args = module.body[0].args
23162316
parameter = function_args.args[0]
23172317

2318+
py_default = None
2319+
2320+
parameter_name = parameter.arg
2321+
name, legacy, kwargs = self.parse_converter(parameter.annotation)
2322+
23182323
if function_args.defaults:
23192324
expr = function_args.defaults[0]
23202325
# mild hack: explicitly support NULL as a default value
23212326
if isinstance(expr, ast.Name) and expr.id == 'NULL':
23222327
value = NULL
2328+
elif isinstance(expr, ast.Attribute):
2329+
a = []
2330+
n = expr
2331+
while isinstance(n, ast.Attribute):
2332+
a.append(n.attr)
2333+
n = n.value
2334+
if not isinstance(n, ast.Name):
2335+
fail("Malformed default value (looked like a Python constant)")
2336+
a.append(n.id)
2337+
py_default = ".".join(reversed(a))
2338+
value = None
2339+
c_default = kwargs.get("c_default")
2340+
if not (isinstance(c_default, str) and c_default):
2341+
fail("When you specify a named constant (" + repr(py_default) + ") as your default value,\nyou MUST specify a valid c_default.")
2342+
kwargs["py_default"] = py_default
23232343
else:
23242344
value = ast.literal_eval(expr)
23252345
else:
23262346
value = unspecified
23272347

2328-
parameter_name = parameter.arg
2329-
name, legacy, kwargs = self.parse_converter(parameter.annotation)
23302348
dict = legacy_converters if legacy else converters
23312349
legacy_str = "legacy " if legacy else ""
23322350
if name not in dict:

0 commit comments

Comments
 (0)