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

Skip to content

Commit 5c66189

Browse files
committed
Issue python#20189: Four additional builtin types (PyTypeObject,
PyMethodDescr_Type, _PyMethodWrapper_Type, and PyWrapperDescr_Type) have been modified to provide introspection information for builtins. Also: many additional Lib, test suite, and Argument Clinic fixes.
1 parent b3c0f40 commit 5c66189

31 files changed

Lines changed: 857 additions & 514 deletions

Doc/library/inspect.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,9 @@ function.
429429
Accepts a wide range of python callables, from plain functions and classes to
430430
:func:`functools.partial` objects.
431431

432+
Raises :exc:`ValueError` if no signature can be provided, and
433+
:exc:`TypeError` if that type of object is not supported.
434+
432435
.. note::
433436

434437
Some callables may not be introspectable in certain implementations of

Include/object.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,9 @@ PyAPI_FUNC(PyTypeObject *) _PyType_CalculateMetaclass(PyTypeObject *, PyObject *
492492
PyAPI_FUNC(unsigned int) PyType_ClearCache(void);
493493
PyAPI_FUNC(void) PyType_Modified(PyTypeObject *);
494494

495+
PyAPI_FUNC(PyObject *) _PyType_GetDocFromInternalDoc(const char *name, const char *internal_doc);
496+
PyAPI_FUNC(PyObject *) _PyType_GetTextSignatureFromInternalDoc(const char *name, const char *internal_doc);
497+
495498
/* Generic operations on objects */
496499
struct _Py_Identifier;
497500
#ifndef Py_LIMITED_API

Lib/idlelib/idle_test/test_calltips.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,27 @@ def gtest(obj, out):
5555
gtest(list.__new__,
5656
'T.__new__(S, ...) -> a new object with type S, a subtype of T')
5757
gtest(list.__init__,
58-
'x.__init__(...) initializes x; see help(type(x)) for signature')
58+
'Initializes self. See help(type(self)) for accurate signature.')
5959
append_doc = "L.append(object) -> None -- append object to end"
6060
gtest(list.append, append_doc)
6161
gtest([].append, append_doc)
6262
gtest(List.append, append_doc)
6363

64-
gtest(types.MethodType, "method(function, instance)")
64+
gtest(types.MethodType, "Create a bound instance method object.")
6565
gtest(SB(), default_tip)
6666

6767
def test_multiline_docstring(self):
6868
# Test fewer lines than max.
69-
self.assertEqual(signature(list),
70-
"list() -> new empty list\n"
71-
"list(iterable) -> new list initialized from iterable's items")
69+
self.assertEqual(signature(dict),
70+
"dict(mapping) -> new dictionary initialized from a mapping object's\n"
71+
"(key, value) pairs\n"
72+
"dict(iterable) -> new dictionary initialized as if via:\n"
73+
"d = {}\n"
74+
"for k, v in iterable:"
75+
)
7276

7377
# Test max lines and line (currently) too long.
7478
self.assertEqual(signature(bytes),
75-
"bytes(iterable_of_ints) -> bytes\n"
7679
"bytes(string, encoding[, errors]) -> bytes\n"
7780
"bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n"
7881
#bytes(int) -> bytes object of size given by the parameter initialized with null bytes

Lib/inspect.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,9 +1419,11 @@ def getgeneratorlocals(generator):
14191419

14201420
_WrapperDescriptor = type(type.__call__)
14211421
_MethodWrapper = type(all.__call__)
1422+
_ClassMethodWrapper = type(int.__dict__['from_bytes'])
14221423

14231424
_NonUserDefinedCallables = (_WrapperDescriptor,
14241425
_MethodWrapper,
1426+
_ClassMethodWrapper,
14251427
types.BuiltinFunctionType)
14261428

14271429

@@ -1443,6 +1445,13 @@ def signature(obj):
14431445
if not callable(obj):
14441446
raise TypeError('{!r} is not a callable object'.format(obj))
14451447

1448+
if (isinstance(obj, _NonUserDefinedCallables) or
1449+
ismethoddescriptor(obj) or
1450+
isinstance(obj, type)):
1451+
sig = Signature.from_builtin(obj)
1452+
if sig:
1453+
return sig
1454+
14461455
if isinstance(obj, types.MethodType):
14471456
# In this case we skip the first parameter of the underlying
14481457
# function (usually `self` or `cls`).
@@ -1460,13 +1469,9 @@ def signature(obj):
14601469
if sig is not None:
14611470
return sig
14621471

1463-
14641472
if isinstance(obj, types.FunctionType):
14651473
return Signature.from_function(obj)
14661474

1467-
if isinstance(obj, types.BuiltinFunctionType):
1468-
return Signature.from_builtin(obj)
1469-
14701475
if isinstance(obj, functools.partial):
14711476
sig = signature(obj.func)
14721477

@@ -2033,7 +2038,7 @@ def p(name_node, default_node, default=empty):
20332038
name = parse_name(name_node)
20342039
if name is invalid:
20352040
return None
2036-
if default_node:
2041+
if default_node and default_node is not _empty:
20372042
try:
20382043
default_node = RewriteSymbolics().visit(default_node)
20392044
o = ast.literal_eval(default_node)
@@ -2066,6 +2071,23 @@ def p(name_node, default_node, default=empty):
20662071
kind = Parameter.VAR_KEYWORD
20672072
p(f.args.kwarg, empty)
20682073

2074+
if parameters and (hasattr(func, '__self__') or
2075+
isinstance(func, _WrapperDescriptor,) or
2076+
ismethoddescriptor(func)
2077+
):
2078+
name = parameters[0].name
2079+
if name not in ('self', 'module', 'type'):
2080+
pass
2081+
elif getattr(func, '__self__', None):
2082+
# strip off self (it's already been bound)
2083+
p = parameters.pop(0)
2084+
if not p.name in ('self', 'module', 'type'):
2085+
raise ValueError('Unexpected name ' + repr(p.name) + ', expected self/module/cls/type')
2086+
else:
2087+
# for builtins, self parameter is always positional-only!
2088+
p = parameters[0].replace(kind=Parameter.POSITIONAL_ONLY)
2089+
parameters[0] = p
2090+
20692091
return cls(parameters, return_annotation=cls.empty)
20702092

20712093

Lib/pydoc.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -925,7 +925,10 @@ def docroutine(self, object, name=None, mod=None,
925925
anchor, name, reallink)
926926
argspec = None
927927
if inspect.isfunction(object) or inspect.isbuiltin(object):
928-
signature = inspect.signature(object)
928+
try:
929+
signature = inspect.signature(object)
930+
except (ValueError, TypeError):
931+
signature = None
929932
if signature:
930933
argspec = str(signature)
931934
if realname == '<lambda>':
@@ -1319,8 +1322,12 @@ def docroutine(self, object, name=None, mod=None, cl=None):
13191322
skipdocs = 1
13201323
title = self.bold(name) + ' = ' + realname
13211324
argspec = None
1322-
if inspect.isfunction(object) or inspect.isbuiltin(object):
1323-
signature = inspect.signature(object)
1325+
1326+
if inspect.isroutine(object):
1327+
try:
1328+
signature = inspect.signature(object)
1329+
except (ValueError, TypeError):
1330+
signature = None
13241331
if signature:
13251332
argspec = str(signature)
13261333
if realname == '<lambda>':

Lib/test/test_capi.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,20 @@ def test_docstring_signature_parsing(self):
125125
self.assertEqual(_testcapi.docstring_no_signature.__text_signature__, None)
126126

127127
self.assertEqual(_testcapi.docstring_with_invalid_signature.__doc__,
128-
"docstring_with_invalid_signature (boo)\n"
128+
"docstring_with_invalid_signature (module, boo)\n"
129129
"\n"
130130
"This docstring has an invalid signature."
131131
)
132132
self.assertEqual(_testcapi.docstring_with_invalid_signature.__text_signature__, None)
133133

134134
self.assertEqual(_testcapi.docstring_with_signature.__doc__,
135135
"This docstring has a valid signature.")
136-
self.assertEqual(_testcapi.docstring_with_signature.__text_signature__, "(sig)")
136+
self.assertEqual(_testcapi.docstring_with_signature.__text_signature__, "(module, sig)")
137137

138138
self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__doc__,
139139
"This docstring has a valid signature and some extra newlines.")
140140
self.assertEqual(_testcapi.docstring_with_signature_and_extra_newlines.__text_signature__,
141-
"(parameter)")
141+
"(module, parameter)")
142142

143143

144144
@unittest.skipUnless(threading, 'Threading required for this test.')

Lib/test/test_generators.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,8 +436,8 @@ def gen():
436436
>>> [s for s in dir(i) if not s.startswith('_')]
437437
['close', 'gi_code', 'gi_frame', 'gi_running', 'send', 'throw']
438438
>>> from test.support import HAVE_DOCSTRINGS
439-
>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'x.__next__() <==> next(x)')
440-
x.__next__() <==> next(x)
439+
>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implements next(self).')
440+
Implements next(self).
441441
>>> iter(i) is i
442442
True
443443
>>> import types

Lib/test/test_genexps.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@
222222
True
223223
224224
>>> from test.support import HAVE_DOCSTRINGS
225-
>>> print(g.__next__.__doc__ if HAVE_DOCSTRINGS else 'x.__next__() <==> next(x)')
226-
x.__next__() <==> next(x)
225+
>>> print(g.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implements next(self).')
226+
Implements next(self).
227227
>>> import types
228228
>>> isinstance(g, types.GeneratorType)
229229
True

Lib/test/test_inspect.py

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
1-
import re
2-
import sys
3-
import types
4-
import unittest
5-
import inspect
6-
import linecache
7-
import datetime
1+
import _testcapi
82
import collections
9-
import os
10-
import shutil
3+
import datetime
114
import functools
125
import importlib
6+
import inspect
7+
import io
8+
import linecache
9+
import os
1310
from os.path import normcase
11+
import _pickle
12+
import re
13+
import shutil
14+
import sys
15+
import types
16+
import unicodedata
17+
import unittest
18+
1419
try:
1520
from concurrent.futures import ThreadPoolExecutor
1621
except ImportError:
1722
ThreadPoolExecutor = None
18-
import _testcapi
1923

2024
from test.support import run_unittest, TESTFN, DirsOnSysPath
2125
from test.support import MISSING_C_DOCSTRINGS
2226
from test.script_helper import assert_python_ok, assert_python_failure
2327
from test import inspect_fodder as mod
2428
from test import inspect_fodder2 as mod2
2529

26-
# C module for test_findsource_binary
27-
import unicodedata
2830

2931
# Functions tested in this suite:
3032
# ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode,
@@ -1582,23 +1584,30 @@ def test(a, b:'foo'=10, *args:'bar', spam:'baz', ham=123, **kwargs:int):
15821584
...))
15831585

15841586
def test_signature_on_unsupported_builtins(self):
1585-
with self.assertRaisesRegex(ValueError, 'not supported by signature'):
1586-
inspect.signature(type)
1587-
with self.assertRaisesRegex(ValueError, 'not supported by signature'):
1588-
# support for 'wrapper_descriptor'
1589-
inspect.signature(type.__call__)
1590-
with self.assertRaisesRegex(ValueError, 'not supported by signature'):
1591-
# support for 'method-wrapper'
1592-
inspect.signature(min.__call__)
1587+
with self.assertRaisesRegex(ValueError, 'no signature found'):
1588+
# min simply doesn't have a signature (yet)
1589+
inspect.signature(min)
15931590

15941591
@unittest.skipIf(MISSING_C_DOCSTRINGS,
15951592
"Signature information for builtins requires docstrings")
15961593
def test_signature_on_builtins(self):
1597-
# min doesn't have a signature (yet)
1598-
self.assertEqual(inspect.signature(min), None)
15991594

1600-
signature = inspect.signature(_testcapi.docstring_with_signature_with_defaults)
1601-
self.assertTrue(isinstance(signature, inspect.Signature))
1595+
def test_unbound_method(o):
1596+
"""Use this to test unbound methods (things that should have a self)"""
1597+
signature = inspect.signature(o)
1598+
self.assertTrue(isinstance(signature, inspect.Signature))
1599+
self.assertEqual(list(signature.parameters.values())[0].name, 'self')
1600+
return signature
1601+
1602+
def test_callable(o):
1603+
"""Use this to test bound methods or normal callables (things that don't expect self)"""
1604+
signature = inspect.signature(o)
1605+
self.assertTrue(isinstance(signature, inspect.Signature))
1606+
if signature.parameters:
1607+
self.assertNotEqual(list(signature.parameters.values())[0].name, 'self')
1608+
return signature
1609+
1610+
signature = test_callable(_testcapi.docstring_with_signature_with_defaults)
16021611
def p(name): return signature.parameters[name].default
16031612
self.assertEqual(p('s'), 'avocado')
16041613
self.assertEqual(p('b'), b'bytes')
@@ -1611,6 +1620,41 @@ def p(name): return signature.parameters[name].default
16111620
self.assertEqual(p('sys'), sys.maxsize)
16121621
self.assertEqual(p('exp'), sys.maxsize - 1)
16131622

1623+
test_callable(type)
1624+
test_callable(object)
1625+
1626+
# normal method
1627+
# (PyMethodDescr_Type, "method_descriptor")
1628+
test_unbound_method(_pickle.Pickler.dump)
1629+
d = _pickle.Pickler(io.StringIO())
1630+
test_callable(d.dump)
1631+
1632+
# static method
1633+
test_callable(str.maketrans)
1634+
test_callable('abc'.maketrans)
1635+
1636+
# class method
1637+
test_callable(dict.fromkeys)
1638+
test_callable({}.fromkeys)
1639+
1640+
# wrapper around slot (PyWrapperDescr_Type, "wrapper_descriptor")
1641+
test_unbound_method(type.__call__)
1642+
test_unbound_method(int.__add__)
1643+
test_callable((3).__add__)
1644+
1645+
# _PyMethodWrapper_Type
1646+
# support for 'method-wrapper'
1647+
test_callable(min.__call__)
1648+
1649+
class ThisWorksNow:
1650+
__call__ = type
1651+
test_callable(ThisWorksNow())
1652+
1653+
1654+
def test_signature_on_builtins_no_signature(self):
1655+
with self.assertRaisesRegex(ValueError, 'no signature found for builtin'):
1656+
inspect.signature(_testcapi.docstring_no_signature)
1657+
16141658
def test_signature_on_non_function(self):
16151659
with self.assertRaisesRegex(TypeError, 'is not a callable object'):
16161660
inspect.signature(42)
@@ -1985,12 +2029,6 @@ class Bar(Spam, Foo):
19852029
((('a', ..., ..., "positional_or_keyword"),),
19862030
...))
19872031

1988-
class ToFail:
1989-
__call__ = type
1990-
with self.assertRaisesRegex(ValueError, "not supported by signature"):
1991-
inspect.signature(ToFail())
1992-
1993-
19942032
class Wrapped:
19952033
pass
19962034
Wrapped.__wrapped__ = lambda a: None

Lib/unittest/mock.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,24 @@ def checksig(_mock_self, *args, **kwargs):
112112
def _copy_func_details(func, funcopy):
113113
funcopy.__name__ = func.__name__
114114
funcopy.__doc__ = func.__doc__
115+
try:
116+
funcopy.__text_signature__ = func.__text_signature__
117+
except AttributeError:
118+
pass
115119
# we explicitly don't copy func.__dict__ into this copy as it would
116120
# expose original attributes that should be mocked
117-
funcopy.__module__ = func.__module__
118-
funcopy.__defaults__ = func.__defaults__
119-
funcopy.__kwdefaults__ = func.__kwdefaults__
121+
try:
122+
funcopy.__module__ = func.__module__
123+
except AttributeError:
124+
pass
125+
try:
126+
funcopy.__defaults__ = func.__defaults__
127+
except AttributeError:
128+
pass
129+
try:
130+
funcopy.__kwdefaults__ = func.__kwdefaults__
131+
except AttributeError:
132+
pass
120133

121134

122135
def _callable(obj):

0 commit comments

Comments
 (0)