Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 13b49d3

Browse files
committed
New function classify_class_attrs(). As a number of SF bug reports
point out, pydoc doesn't tell you where class attributes were defined, gets several new 2.2 features wrong, and isn't aware of some new features checked in on Thursday <wink>. pydoc is hampered in part because inspect.py has the same limitations. Alas, I can't think of a way to fix this within the current architecture of inspect/pydoc: it's simply not possible in 2.2 to figure out everything needed just from examining the object you get back from class.attr. You also need the class context, and the method resolution order, and tests against various things that simply didn't exist before. OTOH, knowledge of how to do that is getting quite complex, so doesn't belong in pydoc. classify_class_attrs takes a different approach, analyzing all the class attrs "at once", and returning the most interesting stuff for each, all in one gulp. pydoc needs to be reworked to use this for classes (instead of the current "filter dir(class) umpteen times against assorted predicates" approach).
1 parent 42b6877 commit 13b49d3

2 files changed

Lines changed: 286 additions & 0 deletions

File tree

Lib/inspect.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,91 @@ def getmembers(object, predicate=None):
163163
results.sort()
164164
return results
165165

166+
def classify_class_attrs(cls):
167+
"""Return list of attribute-descriptor tuples.
168+
169+
For each name in dir(cls), the return list contains a 4-tuple
170+
with these elements:
171+
172+
0. The name (a string).
173+
174+
1. The kind of attribute this is, one of these strings:
175+
'class method' created via classmethod()
176+
'static method' created via staticmethod()
177+
'property' created via property()
178+
'method' any other flavor of method
179+
'data' not a method
180+
181+
2. The class which defined this attribute (a class).
182+
183+
3. The object as obtained directly from the defining class's
184+
__dict__, not via getattr. This is especially important for
185+
data attributes: C.data is just a data object, but
186+
C.__dict__['data'] may be a data descriptor with additional
187+
info, like a __doc__ string.
188+
"""
189+
190+
mro = getmro(cls)
191+
names = dir(cls)
192+
result = []
193+
for name in names:
194+
# Get the object associated with the name.
195+
# Getting an obj from the __dict__ sometimes reveals more than
196+
# using getattr. Static and class methods are dramatic examples.
197+
if name in cls.__dict__:
198+
obj = cls.__dict__[name]
199+
else:
200+
obj = getattr(cls, name)
201+
202+
# Figure out where it was defined.
203+
# A complication: static classes in 2.2 copy dict entries from
204+
# bases into derived classes, so it's not enough just to look for
205+
# "the first" class with the name in its dict. OTOH:
206+
# 1. Some-- but not all --methods in 2.2 come with an __objclass__
207+
# attr that answers the question directly.
208+
# 2. Some-- but not all --classes in 2.2 have a __defined__ dict
209+
# saying which names were defined by the class.
210+
homecls = getattr(obj, "__objclass__", None)
211+
if homecls is None:
212+
# Try __defined__.
213+
for base in mro:
214+
if hasattr(base, "__defined__"):
215+
if name in base.__defined__:
216+
homecls = base
217+
break
218+
if homecls is None:
219+
# Last chance (and first chance for classic classes): search
220+
# the dicts.
221+
for base in mro:
222+
if name in base.__dict__:
223+
homecls = base
224+
break
225+
226+
# Get the object again, in order to get it from the defining
227+
# __dict__ instead of via getattr (if possible).
228+
if homecls is not None and name in homecls.__dict__:
229+
obj = homecls.__dict__[name]
230+
231+
# Also get the object via getattr.
232+
obj_via_getattr = getattr(cls, name)
233+
234+
# Classify the object.
235+
if isinstance(obj, staticmethod):
236+
kind = "static method"
237+
elif isinstance(obj, classmethod):
238+
kind = "class method"
239+
elif isinstance(obj, property):
240+
kind = "property"
241+
elif (ismethod(obj_via_getattr) or
242+
ismethoddescriptor(obj_via_getattr)):
243+
kind = "method"
244+
else:
245+
kind = "data"
246+
247+
result.append((name, kind, homecls, obj))
248+
249+
return result
250+
166251
# ----------------------------------------------------------- class helpers
167252
def _searchbases(cls, accum):
168253
# Simulate the "classic class" search order.

Lib/test/test_inspect.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,204 @@ class D(B, C): pass
233233
expected = (D, B, C, A, object)
234234
got = inspect.getmro(D)
235235
test(expected == got, "expected %r mro, got %r", expected, got)
236+
237+
# Test classify_class_attrs.
238+
def attrs_wo_objs(cls):
239+
return [t[:3] for t in inspect.classify_class_attrs(cls)]
240+
241+
class A:
242+
def s(): pass
243+
s = staticmethod(s)
244+
245+
def c(cls): pass
246+
c = classmethod(c)
247+
248+
def getp(self): pass
249+
p = property(getp)
250+
251+
def m(self): pass
252+
253+
def m1(self): pass
254+
255+
datablob = '1'
256+
257+
attrs = attrs_wo_objs(A)
258+
test(('s', 'static method', A) in attrs, 'missing static method')
259+
test(('c', 'class method', A) in attrs, 'missing class method')
260+
test(('p', 'property', A) in attrs, 'missing property')
261+
test(('m', 'method', A) in attrs, 'missing plain method')
262+
test(('m1', 'method', A) in attrs, 'missing plain method')
263+
test(('datablob', 'data', A) in attrs, 'missing data')
264+
265+
class B(A):
266+
def m(self): pass
267+
268+
attrs = attrs_wo_objs(B)
269+
test(('s', 'static method', A) in attrs, 'missing static method')
270+
test(('c', 'class method', A) in attrs, 'missing class method')
271+
test(('p', 'property', A) in attrs, 'missing property')
272+
test(('m', 'method', B) in attrs, 'missing plain method')
273+
test(('m1', 'method', A) in attrs, 'missing plain method')
274+
test(('datablob', 'data', A) in attrs, 'missing data')
275+
276+
277+
class C(A):
278+
def m(self): pass
279+
def c(self): pass
280+
281+
attrs = attrs_wo_objs(C)
282+
test(('s', 'static method', A) in attrs, 'missing static method')
283+
test(('c', 'method', C) in attrs, 'missing plain method')
284+
test(('p', 'property', A) in attrs, 'missing property')
285+
test(('m', 'method', C) in attrs, 'missing plain method')
286+
test(('m1', 'method', A) in attrs, 'missing plain method')
287+
test(('datablob', 'data', A) in attrs, 'missing data')
288+
289+
class D(B, C):
290+
def m1(self): pass
291+
292+
attrs = attrs_wo_objs(D)
293+
test(('s', 'static method', A) in attrs, 'missing static method')
294+
test(('c', 'class method', A) in attrs, 'missing class method')
295+
test(('p', 'property', A) in attrs, 'missing property')
296+
test(('m', 'method', B) in attrs, 'missing plain method')
297+
test(('m1', 'method', D) in attrs, 'missing plain method')
298+
test(('datablob', 'data', A) in attrs, 'missing data')
299+
300+
# Repeat all that, but w/ new-style non-dynamic classes.
301+
302+
class A(object):
303+
__dynamic__ = 0
304+
305+
def s(): pass
306+
s = staticmethod(s)
307+
308+
def c(cls): pass
309+
c = classmethod(c)
310+
311+
def getp(self): pass
312+
p = property(getp)
313+
314+
def m(self): pass
315+
316+
def m1(self): pass
317+
318+
datablob = '1'
319+
320+
attrs = attrs_wo_objs(A)
321+
test(('s', 'static method', A) in attrs, 'missing static method')
322+
test(('c', 'class method', A) in attrs, 'missing class method')
323+
test(('p', 'property', A) in attrs, 'missing property')
324+
test(('m', 'method', A) in attrs, 'missing plain method')
325+
test(('m1', 'method', A) in attrs, 'missing plain method')
326+
test(('datablob', 'data', A) in attrs, 'missing data')
327+
328+
class B(A):
329+
__dynamic__ = 0
330+
331+
def m(self): pass
332+
333+
attrs = attrs_wo_objs(B)
334+
test(('s', 'static method', A) in attrs, 'missing static method')
335+
test(('c', 'class method', A) in attrs, 'missing class method')
336+
test(('p', 'property', A) in attrs, 'missing property')
337+
test(('m', 'method', B) in attrs, 'missing plain method')
338+
test(('m1', 'method', A) in attrs, 'missing plain method')
339+
test(('datablob', 'data', A) in attrs, 'missing data')
340+
341+
342+
class C(A):
343+
__dynamic__ = 0
344+
345+
def m(self): pass
346+
def c(self): pass
347+
348+
attrs = attrs_wo_objs(C)
349+
test(('s', 'static method', A) in attrs, 'missing static method')
350+
test(('c', 'method', C) in attrs, 'missing plain method')
351+
test(('p', 'property', A) in attrs, 'missing property')
352+
test(('m', 'method', C) in attrs, 'missing plain method')
353+
test(('m1', 'method', A) in attrs, 'missing plain method')
354+
test(('datablob', 'data', A) in attrs, 'missing data')
355+
356+
class D(B, C):
357+
__dynamic__ = 0
358+
359+
def m1(self): pass
360+
361+
attrs = attrs_wo_objs(D)
362+
test(('s', 'static method', A) in attrs, 'missing static method')
363+
test(('c', 'method', C) in attrs, 'missing plain method')
364+
test(('p', 'property', A) in attrs, 'missing property')
365+
test(('m', 'method', B) in attrs, 'missing plain method')
366+
test(('m1', 'method', D) in attrs, 'missing plain method')
367+
test(('datablob', 'data', A) in attrs, 'missing data')
368+
369+
# And again, but w/ new-style dynamic classes.
370+
371+
class A(object):
372+
__dynamic__ = 1
373+
374+
def s(): pass
375+
s = staticmethod(s)
376+
377+
def c(cls): pass
378+
c = classmethod(c)
379+
380+
def getp(self): pass
381+
p = property(getp)
382+
383+
def m(self): pass
384+
385+
def m1(self): pass
386+
387+
datablob = '1'
388+
389+
attrs = attrs_wo_objs(A)
390+
test(('s', 'static method', A) in attrs, 'missing static method')
391+
test(('c', 'class method', A) in attrs, 'missing class method')
392+
test(('p', 'property', A) in attrs, 'missing property')
393+
test(('m', 'method', A) in attrs, 'missing plain method')
394+
test(('m1', 'method', A) in attrs, 'missing plain method')
395+
test(('datablob', 'data', A) in attrs, 'missing data')
396+
397+
class B(A):
398+
__dynamic__ = 1
399+
400+
def m(self): pass
401+
402+
attrs = attrs_wo_objs(B)
403+
test(('s', 'static method', A) in attrs, 'missing static method')
404+
test(('c', 'class method', A) in attrs, 'missing class method')
405+
test(('p', 'property', A) in attrs, 'missing property')
406+
test(('m', 'method', B) in attrs, 'missing plain method')
407+
test(('m1', 'method', A) in attrs, 'missing plain method')
408+
test(('datablob', 'data', A) in attrs, 'missing data')
409+
410+
411+
class C(A):
412+
__dynamic__ = 1
413+
414+
def m(self): pass
415+
def c(self): pass
416+
417+
attrs = attrs_wo_objs(C)
418+
test(('s', 'static method', A) in attrs, 'missing static method')
419+
test(('c', 'method', C) in attrs, 'missing plain method')
420+
test(('p', 'property', A) in attrs, 'missing property')
421+
test(('m', 'method', C) in attrs, 'missing plain method')
422+
test(('m1', 'method', A) in attrs, 'missing plain method')
423+
test(('datablob', 'data', A) in attrs, 'missing data')
424+
425+
class D(B, C):
426+
__dynamic__ = 1
427+
428+
def m1(self): pass
429+
430+
attrs = attrs_wo_objs(D)
431+
test(('s', 'static method', A) in attrs, 'missing static method')
432+
test(('c', 'method', C) in attrs, 'missing plain method')
433+
test(('p', 'property', A) in attrs, 'missing property')
434+
test(('m', 'method', B) in attrs, 'missing plain method')
435+
test(('m1', 'method', D) in attrs, 'missing plain method')
436+
test(('datablob', 'data', A) in attrs, 'missing data')

0 commit comments

Comments
 (0)