@@ -267,11 +267,25 @@ def getmembers(object, predicate=None):
267267 else :
268268 mro = ()
269269 results = []
270- for key in dir (object ):
270+ processed = set ()
271+ names = dir (object )
272+ # add any virtual attributes to the list of names if object is a class
273+ # this may result in duplicate entries if, for example, a virtual
274+ # attribute with the same name as a member property exists
275+ try :
276+ for base in object .__bases__ :
277+ for k , v in base .__dict__ .items ():
278+ if isinstance (v , types .DynamicClassAttribute ):
279+ names .append (k )
280+ except AttributeError :
281+ pass
282+ for key in names :
271283 # First try to get the value via __dict__. Some descriptors don't
272284 # like calling their __get__ (see bug #1785).
273285 for base in mro :
274- if key in base .__dict__ :
286+ if key in base .__dict__ and key not in processed :
287+ # handle the normal case first; if duplicate entries exist
288+ # they will be handled second
275289 value = base .__dict__ [key ]
276290 break
277291 else :
@@ -281,7 +295,8 @@ def getmembers(object, predicate=None):
281295 continue
282296 if not predicate or predicate (value ):
283297 results .append ((key , value ))
284- results .sort ()
298+ processed .add (key )
299+ results .sort (key = lambda pair : pair [0 ])
285300 return results
286301
287302Attribute = namedtuple ('Attribute' , 'name kind defining_class object' )
@@ -298,16 +313,15 @@ def classify_class_attrs(cls):
298313 'class method' created via classmethod()
299314 'static method' created via staticmethod()
300315 'property' created via property()
301- 'method' any other flavor of method
316+ 'method' any other flavor of method or descriptor
302317 'data' not a method
303318
304319 2. The class which defined this attribute (a class).
305320
306- 3. The object as obtained directly from the defining class's
307- __dict__, not via getattr. This is especially important for
308- data attributes: C.data is just a data object, but
309- C.__dict__['data'] may be a data descriptor with additional
310- info, like a __doc__ string.
321+ 3. The object as obtained by calling getattr; if this fails, or if the
322+ resulting object does not live anywhere in the class' mro (including
323+ metaclasses) then the object is looked up in the defining class's
324+ dict (found by walking the mro).
311325
312326 If one of the items in dir(cls) is stored in the metaclass it will now
313327 be discovered and not have None be listed as the class in which it was
@@ -316,46 +330,72 @@ def classify_class_attrs(cls):
316330
317331 mro = getmro (cls )
318332 metamro = getmro (type (cls )) # for attributes stored in the metaclass
333+ metamro = tuple ([cls for cls in metamro if cls not in (type , object )])
334+ possible_bases = (cls ,) + mro + metamro
319335 names = dir (cls )
336+ # add any virtual attributes to the list of names
337+ # this may result in duplicate entries if, for example, a virtual
338+ # attribute with the same name as a member property exists
339+ for base in cls .__bases__ :
340+ for k , v in base .__dict__ .items ():
341+ if isinstance (v , types .DynamicClassAttribute ):
342+ names .append (k )
320343 result = []
344+ processed = set ()
345+ sentinel = object ()
321346 for name in names :
322347 # Get the object associated with the name, and where it was defined.
348+ # Normal objects will be looked up with both getattr and directly in
349+ # its class' dict (in case getattr fails [bug #1785], and also to look
350+ # for a docstring).
351+ # For VirtualAttributes on the second pass we only look in the
352+ # class's dict.
353+ #
323354 # Getting an obj from the __dict__ sometimes reveals more than
324355 # using getattr. Static and class methods are dramatic examples.
325- # Furthermore, some objects may raise an Exception when fetched with
326- # getattr(). This is the case with some descriptors (bug #1785).
327- # Thus, we only use getattr() as a last resort.
328356 homecls = None
329- for base in (cls ,) + mro + metamro :
357+ get_obj = sentinel
358+ dict_obj = sentinel
359+
360+
361+ if name not in processed :
362+ try :
363+ get_obj = getattr (cls , name )
364+ except Exception as exc :
365+ pass
366+ else :
367+ homecls = getattr (get_obj , "__class__" )
368+ homecls = getattr (get_obj , "__objclass__" , homecls )
369+ if homecls not in possible_bases :
370+ # if the resulting object does not live somewhere in the
371+ # mro, drop it and go with the dict_obj version only
372+ homecls = None
373+ get_obj = sentinel
374+
375+ for base in possible_bases :
330376 if name in base .__dict__ :
331- obj = base .__dict__ [name ]
332- homecls = base
377+ dict_obj = base .__dict__ [name ]
378+ homecls = homecls or base
333379 break
334- else :
335- obj = getattr (cls , name )
336- homecls = getattr (obj , "__objclass__" , homecls )
337380
338- # Classify the object.
381+ # Classify the object or its descriptor.
382+ if get_obj is not sentinel :
383+ obj = get_obj
384+ else :
385+ obj = dict_obj
339386 if isinstance (obj , staticmethod ):
340387 kind = "static method"
341388 elif isinstance (obj , classmethod ):
342389 kind = "class method"
343390 elif isinstance (obj , property ):
344391 kind = "property"
345- elif ismethoddescriptor (obj ):
392+ elif isfunction ( obj ) or ismethoddescriptor (obj ):
346393 kind = "method"
347- elif isdatadescriptor (obj ):
348- kind = "data"
349394 else :
350- obj_via_getattr = getattr (cls , name )
351- if (isfunction (obj_via_getattr ) or
352- ismethoddescriptor (obj_via_getattr )):
353- kind = "method"
354- else :
355- kind = "data"
356- obj = obj_via_getattr
395+ kind = "data"
357396
358397 result .append (Attribute (name , kind , homecls , obj ))
398+ processed .add (name )
359399
360400 return result
361401
0 commit comments