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

Skip to content

Commit 44e2eaa

Browse files
committed
Issue #19674: inspect.signature() now produces a correct signature
for some builtins.
1 parent 7fa6e1a commit 44e2eaa

18 files changed

Lines changed: 343 additions & 136 deletions

Lib/inspect.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
__author__ = ('Ka-Ping Yee <[email protected]>',
3232
'Yury Selivanov <[email protected]>')
3333

34+
import ast
3435
import importlib.machinery
3536
import itertools
3637
import linecache
@@ -1461,6 +1462,9 @@ def signature(obj):
14611462
if isinstance(obj, types.FunctionType):
14621463
return Signature.from_function(obj)
14631464

1465+
if isinstance(obj, types.BuiltinFunctionType):
1466+
return Signature.from_builtin(obj)
1467+
14641468
if isinstance(obj, functools.partial):
14651469
sig = signature(obj.func)
14661470

@@ -1942,6 +1946,64 @@ def from_function(cls, func):
19421946
return_annotation=annotations.get('return', _empty),
19431947
__validate_parameters__=False)
19441948

1949+
@classmethod
1950+
def from_builtin(cls, func):
1951+
s = getattr(func, "__text_signature__", None)
1952+
if not s:
1953+
return None
1954+
1955+
if s.endswith("/)"):
1956+
kind = Parameter.POSITIONAL_ONLY
1957+
s = s[:-2] + ')'
1958+
else:
1959+
kind = Parameter.POSITIONAL_OR_KEYWORD
1960+
1961+
s = "def foo" + s + ": pass"
1962+
1963+
try:
1964+
module = ast.parse(s)
1965+
except SyntaxError:
1966+
return None
1967+
if not isinstance(module, ast.Module):
1968+
return None
1969+
1970+
# ast.FunctionDef
1971+
f = module.body[0]
1972+
1973+
parameters = []
1974+
empty = Parameter.empty
1975+
1976+
def p(name_node, default_node, default=empty):
1977+
name = name_node.arg
1978+
1979+
if isinstance(default_node, ast.Num):
1980+
default = default.n
1981+
elif isinstance(default_node, ast.NameConstant):
1982+
default = default_node.value
1983+
parameters.append(Parameter(name, kind, default=default, annotation=empty))
1984+
1985+
# non-keyword-only parameters
1986+
for name, default in reversed(list(itertools.zip_longest(reversed(f.args.args), reversed(f.args.defaults), fillvalue=None))):
1987+
p(name, default)
1988+
1989+
# *args
1990+
if f.args.vararg:
1991+
kind = Parameter.VAR_POSITIONAL
1992+
p(f.args.vararg, empty)
1993+
1994+
# keyword-only arguments
1995+
kind = Parameter.KEYWORD_ONLY
1996+
for name, default in zip(f.args.kwonlyargs, f.args.kw_defaults):
1997+
p(name, default)
1998+
1999+
# **kwargs
2000+
if f.args.kwarg:
2001+
kind = Parameter.VAR_KEYWORD
2002+
p(f.args.kwarg, empty)
2003+
2004+
return cls(parameters, return_annotation=cls.empty)
2005+
2006+
19452007
@property
19462008
def parameters(self):
19472009
return self._parameters

Lib/pydoc.py

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -916,20 +916,18 @@ def docroutine(self, object, name=None, mod=None,
916916
reallink = realname
917917
title = '<a name="%s"><strong>%s</strong></a> = %s' % (
918918
anchor, name, reallink)
919-
if inspect.isfunction(object):
920-
args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann = \
921-
inspect.getfullargspec(object)
922-
argspec = inspect.formatargspec(
923-
args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann,
924-
formatvalue=self.formatvalue,
925-
formatannotation=inspect.formatannotationrelativeto(object))
926-
if realname == '<lambda>':
927-
title = '<strong>%s</strong> <em>lambda</em> ' % name
928-
# XXX lambda's won't usually have func_annotations['return']
929-
# since the syntax doesn't support but it is possible.
930-
# So removing parentheses isn't truly safe.
931-
argspec = argspec[1:-1] # remove parentheses
932-
else:
919+
argspec = None
920+
if inspect.isfunction(object) or inspect.isbuiltin(object):
921+
signature = inspect.signature(object)
922+
if signature:
923+
argspec = str(signature)
924+
if realname == '<lambda>':
925+
title = '<strong>%s</strong> <em>lambda</em> ' % name
926+
# XXX lambda's won't usually have func_annotations['return']
927+
# since the syntax doesn't support but it is possible.
928+
# So removing parentheses isn't truly safe.
929+
argspec = argspec[1:-1] # remove parentheses
930+
if not argspec:
933931
argspec = '(...)'
934932

935933
decl = title + argspec + (note and self.grey(
@@ -1313,20 +1311,18 @@ def docroutine(self, object, name=None, mod=None, cl=None):
13131311
cl.__dict__[realname] is object):
13141312
skipdocs = 1
13151313
title = self.bold(name) + ' = ' + realname
1316-
if inspect.isfunction(object):
1317-
args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann = \
1318-
inspect.getfullargspec(object)
1319-
argspec = inspect.formatargspec(
1320-
args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann,
1321-
formatvalue=self.formatvalue,
1322-
formatannotation=inspect.formatannotationrelativeto(object))
1323-
if realname == '<lambda>':
1324-
title = self.bold(name) + ' lambda '
1325-
# XXX lambda's won't usually have func_annotations['return']
1326-
# since the syntax doesn't support but it is possible.
1327-
# So removing parentheses isn't truly safe.
1328-
argspec = argspec[1:-1] # remove parentheses
1329-
else:
1314+
argspec = None
1315+
if inspect.isfunction(object) or inspect.isbuiltin(object):
1316+
signature = inspect.signature(object)
1317+
if signature:
1318+
argspec = str(signature)
1319+
if realname == '<lambda>':
1320+
title = self.bold(name) + ' lambda '
1321+
# XXX lambda's won't usually have func_annotations['return']
1322+
# since the syntax doesn't support but it is possible.
1323+
# So removing parentheses isn't truly safe.
1324+
argspec = argspec[1:-1] # remove parentheses
1325+
if not argspec:
13301326
argspec = '(...)'
13311327
decl = title + argspec + note
13321328

Lib/test/test_capi.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,35 @@ def __len__(self):
109109
self.assertRaises(TypeError, _posixsubprocess.fork_exec,
110110
Z(),[b'1'],3,[1, 2],5,6,7,8,9,10,11,12,13,14,15,16,17)
111111

112+
def test_docstring_signature_parsing(self):
113+
114+
self.assertEqual(_testcapi.no_docstring.__doc__, None)
115+
self.assertEqual(_testcapi.no_docstring.__text_signature__, None)
116+
117+
self.assertEqual(_testcapi.docstring_empty.__doc__, "")
118+
self.assertEqual(_testcapi.docstring_empty.__text_signature__, None)
119+
120+
self.assertEqual(_testcapi.docstring_no_signature.__doc__,
121+
"This docstring has no signature.")
122+
self.assertEqual(_testcapi.docstring_no_signature.__text_signature__, None)
123+
124+
self.assertEqual(_testcapi.docstring_with_invalid_signature.__doc__,
125+
"docstring_with_invalid_signature (boo)\n"
126+
"\n"
127+
"This docstring has an invalid signature."
128+
)
129+
self.assertEqual(_testcapi.docstring_with_invalid_signature.__text_signature__, None)
130+
131+
self.assertEqual(_testcapi.docstring_with_signature.__doc__,
132+
"This docstring has a valid signature.")
133+
self.assertEqual(_testcapi.docstring_with_signature.__text_signature__, "(sig)")
134+
135+
self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__doc__,
136+
"This docstring has a valid signature and some extra newlines.")
137+
self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__text_signature__,
138+
"(parameter)")
139+
140+
112141
@unittest.skipUnless(threading, 'Threading required for this test.')
113142
class TestPendingCalls(unittest.TestCase):
114143

Lib/test/test_inspect.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,10 +1588,9 @@ def test_signature_on_builtin_function(self):
15881588
with self.assertRaisesRegex(ValueError, 'not supported by signature'):
15891589
# support for 'method-wrapper'
15901590
inspect.signature(min.__call__)
1591-
with self.assertRaisesRegex(ValueError,
1592-
'no signature found for builtin function'):
1593-
# support for 'method-wrapper'
1594-
inspect.signature(min)
1591+
self.assertEqual(inspect.signature(min), None)
1592+
signature = inspect.signature(os.stat)
1593+
self.assertTrue(isinstance(signature, inspect.Signature))
15951594

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

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ Core and Builtins
6868
Library
6969
-------
7070

71+
- Issue #19674: inspect.signature() now produces a correct signature
72+
for some builtins.
73+
7174
- Issue #19722: Added opcode.stack_effect(), which
7275
computes the stack effect of bytecode instructions.
7376

Modules/_cursesmodule.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ typedef chtype attr_t; /* No attr_t type is available */
134134
#define STRICT_SYSV_CURSES
135135
#endif
136136

137+
/*[clinic]
138+
module curses
139+
class curses.window
140+
[clinic]*/
141+
/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
142+
137143
/* Definition of exception curses.error */
138144

139145
static PyObject *PyCursesError;
@@ -550,8 +556,6 @@ PyCursesWindow_Dealloc(PyCursesWindowObject *wo)
550556
/* Addch, Addstr, Addnstr */
551557

552558
/*[clinic]
553-
module curses
554-
class curses.window
555559
556560
curses.window.addch
557561
@@ -580,9 +584,9 @@ current settings for the window object.
580584
[clinic]*/
581585

582586
PyDoc_STRVAR(curses_window_addch__doc__,
587+
"addch([x, y,] ch, [attr])\n"
583588
"Paint character ch at (y, x) with attributes attr.\n"
584589
"\n"
585-
"curses.window.addch([x, y,] ch, [attr])\n"
586590
" x\n"
587591
" X-coordinate.\n"
588592
" y\n"
@@ -646,7 +650,7 @@ curses_window_addch(PyObject *self, PyObject *args)
646650

647651
static PyObject *
648652
curses_window_addch_impl(PyObject *self, int group_left_1, int x, int y, PyObject *ch, int group_right_1, long attr)
649-
/*[clinic checksum: 094d012af1019387c0219a9c0bc76e90729c833f]*/
653+
/*[clinic checksum: 44ed958b891cde91205e584c766e048f3999714f]*/
650654
{
651655
PyCursesWindowObject *cwself = (PyCursesWindowObject *)self;
652656
int coordinates_group = group_left_1;

Modules/_datetimemodule.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
#include "datetime.h"
1717
#undef Py_BUILD_CORE
1818

19+
/*[clinic]
20+
module datetime
21+
class datetime.datetime
22+
[clinic]*/
23+
/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
24+
1925
/* We require that C int be at least 32 bits, and use int virtually
2026
* everywhere. In just a few cases we use a temp long, where a Python
2127
* 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)
41404146
}
41414147

41424148
/*[clinic]
4143-
module datetime
4144-
class datetime.datetime
41454149
41464150
@classmethod
41474151
datetime.datetime.now
@@ -4155,9 +4159,9 @@ If no tz is specified, uses local timezone.
41554159
[clinic]*/
41564160

41574161
PyDoc_STRVAR(datetime_datetime_now__doc__,
4162+
"now(tz=None)\n"
41584163
"Returns new datetime object representing current time local to tz.\n"
41594164
"\n"
4160-
"datetime.datetime.now(tz=None)\n"
41614165
" tz\n"
41624166
" Timezone object.\n"
41634167
"\n"
@@ -4188,7 +4192,7 @@ datetime_datetime_now(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
41884192

41894193
static PyObject *
41904194
datetime_datetime_now_impl(PyTypeObject *cls, PyObject *tz)
4191-
/*[clinic checksum: 5e61647d5d1feaf1ab096c5406ccea17bb7b061c]*/
4195+
/*[clinic checksum: ca3d26a423b3f633b260c7622e303f0915a96f7c]*/
41924196
{
41934197
PyObject *self;
41944198

Modules/_dbmmodule.c

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ static char *which_dbm = "Berkeley DB";
2828
#error "No ndbm.h available!"
2929
#endif
3030

31+
/*[clinic]
32+
module dbm
33+
class dbm.dbm
34+
[clinic]*/
35+
/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
36+
3137
typedef struct {
3238
PyObject_HEAD
3339
int di_size; /* -1 means recompute */
@@ -43,12 +49,6 @@ static PyTypeObject Dbmtype;
4349

4450
static PyObject *DbmError;
4551

46-
/*[clinic]
47-
module dbm
48-
class dbm.dbm
49-
[clinic]*/
50-
/*[clinic checksum: da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
51-
5252
/*[python]
5353
class dbmobject_converter(self_converter):
5454
type = "dbmobject *"
@@ -278,9 +278,8 @@ Return the value for key if present, otherwise default.
278278
[clinic]*/
279279

280280
PyDoc_STRVAR(dbm_dbm_get__doc__,
281-
"Return the value for key if present, otherwise default.\n"
282-
"\n"
283-
"dbm.dbm.get(key, [default])");
281+
"get(key, [default])\n"
282+
"Return the value for key if present, otherwise default.");
284283

285284
#define DBM_DBM_GET_METHODDEF \
286285
{"get", (PyCFunction)dbm_dbm_get, METH_VARARGS, dbm_dbm_get__doc__},
@@ -318,7 +317,7 @@ dbm_dbm_get(PyObject *self, PyObject *args)
318317

319318
static PyObject *
320319
dbm_dbm_get_impl(dbmobject *dp, const char *key, Py_ssize_clean_t key_length, int group_right_1, PyObject *default_value)
321-
/*[clinic checksum: 5b4265e66568f163ef0fc7efec09410eaf793508]*/
320+
/*[clinic checksum: 28cf8928811bde51e535d67ae98ea039d79df717]*/
322321
{
323322
datum dbm_key, val;
324323

@@ -461,9 +460,9 @@ Return a database object.
461460
[clinic]*/
462461

463462
PyDoc_STRVAR(dbmopen__doc__,
463+
"open(filename, flags=\'r\', mode=0o666)\n"
464464
"Return a database object.\n"
465465
"\n"
466-
"dbm.open(filename, flags=\'r\', mode=0o666)\n"
467466
" filename\n"
468467
" The filename to open.\n"
469468
" flags\n"
@@ -498,7 +497,7 @@ dbmopen(PyModuleDef *module, PyObject *args)
498497

499498
static PyObject *
500499
dbmopen_impl(PyModuleDef *module, const char *filename, const char *flags, int mode)
501-
/*[clinic checksum: c1f2036017ec36a43ac6f59893732751e67c19d5]*/
500+
/*[clinic checksum: fb265f75641553ccd963f84c143b35c11f9121fc]*/
502501
{
503502
int iflags;
504503

0 commit comments

Comments
 (0)