Skip to content

Commit 562c13f

Browse files
bpo-29418: Implement inspect.ismethodwrapper and fix inspect.isroutine for cases where methodwrapper is given (GH-19261)
Automerge-Triggered-By: GH:isidentical
1 parent 3954fcb commit 562c13f

File tree

4 files changed

+63
-12
lines changed

4 files changed

+63
-12
lines changed

Doc/library/inspect.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,14 @@ attributes:
429429
Return ``True`` if the object is a built-in function or a bound built-in method.
430430

431431

432+
.. function:: ismethodwrapper(object)
433+
434+
Return ``True`` if the type of object is a :class:`~types.MethodWrapperType`.
435+
436+
These are instances of :class:`~types.MethodWrapperType`, such as :meth:`~object().__str__`,
437+
:meth:`~object().__eq__` and :meth:`~object().__repr__`
438+
439+
432440
.. function:: isroutine(object)
433441

434442
Return ``True`` if the object is a user-defined or built-in function or method.

Lib/inspect.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
"ismemberdescriptor",
122122
"ismethod",
123123
"ismethoddescriptor",
124+
"ismethodwrapper",
124125
"ismodule",
125126
"isroutine",
126127
"istraceback",
@@ -509,12 +510,17 @@ def isbuiltin(object):
509510
__self__ instance to which a method is bound, or None"""
510511
return isinstance(object, types.BuiltinFunctionType)
511512

513+
def ismethodwrapper(object):
514+
"""Return true if the object is a method wrapper."""
515+
return isinstance(object, types.MethodWrapperType)
516+
512517
def isroutine(object):
513518
"""Return true if the object is any kind of function or method."""
514519
return (isbuiltin(object)
515520
or isfunction(object)
516521
or ismethod(object)
517-
or ismethoddescriptor(object))
522+
or ismethoddescriptor(object)
523+
or ismethodwrapper(object))
518524

519525
def isabstract(object):
520526
"""Return true if the object is an abstract base class (ABC)."""
@@ -1887,13 +1893,9 @@ def getcoroutinelocals(coroutine):
18871893
###############################################################################
18881894

18891895

1890-
_WrapperDescriptor = type(type.__call__)
1891-
_MethodWrapper = type(all.__call__)
1892-
_ClassMethodWrapper = type(int.__dict__['from_bytes'])
1893-
1894-
_NonUserDefinedCallables = (_WrapperDescriptor,
1895-
_MethodWrapper,
1896-
_ClassMethodWrapper,
1896+
_NonUserDefinedCallables = (types.WrapperDescriptorType,
1897+
types.MethodWrapperType,
1898+
types.ClassMethodDescriptorType,
18971899
types.BuiltinFunctionType)
18981900

18991901

@@ -2533,7 +2535,7 @@ def _signature_from_callable(obj, *,
25332535
elif not isinstance(obj, _NonUserDefinedCallables):
25342536
# An object with __call__
25352537
# We also check that the 'obj' is not an instance of
2536-
# _WrapperDescriptor or _MethodWrapper to avoid
2538+
# types.WrapperDescriptorType or types.MethodWrapperType to avoid
25372539
# infinite recursion (and even potential segfault)
25382540
call = _signature_get_user_defined_method(type(obj), '__call__')
25392541
if call is not None:

Lib/test/test_inspect.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
# isbuiltin, isroutine, isgenerator, isgeneratorfunction, getmembers,
4545
# getdoc, getfile, getmodule, getsourcefile, getcomments, getsource,
4646
# getclasstree, getargvalues, formatargvalues,
47-
# currentframe, stack, trace, isdatadescriptor
47+
# currentframe, stack, trace, isdatadescriptor,
48+
# ismethodwrapper
4849

4950
# NOTE: There are some additional tests relating to interaction with
5051
# zipimport in the test_zipimport_support test module.
@@ -93,7 +94,8 @@ class IsTestBase(unittest.TestCase):
9394
inspect.ismodule, inspect.istraceback,
9495
inspect.isgenerator, inspect.isgeneratorfunction,
9596
inspect.iscoroutine, inspect.iscoroutinefunction,
96-
inspect.isasyncgen, inspect.isasyncgenfunction])
97+
inspect.isasyncgen, inspect.isasyncgenfunction,
98+
inspect.ismethodwrapper])
9799

98100
def istest(self, predicate, exp):
99101
obj = eval(exp)
@@ -169,6 +171,14 @@ def test_excluding_predicates(self):
169171
self.istest(inspect.ismemberdescriptor, 'datetime.timedelta.days')
170172
else:
171173
self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days))
174+
self.istest(inspect.ismethodwrapper, "object().__str__")
175+
self.istest(inspect.ismethodwrapper, "object().__eq__")
176+
self.istest(inspect.ismethodwrapper, "object().__repr__")
177+
self.assertFalse(inspect.ismethodwrapper(type))
178+
self.assertFalse(inspect.ismethodwrapper(int))
179+
self.assertFalse(inspect.ismethodwrapper(type("AnyClass", (), {})))
180+
181+
172182

173183
def test_iscoroutine(self):
174184
async_gen_coro = async_generator_function_example(1)
@@ -241,8 +251,38 @@ class NotFuture: pass
241251
coro.close(); gen_coro.close() # silence warnings
242252

243253
def test_isroutine(self):
244-
self.assertTrue(inspect.isroutine(mod.spam))
254+
# method
255+
self.assertTrue(inspect.isroutine(git.argue))
256+
self.assertTrue(inspect.isroutine(mod.custom_method))
245257
self.assertTrue(inspect.isroutine([].count))
258+
# function
259+
self.assertTrue(inspect.isroutine(mod.spam))
260+
self.assertTrue(inspect.isroutine(mod.StupidGit.abuse))
261+
# slot-wrapper
262+
self.assertTrue(inspect.isroutine(object.__init__))
263+
self.assertTrue(inspect.isroutine(object.__str__))
264+
self.assertTrue(inspect.isroutine(object.__lt__))
265+
self.assertTrue(inspect.isroutine(int.__lt__))
266+
# method-wrapper
267+
self.assertTrue(inspect.isroutine(object().__init__))
268+
self.assertTrue(inspect.isroutine(object().__str__))
269+
self.assertTrue(inspect.isroutine(object().__lt__))
270+
self.assertTrue(inspect.isroutine((42).__lt__))
271+
# method-descriptor
272+
self.assertTrue(inspect.isroutine(str.join))
273+
self.assertTrue(inspect.isroutine(list.append))
274+
self.assertTrue(inspect.isroutine(''.join))
275+
self.assertTrue(inspect.isroutine([].append))
276+
# object
277+
self.assertFalse(inspect.isroutine(object))
278+
self.assertFalse(inspect.isroutine(object()))
279+
self.assertFalse(inspect.isroutine(str()))
280+
# module
281+
self.assertFalse(inspect.isroutine(mod))
282+
# type
283+
self.assertFalse(inspect.isroutine(type))
284+
self.assertFalse(inspect.isroutine(int))
285+
self.assertFalse(inspect.isroutine(type('some_class', (), {})))
246286

247287
def test_isclass(self):
248288
self.istest(inspect.isclass, 'mod.StupidGit')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement :func:`inspect.ismethodwrapper` and fix :func:`inspect.isroutine` for cases where methodwrapper is given. Patch by Hakan Çelik.

0 commit comments

Comments
 (0)