mirror of
https://github.com/python/cpython.git
synced 2024-11-24 18:34:43 +08:00
bpo-26103: Fix inspect.isdatadescriptor() and a data descriptor definition. (GH-1959)
Look for '__set__' or '__delete__'.
This commit is contained in:
parent
aef639f626
commit
4054b172ab
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -603,6 +603,7 @@ Peter Haight
|
||||
Václav Haisman
|
||||
Zbigniew Halas
|
||||
Walker Hale IV
|
||||
Aaron Christopher Hall
|
||||
Bob Halley
|
||||
Jesse Hallio
|
||||
Jun Hamano
|
||||
|
@ -0,0 +1,2 @@
|
||||
Correct ``inspect.isdatadescriptor`` to look for ``__set__`` or
|
||||
``__delete__``. Patch by Aaron Hall.
|
Loading…
Reference in New Issue
Block a user