Skip to content

Commit 4054b17

Browse files
aaronchallserhiy-storchaka
authored andcommitted
bpo-26103: Fix inspect.isdatadescriptor() and a data descriptor definition. (GH-1959)
Look for '__set__' or '__delete__'.
1 parent aef639f commit 4054b17

File tree

5 files changed

+62
-4
lines changed

5 files changed

+62
-4
lines changed

Doc/howto/descriptor.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ That is all there is to it. Define any of these methods and an object is
5858
considered a descriptor and can override default behavior upon being looked up
5959
as an attribute.
6060

61-
If an object defines both :meth:`__get__` and :meth:`__set__`, it is considered
61+
If an object defines :meth:`__set__` or :meth:`__delete__`, it is considered
6262
a data descriptor. Descriptors that only define :meth:`__get__` are called
6363
non-data descriptors (they are typically used for methods but other uses are
6464
possible).

Lib/inspect.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def ismethoddescriptor(object):
110110
def isdatadescriptor(object):
111111
"""Return true if the object is a data descriptor.
112112
113-
Data descriptors have both a __get__ and a __set__ attribute. Examples are
113+
Data descriptors have a __set__ or a __delete__ attribute. Examples are
114114
properties (defined in Python) and getsets and members (defined in C).
115115
Typically, data descriptors will also have __name__ and __doc__ attributes
116116
(properties, getsets, and members have both of these attributes), but this
@@ -119,7 +119,7 @@ def isdatadescriptor(object):
119119
# mutual exclusion
120120
return False
121121
tp = type(object)
122-
return hasattr(tp, "__set__") and hasattr(tp, "__get__")
122+
return hasattr(tp, "__set__") or hasattr(tp, "__delete__")
123123

124124
if hasattr(types, 'MemberDescriptorType'):
125125
# CPython and equivalent

Lib/test/test_inspect.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,61 @@ class C(metaclass=M):
11341134
attrs = [a[0] for a in inspect.getmembers(C)]
11351135
self.assertNotIn('missing', attrs)
11361136

1137+
class TestIsDataDescriptor(unittest.TestCase):
1138+
1139+
def test_custom_descriptors(self):
1140+
class NonDataDescriptor:
1141+
def __get__(self, value, type=None): pass
1142+
class DataDescriptor0:
1143+
def __set__(self, name, value): pass
1144+
class DataDescriptor1:
1145+
def __delete__(self, name): pass
1146+
class DataDescriptor2:
1147+
__set__ = None
1148+
self.assertFalse(inspect.isdatadescriptor(NonDataDescriptor()),
1149+
'class with only __get__ not a data descriptor')
1150+
self.assertTrue(inspect.isdatadescriptor(DataDescriptor0()),
1151+
'class with __set__ is a data descriptor')
1152+
self.assertTrue(inspect.isdatadescriptor(DataDescriptor1()),
1153+
'class with __delete__ is a data descriptor')
1154+
self.assertTrue(inspect.isdatadescriptor(DataDescriptor2()),
1155+
'class with __set__ = None is a data descriptor')
1156+
1157+
def test_slot(self):
1158+
class Slotted:
1159+
__slots__ = 'foo',
1160+
self.assertTrue(inspect.isdatadescriptor(Slotted.foo),
1161+
'a slot is a data descriptor')
1162+
1163+
def test_property(self):
1164+
class Propertied:
1165+
@property
1166+
def a_property(self):
1167+
pass
1168+
self.assertTrue(inspect.isdatadescriptor(Propertied.a_property),
1169+
'a property is a data descriptor')
1170+
1171+
def test_functions(self):
1172+
class Test(object):
1173+
def instance_method(self): pass
1174+
@classmethod
1175+
def class_method(cls): pass
1176+
@staticmethod
1177+
def static_method(): pass
1178+
def function():
1179+
pass
1180+
a_lambda = lambda: None
1181+
self.assertFalse(inspect.isdatadescriptor(Test().instance_method),
1182+
'a instance method is not a data descriptor')
1183+
self.assertFalse(inspect.isdatadescriptor(Test().class_method),
1184+
'a class method is not a data descriptor')
1185+
self.assertFalse(inspect.isdatadescriptor(Test().static_method),
1186+
'a static method is not a data descriptor')
1187+
self.assertFalse(inspect.isdatadescriptor(function),
1188+
'a function is not a data descriptor')
1189+
self.assertFalse(inspect.isdatadescriptor(a_lambda),
1190+
'a lambda is not a data descriptor')
1191+
11371192

11381193
_global_ref = object()
11391194
class TestGetClosureVars(unittest.TestCase):
@@ -3792,7 +3847,7 @@ def test_main():
37923847
TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState,
37933848
TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject,
37943849
TestBoundArguments, TestSignaturePrivateHelpers,
3795-
TestSignatureDefinitions,
3850+
TestSignatureDefinitions, TestIsDataDescriptor,
37963851
TestGetClosureVars, TestUnwrap, TestMain, TestReload,
37973852
TestGetCoroutineState
37983853
)

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,7 @@ Peter Haight
603603
Václav Haisman
604604
Zbigniew Halas
605605
Walker Hale IV
606+
Aaron Christopher Hall
606607
Bob Halley
607608
Jesse Hallio
608609
Jun Hamano
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Correct ``inspect.isdatadescriptor`` to look for ``__set__`` or
2+
``__delete__``. Patch by Aaron Hall.

0 commit comments

Comments
 (0)