summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/howto/descriptor.rst2
-rw-r--r--Lib/inspect.py4
-rw-r--r--Lib/test/test_inspect.py57
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS.d/next/Library/2018-05-14-09-07-14.bpo-26103._zU8E2.rst2
5 files changed, 62 insertions, 4 deletions
diff --git a/Doc/howto/descriptor.rst b/Doc/howto/descriptor.rst
index 5e85a9a..6e4aa3e 100644
--- a/Doc/howto/descriptor.rst
+++ b/Doc/howto/descriptor.rst
@@ -58,7 +58,7 @@ That is all there is to it. Define any of these methods and an object is
considered a descriptor and can override default behavior upon being looked up
as an attribute.
-If an object defines both :meth:`__get__` and :meth:`__set__`, it is considered
+If an object defines :meth:`__set__` or :meth:`__delete__`, it is considered
a data descriptor. Descriptors that only define :meth:`__get__` are called
non-data descriptors (they are typically used for methods but other uses are
possible).
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 512785f..e5d312e 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -110,7 +110,7 @@ def ismethoddescriptor(object):
def isdatadescriptor(object):
"""Return true if the object is a data descriptor.
- Data descriptors have both a __get__ and a __set__ attribute. Examples are
+ Data descriptors have a __set__ or a __delete__ attribute. Examples are
properties (defined in Python) and getsets and members (defined in C).
Typically, data descriptors will also have __name__ and __doc__ attributes
(properties, getsets, and members have both of these attributes), but this
@@ -119,7 +119,7 @@ def isdatadescriptor(object):
# mutual exclusion
return False
tp = type(object)
- return hasattr(tp, "__set__") and hasattr(tp, "__get__")
+ return hasattr(tp, "__set__") or hasattr(tp, "__delete__")
if hasattr(types, 'MemberDescriptorType'):
# CPython and equivalent
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index 3481a57..ee227a6 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -1134,6 +1134,61 @@ class TestClassesAndFunctions(unittest.TestCase):
attrs = [a[0] for a in inspect.getmembers(C)]
self.assertNotIn('missing', attrs)
+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')
+
+ 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')
+
+ def test_functions(self):
+ class Test(object):
+ def instance_method(self): pass
+ @classmethod
+ def class_method(cls): pass
+ @staticmethod
+ def static_method(): pass
+ 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')
+
_global_ref = object()
class TestGetClosureVars(unittest.TestCase):
@@ -3792,7 +3847,7 @@ def test_main():
TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState,
TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject,
TestBoundArguments, TestSignaturePrivateHelpers,
- TestSignatureDefinitions,
+ TestSignatureDefinitions, TestIsDataDescriptor,
TestGetClosureVars, TestUnwrap, TestMain, TestReload,
TestGetCoroutineState
)
diff --git a/Misc/ACKS b/Misc/ACKS
index cb7f4cd..4d295b6 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -603,6 +603,7 @@ Peter Haight
Václav Haisman
Zbigniew Halas
Walker Hale IV
+Aaron Christopher Hall
Bob Halley
Jesse Hallio
Jun Hamano
diff --git a/Misc/NEWS.d/next/Library/2018-05-14-09-07-14.bpo-26103._zU8E2.rst b/Misc/NEWS.d/next/Library/2018-05-14-09-07-14.bpo-26103._zU8E2.rst
new file mode 100644
index 0000000..cb4c41b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-05-14-09-07-14.bpo-26103._zU8E2.rst
@@ -0,0 +1,2 @@
+Correct ``inspect.isdatadescriptor`` to look for ``__set__`` or
+``__delete__``. Patch by Aaron Hall.