cpython: b08bf8df8eec (original) (raw)
Mercurial > cpython
changeset 74105:b08bf8df8eec
Issue #1785: Fix inspect and pydoc with misbehaving descriptors. Also fixes issue #13581: `help(type)` wouldn't display anything. [#1785]
Antoine Pitrou solipsis@pitrou.net | |
---|---|
date | Wed, 21 Dec 2011 09:59:49 +0100 |
parents | ec44f2e82707(current diff)902f694a7b0e(diff) |
children | b07b1e58582d |
files | Lib/inspect.py Lib/pydoc.py Lib/test/test_inspect.py Misc/NEWS |
diffstat | 4 files changed, 151 insertions(+), 38 deletions(-)[+] [-] Lib/inspect.py 79 Lib/pydoc.py 29 Lib/test/test_inspect.py 79 Misc/NEWS 2 |
line wrap: on
line diff
--- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -99,11 +99,11 @@ def ismethoddescriptor(object): tests return false from the ismethoddescriptor() test, simply because the other tests promise more -- you can, e.g., count on having the func attribute (etc) when an object passes ismethod()."""
- return (hasattr(object, "get")
and not hasattr(object, "__set__") # else it's a data descriptor[](#l1.8)
and not ismethod(object) # mutual exclusion[](#l1.9)
and not isfunction(object)[](#l1.10)
and not isclass(object))[](#l1.11)
- if isclass(object) or ismethod(object) or isfunction(object):
# mutual exclusion[](#l1.13)
return False[](#l1.14)
- tp = type(object)
- return hasattr(tp, "get") and not hasattr(tp, "set")
def isdatadescriptor(object): """Return true if the object is a data descriptor. @@ -113,7 +113,11 @@ def isdatadescriptor(object): Typically, data descriptors will also have name and doc attributes (properties, getsets, and members have both of these attributes), but this is not guaranteed."""
- if isclass(object) or ismethod(object) or isfunction(object):
# mutual exclusion[](#l1.26)
return False[](#l1.27)
- tp = type(object)
- return hasattr(tp, "set") and hasattr(tp, "get")
if hasattr(types, 'MemberDescriptorType'): # CPython and equivalent @@ -253,12 +257,23 @@ def isabstract(object): def getmembers(object, predicate=None): """Return all members of an object as (name, value) pairs sorted by name. Optionally, only return members that satisfy a given predicate."""
- if isclass(object):
mro = (object,) + getmro(object)[](#l1.38)
- else:
results = [] for key in dir(object):mro = ()[](#l1.40)
try:[](#l1.43)
value = getattr(object, key)[](#l1.44)
except AttributeError:[](#l1.45)
continue[](#l1.46)
# First try to get the value via __dict__. Some descriptors don't[](#l1.47)
# like calling their __get__ (see bug #1785).[](#l1.48)
for base in mro:[](#l1.49)
if key in base.__dict__:[](#l1.50)
value = base.__dict__[key][](#l1.51)
break[](#l1.52)
else:[](#l1.53)
try:[](#l1.54)
value = getattr(object, key)[](#l1.55)
except AttributeError:[](#l1.56)
results.sort() @@ -294,30 +309,21 @@ def classify_class_attrs(cls): names = dir(cls) result = [] for name in names:continue[](#l1.57) if not predicate or predicate(value):[](#l1.58) results.append((key, value))[](#l1.59)
# Get the object associated with the name.[](#l1.65)
# Get the object associated with the name, and where it was defined.[](#l1.66) # Getting an obj from the __dict__ sometimes reveals more than[](#l1.67) # using getattr. Static and class methods are dramatic examples.[](#l1.68)
if name in cls.__dict__:[](#l1.69)
obj = cls.__dict__[name][](#l1.70)
# Furthermore, some objects may raise an Exception when fetched with[](#l1.71)
# getattr(). This is the case with some descriptors (bug #1785).[](#l1.72)
# Thus, we only use getattr() as a last resort.[](#l1.73)
homecls = None[](#l1.74)
for base in (cls,) + mro:[](#l1.75)
if name in base.__dict__:[](#l1.76)
obj = base.__dict__[name][](#l1.77)
homecls = base[](#l1.78)
break[](#l1.79) else:[](#l1.80) obj = getattr(cls, name)[](#l1.81)
# Figure out where it was defined.[](#l1.83)
homecls = getattr(obj, "__objclass__", None)[](#l1.84)
if homecls is None:[](#l1.85)
# search the dicts.[](#l1.86)
for base in mro:[](#l1.87)
if name in base.__dict__:[](#l1.88)
homecls = base[](#l1.89)
break[](#l1.90)
# Get the object again, in order to get it from the defining[](#l1.92)
# __dict__ instead of via getattr (if possible).[](#l1.93)
if homecls is not None and name in homecls.__dict__:[](#l1.94)
obj = homecls.__dict__[name][](#l1.95)
# Also get the object via getattr.[](#l1.97)
obj_via_getattr = getattr(cls, name)[](#l1.98)
homecls = getattr(obj, "__objclass__", homecls)[](#l1.99)
# Classify the object. if isinstance(obj, staticmethod): @@ -326,11 +332,18 @@ def classify_class_attrs(cls): kind = "class method" elif isinstance(obj, property): kind = "property"
elif (isfunction(obj_via_getattr) or[](#l1.107)
ismethoddescriptor(obj_via_getattr)):[](#l1.108)
elif ismethoddescriptor(obj):[](#l1.109) kind = "method"[](#l1.110)
elif isdatadescriptor(obj):[](#l1.111)
kind = "data"[](#l1.112) else:[](#l1.113)
kind = "data"[](#l1.114)
obj_via_getattr = getattr(cls, name)[](#l1.115)
if (isfunction(obj_via_getattr) or[](#l1.116)
ismethoddescriptor(obj_via_getattr)):[](#l1.117)
kind = "method"[](#l1.118)
else:[](#l1.119)
kind = "data"[](#l1.120)
obj = obj_via_getattr[](#l1.121)
result.append(Attribute(name, kind, homecls, obj))
--- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -748,8 +748,15 @@ class HTMLDoc(Doc): hr.maybe() push(msg) for name, kind, homecls, value in ok:
push(self.document(getattr(object, name), name, mod,[](#l2.7)
funcs, classes, mdict, object))[](#l2.8)
try:[](#l2.9)
value = getattr(object, name)[](#l2.10)
except Exception:[](#l2.11)
# Some descriptors may meet a failure in their __get__.[](#l2.12)
# (bug #1785)[](#l2.13)
push(self._docdescriptor(name, value, mod))[](#l2.14)
else:[](#l2.15)
push(self.document(value, name, mod,[](#l2.16)
funcs, classes, mdict, object))[](#l2.17) push('\n')[](#l2.18) return attrs[](#l2.19)
@@ -790,7 +797,12 @@ class HTMLDoc(Doc): mdict = {} for key, kind, homecls, value in attrs: mdict[key] = anchor = '#' + name + '-' + key
value = getattr(object, key)[](#l2.25)
try:[](#l2.26)
value = getattr(object, name)[](#l2.27)
except Exception:[](#l2.28)
# Some descriptors may meet a failure in their __get__.[](#l2.29)
# (bug #1785)[](#l2.30)
pass[](#l2.31) try:[](#l2.32) # The value may not be hashable (e.g., a data attr with[](#l2.33) # a dict or list value).[](#l2.34)
@@ -1177,8 +1189,15 @@ location listed above. hr.maybe() push(msg) for name, kind, homecls, value in ok:
push(self.document(getattr(object, name),[](#l2.39)
name, mod, object))[](#l2.40)
try:[](#l2.41)
value = getattr(object, name)[](#l2.42)
except Exception:[](#l2.43)
# Some descriptors may meet a failure in their __get__.[](#l2.44)
# (bug #1785)[](#l2.45)
push(self._docdescriptor(name, value, mod))[](#l2.46)
else:[](#l2.47)
push(self.document(value,[](#l2.48)
name, mod, object))[](#l2.49) return attrs[](#l2.50)
def spilldescriptors(msg, attrs, predicate):
--- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -425,10 +425,37 @@ class TestNoEOL(GetSourceBase): def test_class(self): self.assertSourceEqual(self.fodderModule.X, 1, 2) + +class _BrokenDataDescriptor(object):
- """
- A broken data descriptor. See bug #1785.
- """
- def get(*args):
raise AssertionError("should not __get__ data descriptors")[](#l3.13)
+ + +class _BrokenMethodDescriptor(object):
- """
- A broken method descriptor. See bug #1785.
- """
- def get(*args):
raise AssertionError("should not __get__ method descriptors")[](#l3.27)
Helper for testing classify_class_attrs.
def attrs_wo_objs(cls): return [t[:3] for t in inspect.classify_class_attrs(cls)] + class TestClassesAndFunctions(unittest.TestCase): def test_newstyle_mro(self): # The same w/ new-class MRO. @@ -525,6 +552,9 @@ class TestClassesAndFunctions(unittest.T datablob = '1'
dd = _BrokenDataDescriptor()[](#l3.45)
md = _BrokenMethodDescriptor()[](#l3.46)
+ attrs = attrs_wo_objs(A) self.assertIn(('s', 'static method', A), attrs, 'missing static method') self.assertIn(('c', 'class method', A), attrs, 'missing class method') @@ -533,6 +563,8 @@ class TestClassesAndFunctions(unittest.T 'missing plain method: %r' % attrs) self.assertIn(('m1', 'method', A), attrs, 'missing plain method') self.assertIn(('datablob', 'data', A), attrs, 'missing data')
self.assertIn(('md', 'method', A), attrs, 'missing method descriptor')[](#l3.55)
self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor')[](#l3.56)
class B(A): @@ -545,6 +577,8 @@ class TestClassesAndFunctions(unittest.T self.assertIn(('m', 'method', B), attrs, 'missing plain method') self.assertIn(('m1', 'method', A), attrs, 'missing plain method') self.assertIn(('datablob', 'data', A), attrs, 'missing data')
self.assertIn(('md', 'method', A), attrs, 'missing method descriptor')[](#l3.64)
self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor')[](#l3.65)
class C(A): @@ -559,6 +593,8 @@ class TestClassesAndFunctions(unittest.T self.assertIn(('m', 'method', C), attrs, 'missing plain method') self.assertIn(('m1', 'method', A), attrs, 'missing plain method') self.assertIn(('datablob', 'data', A), attrs, 'missing data')
self.assertIn(('md', 'method', A), attrs, 'missing method descriptor')[](#l3.73)
self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor')[](#l3.74)
class D(B, C): @@ -571,6 +607,49 @@ class TestClassesAndFunctions(unittest.T self.assertIn(('m', 'method', B), attrs, 'missing plain method') self.assertIn(('m1', 'method', D), attrs, 'missing plain method') self.assertIn(('datablob', 'data', A), attrs, 'missing data')
self.assertIn(('md', 'method', A), attrs, 'missing method descriptor')[](#l3.82)
self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor')[](#l3.83)
- def test_classify_builtin_types(self):
# Simple sanity check that all built-in types can have their[](#l3.86)
# attributes classified.[](#l3.87)
for name in dir(__builtins__):[](#l3.88)
builtin = getattr(__builtins__, name)[](#l3.89)
if isinstance(builtin, type):[](#l3.90)
inspect.classify_class_attrs(builtin)[](#l3.91)
- def test_getmembers_descriptors(self):
class A(object):[](#l3.94)
dd = _BrokenDataDescriptor()[](#l3.95)
md = _BrokenMethodDescriptor()[](#l3.96)
def pred_wrapper(pred):[](#l3.98)
# A quick'n'dirty way to discard standard attributes of new-style[](#l3.99)
# classes.[](#l3.100)
class Empty(object):[](#l3.101)
pass[](#l3.102)
def wrapped(x):[](#l3.103)
if '__name__' in dir(x) and hasattr(Empty, x.__name__):[](#l3.104)
return False[](#l3.105)
return pred(x)[](#l3.106)
return wrapped[](#l3.107)
ismethoddescriptor = pred_wrapper(inspect.ismethoddescriptor)[](#l3.109)
isdatadescriptor = pred_wrapper(inspect.isdatadescriptor)[](#l3.110)
self.assertEqual(inspect.getmembers(A, ismethoddescriptor),[](#l3.112)
[('md', A.__dict__['md'])])[](#l3.113)
self.assertEqual(inspect.getmembers(A, isdatadescriptor),[](#l3.114)
[('dd', A.__dict__['dd'])])[](#l3.115)
class B(A):[](#l3.117)
pass[](#l3.118)
self.assertEqual(inspect.getmembers(B, ismethoddescriptor),[](#l3.120)
[('md', A.__dict__['md'])])[](#l3.121)
self.assertEqual(inspect.getmembers(B, isdatadescriptor),[](#l3.122)
[('dd', A.__dict__['dd'])])[](#l3.123)
+ class TestGetcallargsFunctions(unittest.TestCase):
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -419,6 +419,8 @@ Core and Builtins Library ------- +- Issue #1785: Fix inspect and pydoc with misbehaving descriptors. +