|
55 | 55 | # ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode,
|
56 | 56 | # isbuiltin, isroutine, isgenerator, isgeneratorfunction, getmembers,
|
57 | 57 | # getdoc, getfile, getmodule, getsourcefile, getcomments, getsource,
|
58 |
| -# getclasstree, getargvalues, formatargvalues, |
59 |
| -# currentframe, stack, trace, isdatadescriptor, |
60 |
| -# ismethodwrapper |
| 58 | +# getclasstree, getargvalues, formatargvalues, currentframe, |
| 59 | +# stack, trace, ismethoddescriptor, isdatadescriptor, ismethodwrapper |
61 | 60 |
|
62 | 61 | # NOTE: There are some additional tests relating to interaction with
|
63 | 62 | # zipimport in the test_zipimport_support test module.
|
@@ -179,6 +178,7 @@ def test_excluding_predicates(self):
|
179 | 178 | self.istest(inspect.ismethod, 'git.argue')
|
180 | 179 | self.istest(inspect.ismethod, 'mod.custom_method')
|
181 | 180 | self.istest(inspect.ismodule, 'mod')
|
| 181 | + self.istest(inspect.ismethoddescriptor, 'int.__add__') |
182 | 182 | self.istest(inspect.isdatadescriptor, 'collections.defaultdict.default_factory')
|
183 | 183 | self.istest(inspect.isgenerator, '(x for x in range(2))')
|
184 | 184 | self.istest(inspect.isgeneratorfunction, 'generator_function_example')
|
@@ -1813,6 +1813,121 @@ def test_typing_replacement(self):
|
1813 | 1813 | self.assertEqual(inspect.formatannotation(ann1), 'Union[List[testModule.typing.A], int]')
|
1814 | 1814 |
|
1815 | 1815 |
|
| 1816 | +class TestIsMethodDescriptor(unittest.TestCase): |
| 1817 | + |
| 1818 | + def test_custom_descriptors(self): |
| 1819 | + class MethodDescriptor: |
| 1820 | + def __get__(self, *_): pass |
| 1821 | + class MethodDescriptorSub(MethodDescriptor): |
| 1822 | + pass |
| 1823 | + class DataDescriptorWithNoGet: |
| 1824 | + def __set__(self, *_): pass |
| 1825 | + class DataDescriptorWithGetSet: |
| 1826 | + def __get__(self, *_): pass |
| 1827 | + def __set__(self, *_): pass |
| 1828 | + class DataDescriptorWithGetDelete: |
| 1829 | + def __get__(self, *_): pass |
| 1830 | + def __delete__(self, *_): pass |
| 1831 | + class DataDescriptorSub(DataDescriptorWithNoGet, |
| 1832 | + DataDescriptorWithGetDelete): |
| 1833 | + pass |
| 1834 | + |
| 1835 | + # Custom method descriptors: |
| 1836 | + self.assertTrue( |
| 1837 | + inspect.ismethoddescriptor(MethodDescriptor()), |
| 1838 | + '__get__ and no __set__/__delete__ => method descriptor') |
| 1839 | + self.assertTrue( |
| 1840 | + inspect.ismethoddescriptor(MethodDescriptorSub()), |
| 1841 | + '__get__ (inherited) and no __set__/__delete__' |
| 1842 | + ' => method descriptor') |
| 1843 | + |
| 1844 | + # Custom data descriptors: |
| 1845 | + self.assertFalse( |
| 1846 | + inspect.ismethoddescriptor(DataDescriptorWithNoGet()), |
| 1847 | + '__set__ (and no __get__) => not a method descriptor') |
| 1848 | + self.assertFalse( |
| 1849 | + inspect.ismethoddescriptor(DataDescriptorWithGetSet()), |
| 1850 | + '__get__ and __set__ => not a method descriptor') |
| 1851 | + self.assertFalse( |
| 1852 | + inspect.ismethoddescriptor(DataDescriptorWithGetDelete()), |
| 1853 | + '__get__ and __delete__ => not a method descriptor') |
| 1854 | + self.assertFalse( |
| 1855 | + inspect.ismethoddescriptor(DataDescriptorSub()), |
| 1856 | + '__get__, __set__ and __delete__ => not a method descriptor') |
| 1857 | + |
| 1858 | + # Classes of descriptors (are *not* descriptors themselves): |
| 1859 | + self.assertFalse(inspect.ismethoddescriptor(MethodDescriptor)) |
| 1860 | + self.assertFalse(inspect.ismethoddescriptor(MethodDescriptorSub)) |
| 1861 | + self.assertFalse(inspect.ismethoddescriptor(DataDescriptorSub)) |
| 1862 | + |
| 1863 | + def test_builtin_descriptors(self): |
| 1864 | + builtin_slot_wrapper = int.__add__ # This one is mentioned in docs. |
| 1865 | + class Owner: |
| 1866 | + def instance_method(self): pass |
| 1867 | + @classmethod |
| 1868 | + def class_method(cls): pass |
| 1869 | + @staticmethod |
| 1870 | + def static_method(): pass |
| 1871 | + @property |
| 1872 | + def a_property(self): pass |
| 1873 | + class Slotermeyer: |
| 1874 | + __slots__ = 'a_slot', |
| 1875 | + def function(): |
| 1876 | + pass |
| 1877 | + a_lambda = lambda: None |
| 1878 | + |
| 1879 | + # Example builtin method descriptors: |
| 1880 | + self.assertTrue( |
| 1881 | + inspect.ismethoddescriptor(builtin_slot_wrapper), |
| 1882 | + 'a builtin slot wrapper is a method descriptor') |
| 1883 | + self.assertTrue( |
| 1884 | + inspect.ismethoddescriptor(Owner.__dict__['class_method']), |
| 1885 | + 'a classmethod object is a method descriptor') |
| 1886 | + self.assertTrue( |
| 1887 | + inspect.ismethoddescriptor(Owner.__dict__['static_method']), |
| 1888 | + 'a staticmethod object is a method descriptor') |
| 1889 | + |
| 1890 | + # Example builtin data descriptors: |
| 1891 | + self.assertFalse( |
| 1892 | + inspect.ismethoddescriptor(Owner.__dict__['a_property']), |
| 1893 | + 'a property is not a method descriptor') |
| 1894 | + self.assertFalse( |
| 1895 | + inspect.ismethoddescriptor(Slotermeyer.__dict__['a_slot']), |
| 1896 | + 'a slot is not a method descriptor') |
| 1897 | + |
| 1898 | + # `types.MethodType`/`types.FunctionType` instances (they *are* |
| 1899 | + # method descriptors, but `ismethoddescriptor()` explicitly |
| 1900 | + # excludes them): |
| 1901 | + self.assertFalse(inspect.ismethoddescriptor(Owner().instance_method)) |
| 1902 | + self.assertFalse(inspect.ismethoddescriptor(Owner().class_method)) |
| 1903 | + self.assertFalse(inspect.ismethoddescriptor(Owner().static_method)) |
| 1904 | + self.assertFalse(inspect.ismethoddescriptor(Owner.instance_method)) |
| 1905 | + self.assertFalse(inspect.ismethoddescriptor(Owner.class_method)) |
| 1906 | + self.assertFalse(inspect.ismethoddescriptor(Owner.static_method)) |
| 1907 | + self.assertFalse(inspect.ismethoddescriptor(function)) |
| 1908 | + self.assertFalse(inspect.ismethoddescriptor(a_lambda)) |
| 1909 | + |
| 1910 | + def test_descriptor_being_a_class(self): |
| 1911 | + class MethodDescriptorMeta(type): |
| 1912 | + def __get__(self, *_): pass |
| 1913 | + class ClassBeingMethodDescriptor(metaclass=MethodDescriptorMeta): |
| 1914 | + pass |
| 1915 | + # `ClassBeingMethodDescriptor` itself *is* a method descriptor, |
| 1916 | + # but it is *also* a class, and `ismethoddescriptor()` explicitly |
| 1917 | + # excludes classes. |
| 1918 | + self.assertFalse( |
| 1919 | + inspect.ismethoddescriptor(ClassBeingMethodDescriptor), |
| 1920 | + 'classes (instances of type) are explicitly excluded') |
| 1921 | + |
| 1922 | + def test_non_descriptors(self): |
| 1923 | + class Test: |
| 1924 | + pass |
| 1925 | + self.assertFalse(inspect.ismethoddescriptor(Test())) |
| 1926 | + self.assertFalse(inspect.ismethoddescriptor(Test)) |
| 1927 | + self.assertFalse(inspect.ismethoddescriptor([42])) |
| 1928 | + self.assertFalse(inspect.ismethoddescriptor(42)) |
| 1929 | + |
| 1930 | + |
1816 | 1931 | class TestIsDataDescriptor(unittest.TestCase):
|
1817 | 1932 |
|
1818 | 1933 | def test_custom_descriptors(self):
|
|
0 commit comments