summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/inspect.py85
-rw-r--r--Lib/test/test_inspect.py201
2 files changed, 286 insertions, 0 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 3febf18..c5c6709 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -163,6 +163,91 @@ def getmembers(object, predicate=None):
results.sort()
return results
+def classify_class_attrs(cls):
+ """Return list of attribute-descriptor tuples.
+
+ For each name in dir(cls), the return list contains a 4-tuple
+ with these elements:
+
+ 0. The name (a string).
+
+ 1. The kind of attribute this is, one of these strings:
+ 'class method' created via classmethod()
+ 'static method' created via staticmethod()
+ 'property' created via property()
+ 'method' any other flavor of method
+ 'data' not a method
+
+ 2. The class which defined this attribute (a class).
+
+ 3. The object as obtained directly from the defining class's
+ __dict__, not via getattr. This is especially important for
+ data attributes: C.data is just a data object, but
+ C.__dict__['data'] may be a data descriptor with additional
+ info, like a __doc__ string.
+ """
+
+ mro = getmro(cls)
+ names = dir(cls)
+ result = []
+ for name in names:
+ # Get the object associated with the name.
+ # Getting an obj from the __dict__ sometimes reveals more than
+ # using getattr. Static and class methods are dramatic examples.
+ if name in cls.__dict__:
+ obj = cls.__dict__[name]
+ else:
+ obj = getattr(cls, name)
+
+ # Figure out where it was defined.
+ # A complication: static classes in 2.2 copy dict entries from
+ # bases into derived classes, so it's not enough just to look for
+ # "the first" class with the name in its dict. OTOH:
+ # 1. Some-- but not all --methods in 2.2 come with an __objclass__
+ # attr that answers the question directly.
+ # 2. Some-- but not all --classes in 2.2 have a __defined__ dict
+ # saying which names were defined by the class.
+ homecls = getattr(obj, "__objclass__", None)
+ if homecls is None:
+ # Try __defined__.
+ for base in mro:
+ if hasattr(base, "__defined__"):
+ if name in base.__defined__:
+ homecls = base
+ break
+ if homecls is None:
+ # Last chance (and first chance for classic classes): search
+ # the dicts.
+ for base in mro:
+ if name in base.__dict__:
+ homecls = base
+ break
+
+ # Get the object again, in order to get it from the defining
+ # __dict__ instead of via getattr (if possible).
+ if homecls is not None and name in homecls.__dict__:
+ obj = homecls.__dict__[name]
+
+ # Also get the object via getattr.
+ obj_via_getattr = getattr(cls, name)
+
+ # Classify the object.
+ if isinstance(obj, staticmethod):
+ kind = "static method"
+ elif isinstance(obj, classmethod):
+ kind = "class method"
+ elif isinstance(obj, property):
+ kind = "property"
+ elif (ismethod(obj_via_getattr) or
+ ismethoddescriptor(obj_via_getattr)):
+ kind = "method"
+ else:
+ kind = "data"
+
+ result.append((name, kind, homecls, obj))
+
+ return result
+
# ----------------------------------------------------------- class helpers
def _searchbases(cls, accum):
# Simulate the "classic class" search order.
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index dbb6609..9167b14 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -233,3 +233,204 @@ class D(B, C): pass
expected = (D, B, C, A, object)
got = inspect.getmro(D)
test(expected == got, "expected %r mro, got %r", expected, got)
+
+# Test classify_class_attrs.
+def attrs_wo_objs(cls):
+ return [t[:3] for t in inspect.classify_class_attrs(cls)]
+
+class A:
+ def s(): pass
+ s = staticmethod(s)
+
+ def c(cls): pass
+ c = classmethod(c)
+
+ def getp(self): pass
+ p = property(getp)
+
+ def m(self): pass
+
+ def m1(self): pass
+
+ datablob = '1'
+
+attrs = attrs_wo_objs(A)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'class method', A) in attrs, 'missing class method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', A) in attrs, 'missing plain method')
+test(('m1', 'method', A) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')
+
+class B(A):
+ def m(self): pass
+
+attrs = attrs_wo_objs(B)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'class method', A) in attrs, 'missing class method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', B) in attrs, 'missing plain method')
+test(('m1', 'method', A) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')
+
+
+class C(A):
+ def m(self): pass
+ def c(self): pass
+
+attrs = attrs_wo_objs(C)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'method', C) in attrs, 'missing plain method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', C) in attrs, 'missing plain method')
+test(('m1', 'method', A) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')
+
+class D(B, C):
+ def m1(self): pass
+
+attrs = attrs_wo_objs(D)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'class method', A) in attrs, 'missing class method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', B) in attrs, 'missing plain method')
+test(('m1', 'method', D) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')
+
+# Repeat all that, but w/ new-style non-dynamic classes.
+
+class A(object):
+ __dynamic__ = 0
+
+ def s(): pass
+ s = staticmethod(s)
+
+ def c(cls): pass
+ c = classmethod(c)
+
+ def getp(self): pass
+ p = property(getp)
+
+ def m(self): pass
+
+ def m1(self): pass
+
+ datablob = '1'
+
+attrs = attrs_wo_objs(A)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'class method', A) in attrs, 'missing class method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', A) in attrs, 'missing plain method')
+test(('m1', 'method', A) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')
+
+class B(A):
+ __dynamic__ = 0
+
+ def m(self): pass
+
+attrs = attrs_wo_objs(B)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'class method', A) in attrs, 'missing class method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', B) in attrs, 'missing plain method')
+test(('m1', 'method', A) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')
+
+
+class C(A):
+ __dynamic__ = 0
+
+ def m(self): pass
+ def c(self): pass
+
+attrs = attrs_wo_objs(C)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'method', C) in attrs, 'missing plain method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', C) in attrs, 'missing plain method')
+test(('m1', 'method', A) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')
+
+class D(B, C):
+ __dynamic__ = 0
+
+ def m1(self): pass
+
+attrs = attrs_wo_objs(D)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'method', C) in attrs, 'missing plain method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', B) in attrs, 'missing plain method')
+test(('m1', 'method', D) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')
+
+# And again, but w/ new-style dynamic classes.
+
+class A(object):
+ __dynamic__ = 1
+
+ def s(): pass
+ s = staticmethod(s)
+
+ def c(cls): pass
+ c = classmethod(c)
+
+ def getp(self): pass
+ p = property(getp)
+
+ def m(self): pass
+
+ def m1(self): pass
+
+ datablob = '1'
+
+attrs = attrs_wo_objs(A)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'class method', A) in attrs, 'missing class method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', A) in attrs, 'missing plain method')
+test(('m1', 'method', A) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')
+
+class B(A):
+ __dynamic__ = 1
+
+ def m(self): pass
+
+attrs = attrs_wo_objs(B)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'class method', A) in attrs, 'missing class method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', B) in attrs, 'missing plain method')
+test(('m1', 'method', A) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')
+
+
+class C(A):
+ __dynamic__ = 1
+
+ def m(self): pass
+ def c(self): pass
+
+attrs = attrs_wo_objs(C)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'method', C) in attrs, 'missing plain method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', C) in attrs, 'missing plain method')
+test(('m1', 'method', A) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')
+
+class D(B, C):
+ __dynamic__ = 1
+
+ def m1(self): pass
+
+attrs = attrs_wo_objs(D)
+test(('s', 'static method', A) in attrs, 'missing static method')
+test(('c', 'method', C) in attrs, 'missing plain method')
+test(('p', 'property', A) in attrs, 'missing property')
+test(('m', 'method', B) in attrs, 'missing plain method')
+test(('m1', 'method', D) in attrs, 'missing plain method')
+test(('datablob', 'data', A) in attrs, 'missing data')