Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Doc/howto/descriptor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
4 changes: 2 additions & 2 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,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
Expand All @@ -120,7 +120,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
Expand Down
59 changes: 57 additions & 2 deletions Lib/test/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,61 @@ class C(metaclass=M):
attrs = [a[0] for a in inspect.getmembers(C)]
self.assertNotIn('missing', attrs)

class TestIsDataDescriptor(unittest.TestCase):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are not ran. Add the test class in run_unittest() list below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added


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):
Expand Down Expand Up @@ -2018,7 +2073,7 @@ def test_staticmethod(*args): # NOQA
((('args', ..., ..., 'var_positional'),), ...))
self.assertEqual(self.signature(A.f3),
((('args', ..., ..., 'var_positional'),), ...))
self.assertEqual(self.signature(A.f4),
self.assertEqual(self.signature(A.f4),
((('args', ..., ..., 'var_positional'),
('kwargs', ..., ..., 'var_keyword')), ...))
@cpython_only
Expand Down Expand Up @@ -3713,7 +3768,7 @@ def test_main():
TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState,
TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject,
TestBoundArguments, TestSignaturePrivateHelpers,
TestSignatureDefinitions,
TestSignatureDefinitions, TestIsDataDescriptor,
TestGetClosureVars, TestUnwrap, TestMain, TestReload,
TestGetCoroutineState
)
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ Peter Haight
Václav Haisman
Zbigniew Halas
Walker Hale IV
Aaron Christopher Hall
Bob Halley
Jesse Hallio
Jun Hamano
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Correct ``inspect.isdatadescriptor`` to look for ``__set__`` or
``__delete__``. Patch by Aaron Hall.