From 9944ef6a6284b67131978495ebead7574a18a053 Mon Sep 17 00:00:00 2001 From: Jan Kaliszewski Date: Tue, 23 Jul 2024 00:48:05 +0200 Subject: [PATCH 1/8] gh-122102: Improve tests of `inspect.is{data,method}descriptor()` --- Lib/test/test_inspect/test_inspect.py | 427 ++++++++++++++---- ...-07-22-01-32-00.gh-issue-122102.6sBzoH.rst | 2 + 2 files changed, 344 insertions(+), 85 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2024-07-22-01-32-00.gh-issue-122102.6sBzoH.rst diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index d39c3ccdc847bd..dc5c546da1e206 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1816,49 +1816,109 @@ def test_typing_replacement(self): class TestIsMethodDescriptor(unittest.TestCase): def test_custom_descriptors(self): + # Custom method descriptors (non-data descriptors): + class MethodDescriptor: + """with __get__ and no __set__/__delete__""" def __get__(self, *_): pass + class MethodDescriptorWithGetNone: + """with __get__=None and no __set__/__delete__""" + __get__ = None class MethodDescriptorSub(MethodDescriptor): - pass - class DataDescriptorWithNoGet: + """with __get__ (inherited) and no __set__/__delete__""" + + for cls in [ + MethodDescriptor, + MethodDescriptorWithGetNone, + MethodDescriptorSub, + ]: + self.assertTrue( + inspect.ismethoddescriptor(cls()), + f"an object {cls.__doc__} is a method descriptor", + ) + self.assertFalse( + inspect.ismethoddescriptor(cls), + f'a class of a method descriptor is not a method descriptor', + ) + + # Custom data descriptors: + + class DataDescriptorWithSetOnly: + """with __set__ (and no __get__)""" def __set__(self, *_): pass + class DataDescriptorWithSetNone: + """with __set__=None (and no __get__)""" + __set__ = None + class DataDescriptorWithDeleteOnly: + """with __delete__ (and no __get__)""" + def __delete__(self, *_): pass + class DataDescriptorWithDeleteNone: + """with __delete__=None (and no __get__)""" + __delete__ = None + class DataDescriptorWithSetDelete: + """with __set__ and __delete__ (and no __get__)""" + def __set__(self, *_): pass + def __delete__(self, *_): pass class DataDescriptorWithGetSet: + """with __get__ and __set__""" def __get__(self, *_): pass def __set__(self, *_): pass class DataDescriptorWithGetDelete: + """with __get__ and __delete__""" def __get__(self, *_): pass def __delete__(self, *_): pass - class DataDescriptorSub(DataDescriptorWithNoGet, - DataDescriptorWithGetDelete): - pass - - # Custom method descriptors: - self.assertTrue( - inspect.ismethoddescriptor(MethodDescriptor()), - '__get__ and no __set__/__delete__ => method descriptor') - self.assertTrue( - inspect.ismethoddescriptor(MethodDescriptorSub()), - '__get__ (inherited) and no __set__/__delete__' - ' => method descriptor') - - # Custom data descriptors: - self.assertFalse( - inspect.ismethoddescriptor(DataDescriptorWithNoGet()), - '__set__ (and no __get__) => not a method descriptor') - self.assertFalse( - inspect.ismethoddescriptor(DataDescriptorWithGetSet()), - '__get__ and __set__ => not a method descriptor') - self.assertFalse( - inspect.ismethoddescriptor(DataDescriptorWithGetDelete()), - '__get__ and __delete__ => not a method descriptor') - self.assertFalse( - inspect.ismethoddescriptor(DataDescriptorSub()), - '__get__, __set__ and __delete__ => not a method descriptor') - - # Classes of descriptors (are *not* descriptors themselves): - self.assertFalse(inspect.ismethoddescriptor(MethodDescriptor)) - self.assertFalse(inspect.ismethoddescriptor(MethodDescriptorSub)) - self.assertFalse(inspect.ismethoddescriptor(DataDescriptorSub)) + class DataDescriptorWithGetSetDelete: + """with __get__, __set__ and __delete__""" + def __get__(self, *_): pass + def __set__(self, *_): pass + def __delete__(self, *_): pass + class DataDescriptorSub1(DataDescriptorWithSetOnly): + """with __set__ (inherited) and __get__""" + def __get__(self, *_): pass + class DataDescriptorSub2(DataDescriptorWithSetNone): + """with __set__=None (inherited) and __get__""" + def __get__(self, *_): pass + class DataDescriptorSub3(DataDescriptorWithDeleteOnly): + """with __delete__ (inherited) and __get__""" + def __get__(self, *_): pass + class DataDescriptorSub4(DataDescriptorWithDeleteNone): + """with __delete__=None (inherited) and __get__""" + def __get__(self, *_): pass + class DataDescriptorSub5(MethodDescriptor): + """with __get__ (inherited) and __set__""" + def __set__(self, *_): pass + class DataDescriptorSub6(MethodDescriptor): + """with __get__ (inherited) and __delete__""" + def __delete__(self, *_): pass + class DataDescriptorSub7(DataDescriptorWithSetOnly, + DataDescriptorWithGetDelete): + """with __get__, __set__ and __delete__ (all inherited)""" + + for cls in [ + DataDescriptorWithSetOnly, + DataDescriptorWithSetNone, + DataDescriptorWithDeleteOnly, + DataDescriptorWithDeleteNone, + DataDescriptorWithSetDelete, + DataDescriptorWithGetSet, + DataDescriptorWithGetDelete, + DataDescriptorWithGetSetDelete, + DataDescriptorSub1, + DataDescriptorSub2, + DataDescriptorSub3, + DataDescriptorSub4, + DataDescriptorSub5, + DataDescriptorSub6, + DataDescriptorSub7, + ]: + self.assertFalse( + inspect.ismethoddescriptor(cls()), + f"an object {cls.__doc__} is not a method descriptor", + ) + self.assertFalse( + inspect.ismethoddescriptor(cls), + f'a class of a data descriptor is not a method descriptor', + ) def test_builtin_descriptors(self): builtin_slot_wrapper = int.__add__ # This one is mentioned in docs. @@ -1872,11 +1932,14 @@ def static_method(): pass def a_property(self): pass class Slotermeyer: __slots__ = 'a_slot', + attribute_slot = Slotermeyer.__dict__['a_slot'] + frame_locals = types.FrameType.f_locals def function(): pass a_lambda = lambda: None - # Example builtin method descriptors: + # Example builtin method descriptors (non-data descriptors): + self.assertTrue( inspect.ismethoddescriptor(builtin_slot_wrapper), 'a builtin slot wrapper is a method descriptor') @@ -1887,17 +1950,10 @@ def function(): inspect.ismethoddescriptor(Owner.__dict__['static_method']), 'a staticmethod object is a method descriptor') - # Example builtin data descriptors: - self.assertFalse( - inspect.ismethoddescriptor(Owner.__dict__['a_property']), - 'a property is not a method descriptor') - self.assertFalse( - inspect.ismethoddescriptor(Slotermeyer.__dict__['a_slot']), - 'a slot is not a method descriptor') + # `types.MethodType`/`types.FunctionType` instances (note that + # they *are* method descriptors, but `ismethoddescriptor()` + # explicitly excludes them): - # `types.MethodType`/`types.FunctionType` instances (they *are* - # method descriptors, but `ismethoddescriptor()` explicitly - # excludes them): self.assertFalse(inspect.ismethoddescriptor(Owner().instance_method)) self.assertFalse(inspect.ismethoddescriptor(Owner().class_method)) self.assertFalse(inspect.ismethoddescriptor(Owner().static_method)) @@ -1907,6 +1963,34 @@ def function(): self.assertFalse(inspect.ismethoddescriptor(function)) self.assertFalse(inspect.ismethoddescriptor(a_lambda)) + # Example builtin data descriptors: + + self.assertFalse( + inspect.ismethoddescriptor(Owner.__dict__['a_property']), + 'a property is not a method descriptor') + + if hasattr(types, 'MemberDescriptorType'): + assert inspect.ismemberdescriptor(attribute_slot) + self.assertFalse( + inspect.ismethoddescriptor(attribute_slot), + 'a member descriptor, such as an attribute ' + 'slot descriptor, is not a method descriptor') + else: + self.assertFalse( + inspect.ismethoddescriptor(attribute_slot), + 'an attribute slot descriptor is not a method descriptor') + + if hasattr(types, 'GetSetDescriptorType'): + assert inspect.isgetsetdescriptor(frame_locals) + self.assertFalse( + inspect.ismethoddescriptor(frame_locals), + "a getset descriptor, such as a frame's 'f_locals' " + "descriptor, is not a method descriptor") + else: + self.assertFalse( + inspect.ismethoddescriptor(frame_locals), + "a frame's 'f_locals' descriptor, is not a method descriptor") + def test_descriptor_being_a_class(self): class MethodDescriptorMeta(type): def __get__(self, *_): pass @@ -1931,57 +2015,230 @@ class Test: class TestIsDataDescriptor(unittest.TestCase): def test_custom_descriptors(self): - class NonDataDescriptor: - def __get__(self, value, type=None): pass - class DataDescriptor0: - def __set__(self, name, value): pass - class DataDescriptor1: - def __delete__(self, name): pass - class DataDescriptor2: - __set__ = None - self.assertFalse(inspect.isdatadescriptor(NonDataDescriptor()), - 'class with only __get__ not a data descriptor') - self.assertTrue(inspect.isdatadescriptor(DataDescriptor0()), - 'class with __set__ is a data descriptor') - self.assertTrue(inspect.isdatadescriptor(DataDescriptor1()), - 'class with __delete__ is a data descriptor') - self.assertTrue(inspect.isdatadescriptor(DataDescriptor2()), - 'class with __set__ = None is a data descriptor') - - def test_slot(self): - class Slotted: - __slots__ = 'foo', - self.assertTrue(inspect.isdatadescriptor(Slotted.foo), - 'a slot is a data descriptor') + # Custom method descriptors (non-data descriptors): - def test_property(self): - class Propertied: - @property - def a_property(self): - pass - self.assertTrue(inspect.isdatadescriptor(Propertied.a_property), - 'a property is a data descriptor') + class MethodDescriptor: + """with __get__ and no __set__/__delete__""" + def __get__(self, *_): pass + class MethodDescriptorWithGetNone: + """with __get__=None and no __set__/__delete__""" + __get__ = None + class MethodDescriptorSub(MethodDescriptor): + """with __get__ (inherited) and no __set__/__delete__""" + + for cls in [ + MethodDescriptor, + MethodDescriptorWithGetNone, + MethodDescriptorSub, + ]: + self.assertFalse( + inspect.isdatadescriptor(cls()), + f"object {cls.__doc__} is not a data descriptor", + ) + self.assertFalse( + inspect.isdatadescriptor(cls), + f'class of a method descriptor is not a data descriptor', + ) - def test_functions(self): - class Test(object): + # Custom data descriptors: + + class DataDescriptorWithSetOnly: + """with __set__ (and no __get__)""" + def __set__(self, *_): pass + class DataDescriptorWithSetNone: + """with __set__=None (and no __get__)""" + __set__ = None + class DataDescriptorWithDeleteOnly: + """with __delete__ (and no __get__)""" + def __delete__(self, *_): pass + class DataDescriptorWithDeleteNone: + """with __delete__=None (and no __get__)""" + __delete__ = None + class DataDescriptorWithSetDelete: + """with __set__ and __delete__ (and no __get__)""" + def __set__(self, *_): pass + def __delete__(self, *_): pass + class DataDescriptorWithGetSet: + """with __get__ and __set__""" + def __get__(self, *_): pass + def __set__(self, *_): pass + class DataDescriptorWithGetDelete: + """with __get__ and __delete__""" + def __get__(self, *_): pass + def __delete__(self, *_): pass + class DataDescriptorWithGetSetDelete: + """with __get__, __set__ and __delete__""" + def __get__(self, *_): pass + def __set__(self, *_): pass + def __delete__(self, *_): pass + class DataDescriptorSub1(DataDescriptorWithSetOnly): + """with __set__ (inherited; and no __get__)""" + class DataDescriptorSub2(DataDescriptorWithDeleteOnly): + """with __delete__ (inherited; and no __get__)""" + class DataDescriptorSub3(DataDescriptorWithSetDelete): + """with __set__ and __delete__ (both inherited; and no __get__)""" + class DataDescriptorSub4(DataDescriptorWithSetOnly): + """with __set__ (inherited) and __get__""" + def __get__(self, *_): pass + class DataDescriptorSub5(DataDescriptorWithSetNone): + """with __set__=None (inherited) and __get__""" + def __get__(self, *_): pass + class DataDescriptorSub6(DataDescriptorWithDeleteOnly): + """with __delete__ (inherited) and __get__""" + def __get__(self, *_): pass + class DataDescriptorSub7(DataDescriptorWithDeleteNone): + """with __delete__=None (inherited) and __get__""" + def __get__(self, *_): pass + class DataDescriptorSub8(DataDescriptorWithSetDelete): + """with __set__ and __delete__ (both inherited) and __get__""" + def __get__(self, *_): pass + class DataDescriptorSub9(MethodDescriptor): + """with __get__ (inherited) and __set__""" + def __set__(self, *_): pass + class DataDescriptorSub10(MethodDescriptor): + """with __get__ (inherited) and __set__=None""" + __set__ = None + class DataDescriptorSub12(MethodDescriptor): + """with __get__ (inherited) and __delete__""" + def __delete__(self, *_): pass + class DataDescriptorSub11(MethodDescriptor): + """with __get__ (inherited) and __delete__=None""" + __delete__ = None + class DataDescriptorSub13(DataDescriptorWithGetSet): + """with __get__ and __set__ (both inherited)""" + class DataDescriptorSub14(MethodDescriptor, + DataDescriptorWithDeleteOnly): + """with __get__ and __delete__ (both inherited)""" + class DataDescriptorSub15(DataDescriptorWithDeleteOnly, + DataDescriptorWithGetSet): + """with __get__, __set__ and __delete__ (all inherited)""" + + for cls in [ + DataDescriptorWithSetOnly, + DataDescriptorWithSetNone, + DataDescriptorWithDeleteOnly, + DataDescriptorWithDeleteNone, + DataDescriptorWithSetDelete, + DataDescriptorWithGetSet, + DataDescriptorWithGetDelete, + DataDescriptorWithGetSetDelete, + DataDescriptorSub1, + DataDescriptorSub2, + DataDescriptorSub3, + DataDescriptorSub4, + DataDescriptorSub5, + DataDescriptorSub6, + DataDescriptorSub7, + DataDescriptorSub8, + DataDescriptorSub9, + DataDescriptorSub10, + DataDescriptorSub11, + DataDescriptorSub12, + DataDescriptorSub13, + DataDescriptorSub14, + DataDescriptorSub15, + ]: + self.assertTrue( + inspect.isdatadescriptor(cls()), + f'object {cls.__doc__} is a data descriptor', + ) + self.assertFalse( + inspect.isdatadescriptor(cls), + f'class of a data descriptor is not a data descriptor', + ) + + def test_builtin_descriptors(self): + builtin_slot_wrapper = int.__add__ + class Owner: def instance_method(self): pass @classmethod def class_method(cls): pass @staticmethod def static_method(): pass + @property + def a_property(self): pass + class Slotermeyer: + __slots__ = 'a_slot', + attribute_slot = Slotermeyer.__dict__['a_slot'] + frame_locals = types.FrameType.f_locals def function(): pass a_lambda = lambda: None - self.assertFalse(inspect.isdatadescriptor(Test().instance_method), - 'a instance method is not a data descriptor') - self.assertFalse(inspect.isdatadescriptor(Test().class_method), - 'a class method is not a data descriptor') - self.assertFalse(inspect.isdatadescriptor(Test().static_method), - 'a static method is not a data descriptor') - self.assertFalse(inspect.isdatadescriptor(function), - 'a function is not a data descriptor') - self.assertFalse(inspect.isdatadescriptor(a_lambda), - 'a lambda is not a data descriptor') + + # Example builtin method descriptors (non-data descriptors): + + self.assertFalse( + inspect.isdatadescriptor(Owner.__dict__['class_method']), + 'a classmethod object is not a data descriptor') + self.assertFalse( + inspect.isdatadescriptor(Owner.__dict__['static_method']), + 'a staticmethod object is not a data descriptor') + self.assertFalse( + inspect.isdatadescriptor(builtin_slot_wrapper), + 'a builtin slot wrapper is not a data descriptor') + + # ...so also `types.MethodType`/`types.FunctionType` instances + # (not only that they are not data descriptors, but also + # `isdatadescriptor()` explicitly excludes them): + + self.assertFalse(inspect.isdatadescriptor(Owner().instance_method)) + self.assertFalse(inspect.isdatadescriptor(Owner().class_method)) + self.assertFalse(inspect.isdatadescriptor(Owner().static_method)) + self.assertFalse(inspect.isdatadescriptor(Owner.instance_method)) + self.assertFalse(inspect.isdatadescriptor(Owner.class_method)) + self.assertFalse(inspect.isdatadescriptor(Owner.static_method)) + self.assertFalse(inspect.isdatadescriptor(function)) + self.assertFalse(inspect.isdatadescriptor(a_lambda)) + + # Example builtin data descriptors: + + self.assertTrue( + inspect.isdatadescriptor(Owner.__dict__['a_property']), + 'a property is a data descriptor') + + if hasattr(types, 'MemberDescriptorType'): + assert inspect.ismemberdescriptor(attribute_slot) + self.assertTrue( + inspect.isdatadescriptor(attribute_slot), + 'a member descriptor, such as an attribute ' + 'slot descriptor, is a data descriptor') + else: + self.assertTrue( + inspect.isdatadescriptor(attribute_slot), + 'an attribute slot descriptor is a data descriptor') + + if hasattr(types, 'GetSetDescriptorType'): + assert inspect.isgetsetdescriptor(frame_locals) + self.assertTrue( + inspect.isdatadescriptor(frame_locals), + "a getset descriptor, such as a frame's 'f_locals' " + "descriptor, is a data descriptor") + else: + self.assertTrue( + inspect.isdatadescriptor(frame_locals), + "a frame's 'f_locals' descriptor, is a data descriptor") + + def test_descriptor_being_a_class(self): + class DataDescriptorMeta(type): + def __get__(self, *_): pass + def __set__(self, *_): pass + def __delete__(self, *_): pass + class ClassBeingDataDescriptor(metaclass=DataDescriptorMeta): + pass + # `ClassBeingDataDescriptor` itself *is* a data descriptor, but + # it is *also* a class, and `isdatadescriptor()` explicitly + # excludes classes. + self.assertFalse( + inspect.isdatadescriptor(ClassBeingDataDescriptor), + 'classes (instances of type) are explicitly excluded') + + def test_non_descriptors(self): + class Test: + pass + self.assertFalse(inspect.isdatadescriptor(Test())) + self.assertFalse(inspect.isdatadescriptor(Test)) + self.assertFalse(inspect.isdatadescriptor([42])) + self.assertFalse(inspect.isdatadescriptor(42)) _global_ref = object() diff --git a/Misc/NEWS.d/next/Tests/2024-07-22-01-32-00.gh-issue-122102.6sBzoH.rst b/Misc/NEWS.d/next/Tests/2024-07-22-01-32-00.gh-issue-122102.6sBzoH.rst new file mode 100644 index 00000000000000..efd5627974207c --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-07-22-01-32-00.gh-issue-122102.6sBzoH.rst @@ -0,0 +1,2 @@ +Improve the tests for :func:`inspect.ismethoddescriptor` and +:func:`inspect.isdatadescriptor`. Patch by Jan Kaliszewski. From 40ce7937eec5c825042a9e73a43683dd63da2968 Mon Sep 17 00:00:00 2001 From: Jan Kaliszewski Date: Tue, 23 Jul 2024 16:59:33 +0200 Subject: [PATCH 2/8] Update Lib/test/test_inspect/test_inspect.py (use `self.subTest()` in loops) --- Lib/test/test_inspect/test_inspect.py | 52 +++++++++++---------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index dc5c546da1e206..7d90a1765a31f8 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1832,14 +1832,11 @@ class MethodDescriptorSub(MethodDescriptor): MethodDescriptorWithGetNone, MethodDescriptorSub, ]: - self.assertTrue( - inspect.ismethoddescriptor(cls()), - f"an object {cls.__doc__} is a method descriptor", - ) - self.assertFalse( - inspect.ismethoddescriptor(cls), - f'a class of a method descriptor is not a method descriptor', - ) + with self.subTest(f"a descriptor {cls.__doc__}"): + self.assertTrue(inspect.ismethoddescriptor(cls())) + with self.subTest(f"a descriptor class {cls.__doc__} " + f"(not a descriptor itself)"): + self.assertFalse(inspect.ismethoddescriptor(cls)) # Custom data descriptors: @@ -1911,14 +1908,11 @@ class DataDescriptorSub7(DataDescriptorWithSetOnly, DataDescriptorSub6, DataDescriptorSub7, ]: - self.assertFalse( - inspect.ismethoddescriptor(cls()), - f"an object {cls.__doc__} is not a method descriptor", - ) - self.assertFalse( - inspect.ismethoddescriptor(cls), - f'a class of a data descriptor is not a method descriptor', - ) + with self.subTest(f"a descriptor {cls.__doc__}"): + self.assertFalse(inspect.ismethoddescriptor(cls())) + with self.subTest(f"a descriptor class {cls.__doc__} " + f"(not a descriptor itself)"): + self.assertFalse(inspect.ismethoddescriptor(cls)) def test_builtin_descriptors(self): builtin_slot_wrapper = int.__add__ # This one is mentioned in docs. @@ -2031,14 +2025,11 @@ class MethodDescriptorSub(MethodDescriptor): MethodDescriptorWithGetNone, MethodDescriptorSub, ]: - self.assertFalse( - inspect.isdatadescriptor(cls()), - f"object {cls.__doc__} is not a data descriptor", - ) - self.assertFalse( - inspect.isdatadescriptor(cls), - f'class of a method descriptor is not a data descriptor', - ) + with self.subTest(f"a descriptor {cls.__doc__}"): + self.assertFalse(inspect.isdatadescriptor(cls())) + with self.subTest(f"a descriptor class {cls.__doc__} " + f"(not a descriptor itself)"): + self.assertFalse(inspect.isdatadescriptor(cls)) # Custom data descriptors: @@ -2138,14 +2129,11 @@ class DataDescriptorSub15(DataDescriptorWithDeleteOnly, DataDescriptorSub14, DataDescriptorSub15, ]: - self.assertTrue( - inspect.isdatadescriptor(cls()), - f'object {cls.__doc__} is a data descriptor', - ) - self.assertFalse( - inspect.isdatadescriptor(cls), - f'class of a data descriptor is not a data descriptor', - ) + with self.subTest(f"a descriptor {cls.__doc__}"): + self.assertTrue(inspect.isdatadescriptor(cls())) + with self.subTest(f"a descriptor class {cls.__doc__} " + f"(not a descriptor itself)"): + self.assertFalse(inspect.isdatadescriptor(cls)) def test_builtin_descriptors(self): builtin_slot_wrapper = int.__add__ From 9e5c475e108ff07d23578354f3cd89c6d4656923 Mon Sep 17 00:00:00 2001 From: Jan Kaliszewski Date: Tue, 23 Jul 2024 21:11:58 +0200 Subject: [PATCH 3/8] Update Lib/test/test_inspect/test_inspect.py (further minor tweaks/improvements...) --- Lib/test/test_inspect/test_inspect.py | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 7d90a1765a31f8..316af8d4f413ff 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1832,10 +1832,10 @@ class MethodDescriptorSub(MethodDescriptor): MethodDescriptorWithGetNone, MethodDescriptorSub, ]: - with self.subTest(f"a descriptor {cls.__doc__}"): + with self.subTest(f"a descriptor {cls.__doc__}", cls=cls): self.assertTrue(inspect.ismethoddescriptor(cls())) - with self.subTest(f"a descriptor class {cls.__doc__} " - f"(not a descriptor itself)"): + with self.subTest(f"a descriptor class (not a descriptor " + f"itself) {cls.__doc__} ", cls=cls): self.assertFalse(inspect.ismethoddescriptor(cls)) # Custom data descriptors: @@ -1908,10 +1908,10 @@ class DataDescriptorSub7(DataDescriptorWithSetOnly, DataDescriptorSub6, DataDescriptorSub7, ]: - with self.subTest(f"a descriptor {cls.__doc__}"): + with self.subTest(f"a descriptor {cls.__doc__}", cls=cls): self.assertFalse(inspect.ismethoddescriptor(cls())) - with self.subTest(f"a descriptor class {cls.__doc__} " - f"(not a descriptor itself)"): + with self.subTest(f"a descriptor class (not a descriptor " + f"itself) {cls.__doc__} ", cls=cls): self.assertFalse(inspect.ismethoddescriptor(cls)) def test_builtin_descriptors(self): @@ -2025,10 +2025,10 @@ class MethodDescriptorSub(MethodDescriptor): MethodDescriptorWithGetNone, MethodDescriptorSub, ]: - with self.subTest(f"a descriptor {cls.__doc__}"): + with self.subTest(f"a descriptor {cls.__doc__}", cls=cls): self.assertFalse(inspect.isdatadescriptor(cls())) - with self.subTest(f"a descriptor class {cls.__doc__} " - f"(not a descriptor itself)"): + with self.subTest(f"a descriptor class (not a descriptor " + f"itself) {cls.__doc__} ", cls=cls): self.assertFalse(inspect.isdatadescriptor(cls)) # Custom data descriptors: @@ -2129,10 +2129,10 @@ class DataDescriptorSub15(DataDescriptorWithDeleteOnly, DataDescriptorSub14, DataDescriptorSub15, ]: - with self.subTest(f"a descriptor {cls.__doc__}"): + with self.subTest(f"a descriptor {cls.__doc__}", cls=cls): self.assertTrue(inspect.isdatadescriptor(cls())) - with self.subTest(f"a descriptor class {cls.__doc__} " - f"(not a descriptor itself)"): + with self.subTest(f"a descriptor class (not a descriptor " + f"itself) {cls.__doc__} ", cls=cls): self.assertFalse(inspect.isdatadescriptor(cls)) def test_builtin_descriptors(self): @@ -2155,15 +2155,15 @@ def function(): # Example builtin method descriptors (non-data descriptors): + self.assertFalse( + inspect.isdatadescriptor(builtin_slot_wrapper), + 'a builtin slot wrapper is not a data descriptor') self.assertFalse( inspect.isdatadescriptor(Owner.__dict__['class_method']), 'a classmethod object is not a data descriptor') self.assertFalse( inspect.isdatadescriptor(Owner.__dict__['static_method']), 'a staticmethod object is not a data descriptor') - self.assertFalse( - inspect.isdatadescriptor(builtin_slot_wrapper), - 'a builtin slot wrapper is not a data descriptor') # ...so also `types.MethodType`/`types.FunctionType` instances # (not only that they are not data descriptors, but also From ca12cc266135d09b3f671215eb13905a1db2ae7a Mon Sep 17 00:00:00 2001 From: Jan Kaliszewski Date: Wed, 24 Jul 2024 13:49:06 +0200 Subject: [PATCH 4/8] Update Lib/test/test_inspect/test_inspect.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_inspect/test_inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 316af8d4f413ff..e1b2c9e063297a 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1983,7 +1983,7 @@ def function(): else: self.assertFalse( inspect.ismethoddescriptor(frame_locals), - "a frame's 'f_locals' descriptor, is not a method descriptor") + "a frame's 'f_locals' descriptor is not a method descriptor") def test_descriptor_being_a_class(self): class MethodDescriptorMeta(type): From ebcb0dc4dd3e8114beb14f125ab4c658cb09e838 Mon Sep 17 00:00:00 2001 From: Jan Kaliszewski Date: Thu, 25 Jul 2024 21:30:53 +0200 Subject: [PATCH 5/8] Update Lib/test/test_inspect/test_inspect.py (further rearrangements/improvements...) --- Lib/test/test_inspect/test_inspect.py | 48 +++++++++++++++++++-------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index e1b2c9e063297a..a9c9bfb32df8b9 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1832,11 +1832,9 @@ class MethodDescriptorSub(MethodDescriptor): MethodDescriptorWithGetNone, MethodDescriptorSub, ]: - with self.subTest(f"a descriptor {cls.__doc__}", cls=cls): + with self.subTest(f"a descriptor {cls.__doc__}; " + f"its class: {cls.__name__}"): self.assertTrue(inspect.ismethoddescriptor(cls())) - with self.subTest(f"a descriptor class (not a descriptor " - f"itself) {cls.__doc__} ", cls=cls): - self.assertFalse(inspect.ismethoddescriptor(cls)) # Custom data descriptors: @@ -1908,10 +1906,22 @@ class DataDescriptorSub7(DataDescriptorWithSetOnly, DataDescriptorSub6, DataDescriptorSub7, ]: - with self.subTest(f"a descriptor {cls.__doc__}", cls=cls): + with self.subTest(f"a descriptor {cls.__doc__}; " + f"its class: {cls.__name__}"): self.assertFalse(inspect.ismethoddescriptor(cls())) - with self.subTest(f"a descriptor class (not a descriptor " - f"itself) {cls.__doc__} ", cls=cls): + + # Custom *classes* of (method/data) descriptors: + + for cls in [ + MethodDescriptor, + MethodDescriptorSub, + DataDescriptorWithSetOnly, + DataDescriptorWithGetSetDelete, + DataDescriptorSub1, + DataDescriptorSub7, + ]: + with self.subTest(f"a descriptor class (not a " + f"descriptor itself): {cls.__name__}"): self.assertFalse(inspect.ismethoddescriptor(cls)) def test_builtin_descriptors(self): @@ -2025,11 +2035,9 @@ class MethodDescriptorSub(MethodDescriptor): MethodDescriptorWithGetNone, MethodDescriptorSub, ]: - with self.subTest(f"a descriptor {cls.__doc__}", cls=cls): + with self.subTest(f"a descriptor {cls.__doc__}; " + f"its class: {cls.__name__}"): self.assertFalse(inspect.isdatadescriptor(cls())) - with self.subTest(f"a descriptor class (not a descriptor " - f"itself) {cls.__doc__} ", cls=cls): - self.assertFalse(inspect.isdatadescriptor(cls)) # Custom data descriptors: @@ -2129,10 +2137,22 @@ class DataDescriptorSub15(DataDescriptorWithDeleteOnly, DataDescriptorSub14, DataDescriptorSub15, ]: - with self.subTest(f"a descriptor {cls.__doc__}", cls=cls): + with self.subTest(f"a descriptor {cls.__doc__}; " + f"its class: {cls.__name__}"): self.assertTrue(inspect.isdatadescriptor(cls())) - with self.subTest(f"a descriptor class (not a descriptor " - f"itself) {cls.__doc__} ", cls=cls): + + # Custom *classes* of (method/data) descriptors: + + for cls in [ + MethodDescriptor, + MethodDescriptorSub, + DataDescriptorWithSetOnly, + DataDescriptorWithGetSetDelete, + DataDescriptorSub1, + DataDescriptorSub15, + ]: + with self.subTest(f"a descriptor class (not a " + f"descriptor itself): {cls.__name__}"): self.assertFalse(inspect.isdatadescriptor(cls)) def test_builtin_descriptors(self): From 9dab20876cc4c102439d61f3a2ae738261101413 Mon Sep 17 00:00:00 2001 From: Jan Kaliszewski Date: Sat, 3 Aug 2024 19:50:46 +0200 Subject: [PATCH 6/8] Remove Misc/NEWS.d/next/Tests/2024-07-22-01-32-00.gh-issue-122102.6sBzoH.rst --- .../next/Tests/2024-07-22-01-32-00.gh-issue-122102.6sBzoH.rst | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 Misc/NEWS.d/next/Tests/2024-07-22-01-32-00.gh-issue-122102.6sBzoH.rst diff --git a/Misc/NEWS.d/next/Tests/2024-07-22-01-32-00.gh-issue-122102.6sBzoH.rst b/Misc/NEWS.d/next/Tests/2024-07-22-01-32-00.gh-issue-122102.6sBzoH.rst deleted file mode 100644 index efd5627974207c..00000000000000 --- a/Misc/NEWS.d/next/Tests/2024-07-22-01-32-00.gh-issue-122102.6sBzoH.rst +++ /dev/null @@ -1,2 +0,0 @@ -Improve the tests for :func:`inspect.ismethoddescriptor` and -:func:`inspect.isdatadescriptor`. Patch by Jan Kaliszewski. From fc33fd5cf69e0da73fc5b68460cdf43330b2fa34 Mon Sep 17 00:00:00 2001 From: Jan Kaliszewski Date: Sat, 3 Aug 2024 19:58:01 +0200 Subject: [PATCH 7/8] Update Lib/test/test_inspect/test_inspect.py (further cosmetics...) --- Lib/test/test_inspect/test_inspect.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index a9c9bfb32df8b9..98055fd77fbaba 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1936,11 +1936,8 @@ def static_method(): pass def a_property(self): pass class Slotermeyer: __slots__ = 'a_slot', - attribute_slot = Slotermeyer.__dict__['a_slot'] - frame_locals = types.FrameType.f_locals def function(): pass - a_lambda = lambda: None # Example builtin method descriptors (non-data descriptors): @@ -1965,7 +1962,7 @@ def function(): self.assertFalse(inspect.ismethoddescriptor(Owner.class_method)) self.assertFalse(inspect.ismethoddescriptor(Owner.static_method)) self.assertFalse(inspect.ismethoddescriptor(function)) - self.assertFalse(inspect.ismethoddescriptor(a_lambda)) + self.assertFalse(inspect.ismethoddescriptor(lambda: None)) # Example builtin data descriptors: @@ -1973,6 +1970,7 @@ def function(): inspect.ismethoddescriptor(Owner.__dict__['a_property']), 'a property is not a method descriptor') + attribute_slot = Slotermeyer.__dict__['a_slot'] if hasattr(types, 'MemberDescriptorType'): assert inspect.ismemberdescriptor(attribute_slot) self.assertFalse( @@ -1984,6 +1982,7 @@ def function(): inspect.ismethoddescriptor(attribute_slot), 'an attribute slot descriptor is not a method descriptor') + frame_locals = types.FrameType.f_locals if hasattr(types, 'GetSetDescriptorType'): assert inspect.isgetsetdescriptor(frame_locals) self.assertFalse( @@ -2167,11 +2166,8 @@ def static_method(): pass def a_property(self): pass class Slotermeyer: __slots__ = 'a_slot', - attribute_slot = Slotermeyer.__dict__['a_slot'] - frame_locals = types.FrameType.f_locals def function(): pass - a_lambda = lambda: None # Example builtin method descriptors (non-data descriptors): @@ -2196,7 +2192,7 @@ def function(): self.assertFalse(inspect.isdatadescriptor(Owner.class_method)) self.assertFalse(inspect.isdatadescriptor(Owner.static_method)) self.assertFalse(inspect.isdatadescriptor(function)) - self.assertFalse(inspect.isdatadescriptor(a_lambda)) + self.assertFalse(inspect.isdatadescriptor(lambda: None)) # Example builtin data descriptors: @@ -2204,6 +2200,7 @@ def function(): inspect.isdatadescriptor(Owner.__dict__['a_property']), 'a property is a data descriptor') + attribute_slot = Slotermeyer.__dict__['a_slot'] if hasattr(types, 'MemberDescriptorType'): assert inspect.ismemberdescriptor(attribute_slot) self.assertTrue( @@ -2215,6 +2212,7 @@ def function(): inspect.isdatadescriptor(attribute_slot), 'an attribute slot descriptor is a data descriptor') + frame_locals = types.FrameType.f_locals if hasattr(types, 'GetSetDescriptorType'): assert inspect.isgetsetdescriptor(frame_locals) self.assertTrue( From ec79a04e1fd05892939f154b6258d94d1d0d138d Mon Sep 17 00:00:00 2001 From: Jan Kaliszewski Date: Sat, 3 Aug 2024 21:21:49 +0200 Subject: [PATCH 8/8] Include also `functools.partial`-related cases --- Lib/test/test_inspect/test_inspect.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index d44f71e9dedcce..1faf66ce3d7e37 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -1715,18 +1715,19 @@ class DataDescriptorSub7(DataDescriptorWithSetOnly, def test_builtin_descriptors(self): builtin_slot_wrapper = int.__add__ # This one is mentioned in docs. + def function(): + pass class Owner: def instance_method(self): pass @classmethod def class_method(cls): pass @staticmethod def static_method(): pass + partial_as_method = functools.partial(function) @property def a_property(self): pass class Slotermeyer: __slots__ = 'a_slot', - def function(): - pass # Example builtin method descriptors (non-data descriptors): @@ -1739,6 +1740,9 @@ def function(): self.assertTrue( inspect.ismethoddescriptor(Owner.__dict__['static_method']), 'a staticmethod object is a method descriptor') + self.assertTrue( + inspect.ismethoddescriptor(functools.partial(function)), + 'a functools.partial object is a method descriptor') # `types.MethodType`/`types.FunctionType` instances (note that # they *are* method descriptors, but `ismethoddescriptor()` @@ -1747,12 +1751,12 @@ def function(): self.assertFalse(inspect.ismethoddescriptor(Owner().instance_method)) self.assertFalse(inspect.ismethoddescriptor(Owner().class_method)) self.assertFalse(inspect.ismethoddescriptor(Owner().static_method)) + self.assertFalse(inspect.ismethoddescriptor(Owner().partial_as_method)) self.assertFalse(inspect.ismethoddescriptor(Owner.instance_method)) self.assertFalse(inspect.ismethoddescriptor(Owner.class_method)) self.assertFalse(inspect.ismethoddescriptor(Owner.static_method)) self.assertFalse(inspect.ismethoddescriptor(function)) self.assertFalse(inspect.ismethoddescriptor(lambda: None)) - self.assertTrue(inspect.ismethoddescriptor(functools.partial(function))) # Example builtin data descriptors: @@ -1946,18 +1950,19 @@ class DataDescriptorSub15(DataDescriptorWithDeleteOnly, def test_builtin_descriptors(self): builtin_slot_wrapper = int.__add__ + def function(): + pass class Owner: def instance_method(self): pass @classmethod def class_method(cls): pass @staticmethod def static_method(): pass + partial_as_method = functools.partial(function) @property def a_property(self): pass class Slotermeyer: __slots__ = 'a_slot', - def function(): - pass # Example builtin method descriptors (non-data descriptors): @@ -1970,6 +1975,9 @@ def function(): self.assertFalse( inspect.isdatadescriptor(Owner.__dict__['static_method']), 'a staticmethod object is not a data descriptor') + self.assertFalse( + inspect.isdatadescriptor(functools.partial(function)), + 'a functools.partial object is not a data descriptor') # ...so also `types.MethodType`/`types.FunctionType` instances # (not only that they are not data descriptors, but also @@ -1978,6 +1986,7 @@ def function(): self.assertFalse(inspect.isdatadescriptor(Owner().instance_method)) self.assertFalse(inspect.isdatadescriptor(Owner().class_method)) self.assertFalse(inspect.isdatadescriptor(Owner().static_method)) + self.assertFalse(inspect.isdatadescriptor(Owner().partial_as_method)) self.assertFalse(inspect.isdatadescriptor(Owner.instance_method)) self.assertFalse(inspect.isdatadescriptor(Owner.class_method)) self.assertFalse(inspect.isdatadescriptor(Owner.static_method))