[Python-checkins] bpo-19903: IDLE: Calltips changed to use inspect.signature (#2822)

Terry Jan Reedy webhook-mailer at python.org
Wed Aug 9 21:07:28 EDT 2017


https://github.com/python/cpython/commit/3b0f620c1a2a21272a9e2aeca6ca1d1ac10f8162
commit: 3b0f620c1a2a21272a9e2aeca6ca1d1ac10f8162
branch: master
author: Louie Lu <git at louie.lu>
committer: Terry Jan Reedy <tjreedy at udel.edu>
date: 2017年08月09日T20:58:13-04:00
summary:
bpo-19903: IDLE: Calltips changed to use inspect.signature (#2822)
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.
files:
A Misc/NEWS.d/next/IDLE/2017-08-03-14-08-42.bpo-19903.sqE1FS.rst
M Lib/idlelib/calltips.py
M Lib/idlelib/idle_test/test_calltips.py
diff --git a/Lib/idlelib/calltips.py b/Lib/idlelib/calltips.py
index a8a3abe6c69..49625eac158 100644
--- a/Lib/idlelib/calltips.py
+++ b/Lib/idlelib/calltips.py
@@ -123,6 +123,8 @@ def get_entity(expression):
 _INDENT = ' '*4 # for wrapped signatures
 _first_param = re.compile(r'(?<=\()\w*,円?\s*')
 _default_callable_argspec = "See source or doc"
+_invalid_method = "invalid method signature"
+_argument_positional = "\n['/' marks preceding arguments as positional-only]\n"
 
 
 def get_argspec(ob):
@@ -134,25 +136,30 @@ def get_argspec(ob):
 empty line or _MAX_LINES. For builtins, this typically includes
 the arguments in addition to the return value.
 '''
- argspec = ""
+ argspec = default = ""
 try:
 ob_call = ob.__call__
 except BaseException:
- return argspec
- if isinstance(ob, type):
- fob = ob.__init__
- elif isinstance(ob_call, types.MethodType):
- fob = ob_call
- else:
- fob = ob
- if isinstance(fob, (types.FunctionType, types.MethodType)):
- argspec = inspect.formatargspec(*inspect.getfullargspec(fob))
- if (isinstance(ob, (type, types.MethodType)) or
- isinstance(ob_call, types.MethodType)):
- argspec = _first_param.sub("", argspec)
+ return default
+
+ fob = ob_call if isinstance(ob_call, types.MethodType) else ob
+
+ try:
+ argspec = str(inspect.signature(fob))
+ except ValueError as err:
+ msg = str(err)
+ if msg.startswith(_invalid_method):
+ return _invalid_method
+
+ if '/' in argspec:
+ """Using AC's positional argument should add the explain"""
+ argspec += _argument_positional
+ if isinstance(fob, type) and argspec == '()':
+ """fob with no argument, use default callable argspec"""
+ argspec = _default_callable_argspec
 
 lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
- if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
+ if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
 
 if isinstance(ob_call, types.MethodType):
 doc = ob_call.__doc__
@@ -171,6 +178,7 @@ def get_argspec(ob):
 argspec = _default_callable_argspec
 return argspec
 
+
 if __name__ == '__main__':
 from unittest import main
 main('idlelib.idle_test.test_calltips', verbosity=2)
diff --git a/Lib/idlelib/idle_test/test_calltips.py b/Lib/idlelib/idle_test/test_calltips.py
index 29b9f06faf8..fa92ece78ee 100644
--- a/Lib/idlelib/idle_test/test_calltips.py
+++ b/Lib/idlelib/idle_test/test_calltips.py
@@ -46,6 +46,7 @@ def test_builtins(self):
 
 # Python class that inherits builtin methods
 class List(list): "List() doc"
+
 # Simulate builtin with no docstring for default tip test
 class SB: __call__ = None
 
@@ -53,18 +54,28 @@ def gtest(obj, out):
 self.assertEqual(signature(obj), out)
 
 if List.__doc__ is not None:
- gtest(List, List.__doc__)
+ gtest(List, '(iterable=(), /)' + ct._argument_positional + '\n' +
+ List.__doc__)
 gtest(list.__new__,
- 'Create and return a new object. See help(type) for accurate signature.')
+ '(*args, **kwargs)\nCreate and return a new object. See help(type) for accurate signature.')
 gtest(list.__init__,
+ '(self, /, *args, **kwargs)' + ct._argument_positional + '\n' +
 'Initialize self. See help(type(self)) for accurate signature.')
- append_doc = "Append object to the end of the list."
- gtest(list.append, append_doc)
- gtest([].append, append_doc)
- gtest(List.append, append_doc)
+ append_doc = ct._argument_positional + "\nAppend object to the end of the list."
+ gtest(list.append, '(self, object, /)' + append_doc)
+ gtest(List.append, '(self, object, /)' + append_doc)
+ gtest([].append, '(object, /)' + append_doc)
 
 gtest(types.MethodType, "method(function, instance)")
 gtest(SB(), default_tip)
+ import re
+ p = re.compile('')
+ gtest(re.sub, '''(pattern, repl, string, count=0, flags=0)\nReturn the string obtained by replacing the leftmost
+non-overlapping occurrences of the pattern in string by the
+replacement repl. repl can be either a string or a callable;
+if a string, backslash escapes in it are processed. If it is
+a callable, it's passed the match object and must return''')
+ gtest(p.sub, '''(repl, string, count=0)\nReturn the string obtained by replacing the leftmost non-overlapping occurrences o...''')
 
 def test_signature_wrap(self):
 if textwrap.TextWrapper.__doc__ is not None:
@@ -132,12 +143,20 @@ def test_starred_parameter(self):
 # test that starred first parameter is *not* removed from argspec
 class C:
 def m1(*args): pass
- def m2(**kwds): pass
 c = C()
- for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),
- (C.m2, "(**kwds)"), (c.m2, "(**kwds)"),):
+ for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),):
 self.assertEqual(signature(meth), mtip)
 
+ def test_invalid_method_signature(self):
+ class C:
+ def m2(**kwargs): pass
+ class Test:
+ def __call__(*, a): pass
+
+ mtip = ct._invalid_method
+ self.assertEqual(signature(C().m2), mtip)
+ self.assertEqual(signature(Test()), mtip)
+
 def test_non_ascii_name(self):
 # test that re works to delete a first parameter name that
 # includes non-ascii chars, such as various forms of A.
@@ -156,17 +175,23 @@ def test_attribute_exception(self):
 class NoCall:
 def __getattr__(self, name):
 raise BaseException
- class Call(NoCall):
+ class CallA(NoCall):
+ def __call__(oui, a, b, c):
+ pass
+ class CallB(NoCall):
 def __call__(self, ci):
 pass
- for meth, mtip in ((NoCall, default_tip), (Call, default_tip),
- (NoCall(), ''), (Call(), '(ci)')):
+
+ for meth, mtip in ((NoCall, default_tip), (CallA, default_tip),
+ (NoCall(), ''), (CallA(), '(a, b, c)'),
+ (CallB(), '(ci)')):
 self.assertEqual(signature(meth), mtip)
 
 def test_non_callables(self):
 for obj in (0, 0.0, '0', b'0', [], {}):
 self.assertEqual(signature(obj), '')
 
+
 class Get_entityTest(unittest.TestCase):
 def test_bad_entity(self):
 self.assertIsNone(ct.get_entity('1/0'))
diff --git a/Misc/NEWS.d/next/IDLE/2017-08-03-14-08-42.bpo-19903.sqE1FS.rst b/Misc/NEWS.d/next/IDLE/2017-08-03-14-08-42.bpo-19903.sqE1FS.rst
new file mode 100644
index 00000000000..f25fc80c3dd
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2017-08-03-14-08-42.bpo-19903.sqE1FS.rst
@@ -0,0 +1,3 @@
+IDLE: Calltips use `inspect.signature` instead of `inspect.getfullargspec`.
+This improves calltips for builtins converted to use Argument Clinic.
+Patch by Louie Lu.


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /