Skip to content

Commit 646f6c3

Browse files
authored
[3.6] bpo-19903: IDLE: Calltips changed to use inspect.signature (GH-2822) (#3053)
Idlelib.calltips.get_argspec now uses inspect.signature instead of inspect.getfullargspec, like help() does. This improves the signature in the call tip in a few different cases, including builtins converted to provide a signature. A message is added if the object is not callable, has an invalid signature, or if it has positional-only parameters. Patch by Louie Lu.. (cherry picked from commit 3b0f620)
1 parent 4388b42 commit 646f6c3

File tree

3 files changed

+60
-23
lines changed

3 files changed

+60
-23
lines changed

Lib/idlelib/calltips.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ def get_entity(expression):
123123
_INDENT = ' '*4 # for wrapped signatures
124124
_first_param = re.compile(r'(?<=\()\w*\,?\s*')
125125
_default_callable_argspec = "See source or doc"
126+
_invalid_method = "invalid method signature"
127+
_argument_positional = "\n['/' marks preceding arguments as positional-only]\n"
126128

127129

128130
def get_argspec(ob):
@@ -134,25 +136,30 @@ def get_argspec(ob):
134136
empty line or _MAX_LINES. For builtins, this typically includes
135137
the arguments in addition to the return value.
136138
'''
137-
argspec = ""
139+
argspec = default = ""
138140
try:
139141
ob_call = ob.__call__
140142
except BaseException:
141-
return argspec
142-
if isinstance(ob, type):
143-
fob = ob.__init__
144-
elif isinstance(ob_call, types.MethodType):
145-
fob = ob_call
146-
else:
147-
fob = ob
148-
if isinstance(fob, (types.FunctionType, types.MethodType)):
149-
argspec = inspect.formatargspec(*inspect.getfullargspec(fob))
150-
if (isinstance(ob, (type, types.MethodType)) or
151-
isinstance(ob_call, types.MethodType)):
152-
argspec = _first_param.sub("", argspec)
143+
return default
144+
145+
fob = ob_call if isinstance(ob_call, types.MethodType) else ob
146+
147+
try:
148+
argspec = str(inspect.signature(fob))
149+
except ValueError as err:
150+
msg = str(err)
151+
if msg.startswith(_invalid_method):
152+
return _invalid_method
153+
154+
if '/' in argspec:
155+
"""Using AC's positional argument should add the explain"""
156+
argspec += _argument_positional
157+
if isinstance(fob, type) and argspec == '()':
158+
"""fob with no argument, use default callable argspec"""
159+
argspec = _default_callable_argspec
153160

154161
lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
155-
if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
162+
if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
156163

157164
if isinstance(ob_call, types.MethodType):
158165
doc = ob_call.__doc__
@@ -171,6 +178,7 @@ def get_argspec(ob):
171178
argspec = _default_callable_argspec
172179
return argspec
173180

181+
174182
if __name__ == '__main__':
175183
from unittest import main
176184
main('idlelib.idle_test.test_calltips', verbosity=2)

Lib/idlelib/idle_test/test_calltips.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,37 @@ def test_builtins(self):
4646

4747
# Python class that inherits builtin methods
4848
class List(list): "List() doc"
49+
4950
# Simulate builtin with no docstring for default tip test
5051
class SB: __call__ = None
5152

5253
def gtest(obj, out):
5354
self.assertEqual(signature(obj), out)
5455

5556
if List.__doc__ is not None:
56-
gtest(List, List.__doc__)
57+
gtest(List, List.__doc__) # This and append_doc changed in 3.7.
5758
gtest(list.__new__,
58-
'Create and return a new object. See help(type) for accurate signature.')
59+
'(*args, **kwargs)\nCreate and return a new object.'
60+
' See help(type) for accurate signature.')
5961
gtest(list.__init__,
62+
'(self, /, *args, **kwargs)' + ct._argument_positional + '\n' +
6063
'Initialize self. See help(type(self)) for accurate signature.')
61-
append_doc = "L.append(object) -> None -- append object to end" #see3.7
64+
65+
append_doc = "L.append(object) -> None -- append object to end"
6266
gtest(list.append, append_doc)
6367
gtest([].append, append_doc)
6468
gtest(List.append, append_doc)
6569

6670
gtest(types.MethodType, "method(function, instance)")
6771
gtest(SB(), default_tip)
72+
import re
73+
p = re.compile('')
74+
gtest(re.sub, '''(pattern, repl, string, count=0, flags=0)\nReturn the string obtained by replacing the leftmost
75+
non-overlapping occurrences of the pattern in string by the
76+
replacement repl. repl can be either a string or a callable;
77+
if a string, backslash escapes in it are processed. If it is
78+
a callable, it's passed the match object and must return''')
79+
gtest(p.sub, '''(repl, string, count=0)\nReturn the string obtained by replacing the leftmost non-overlapping occurrences o...''')
6880

6981
def test_signature_wrap(self):
7082
if textwrap.TextWrapper.__doc__ is not None:
@@ -132,12 +144,20 @@ def test_starred_parameter(self):
132144
# test that starred first parameter is *not* removed from argspec
133145
class C:
134146
def m1(*args): pass
135-
def m2(**kwds): pass
136147
c = C()
137-
for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),
138-
(C.m2, "(**kwds)"), (c.m2, "(**kwds)"),):
148+
for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),):
139149
self.assertEqual(signature(meth), mtip)
140150

151+
def test_invalid_method_signature(self):
152+
class C:
153+
def m2(**kwargs): pass
154+
class Test:
155+
def __call__(*, a): pass
156+
157+
mtip = ct._invalid_method
158+
self.assertEqual(signature(C().m2), mtip)
159+
self.assertEqual(signature(Test()), mtip)
160+
141161
def test_non_ascii_name(self):
142162
# test that re works to delete a first parameter name that
143163
# includes non-ascii chars, such as various forms of A.
@@ -156,17 +176,23 @@ def test_attribute_exception(self):
156176
class NoCall:
157177
def __getattr__(self, name):
158178
raise BaseException
159-
class Call(NoCall):
179+
class CallA(NoCall):
180+
def __call__(oui, a, b, c):
181+
pass
182+
class CallB(NoCall):
160183
def __call__(self, ci):
161184
pass
162-
for meth, mtip in ((NoCall, default_tip), (Call, default_tip),
163-
(NoCall(), ''), (Call(), '(ci)')):
185+
186+
for meth, mtip in ((NoCall, default_tip), (CallA, default_tip),
187+
(NoCall(), ''), (CallA(), '(a, b, c)'),
188+
(CallB(), '(ci)')):
164189
self.assertEqual(signature(meth), mtip)
165190

166191
def test_non_callables(self):
167192
for obj in (0, 0.0, '0', b'0', [], {}):
168193
self.assertEqual(signature(obj), '')
169194

195+
170196
class Get_entityTest(unittest.TestCase):
171197
def test_bad_entity(self):
172198
self.assertIsNone(ct.get_entity('1/0'))
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
IDLE: Calltips use `inspect.signature` instead of `inspect.getfullargspec`.
2+
This improves calltips for builtins converted to use Argument Clinic.
3+
Patch by Louie Lu.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy