5.3 Getting All Members of a Class Hierarchy


Credit: Jürgen Hermann, Alex Martelli

5.3.1 Problem

You need to map all members of a class, including inherited members, into a dictionary of class attribute names.

5.3.2 Solution

Here is a solution that works portably and transparently on both new-style (Python 2.2) and classic classes with any Python version:

def all_members(aClass):     try:         # Try getting all relevant classes in method-resolution order         mro = list(aClass._ _mro_ _)     except AttributeError:         # If a class has no _ _mro_ _, then it's a classic class         def getmro(aClass, recurse):             mro = [aClass]             for base in aClass._ _bases_ _: mro.extend(recurse(base, recurse))             return mro         mro = getmro(aClass, getmro)     mro.reverse(  )     members = {}     for someClass in mro: members.update(vars(someClass))     return members

5.3.3 Discussion

The all_members function in this recipe creates a dictionary that includes each member (such as methods and data attributes) of a class with the name as the key and the class attribute value as the corresponding value. Here's a usage example:

class Eggs:     eggs = 'eggs'     spam = None class Spam:     spam = 'spam' class Breakfast(Spam, Eggs):     eggs = 'scrambled' print all_members(Eggs) print all_members(Spam) print all_members(Breakfast)

And here's the output of this example (note that the order in which each dictionary's items are printed is arbitrary and may vary between Python interpreters):

{'spam': None, '_ _doc_ _': None, 'eggs': 'eggs', '_ _module_ _': '_ _main_ _'} {'spam': 'spam', '_ _doc_ _': None, '_ _module_ _': '_ _main_ _'} {'_ _doc_ _': None, 'eggs': 'scrambled', 'spam': 'spam', '_ _module_ _': '_ _main_ _'}

After constructing the dictionary d with d=all_members(c), you can use d for repeated introspection about class c. d.has_key(x) is the same as hasattr(c,x), and d.get(x) is the same as getattr(c,x,None), but it doesn't repeat the dynamic search procedure each time. Apart from the order of its items, d.keys is like dir(c) if c is a new-style class (for which dir also returns the names of inherited attributes) but is richer and potentially more useful than dir(c) if c is a classic class (for which dir does not list inherited attributes, only attributes defined or overridden directly in class c itself).

The all_members function starts by getting a list of all relevant classes (the class itself and all of its bases, direct and indirect), in the order in which attributes are looked up, in the mro variable (MRO stands for method-resolution order). This happens immediately for a new-style class, since it exposes this information with its _ _mro_ _ attribute we just need to build a list from it, since it is a tuple. If accessing _ _mro_ _ fails, we're dealing with a classic class and must build mro up in a recursive way. We do that in the nested function getmro in the except clause. Note that we give getmro itself as an argument to facilitate recursion in older Python versions that did not support lexically nested scopes.

Once we have mro, we need to reverse it, because we build up our dictionary with the update method. When we call adict.update(anotherdict), the entries in the two dictionaries adict and anotherdict are merged as the new contents of adict. In case of conflict (i.e., a key k is present in both dictionaries), the value used is anotherdict[k], which overrides the previous value of adict[k]. Therefore, we must build our dictionary starting with the classes that are looked up last when Python is looking for an attribute. We move towards the classes that are looked up earlier to reproduce how overriding works with inheritance. The dictionaries we merge in this way are those given sequentially by the built-in function vars on each class. vars takes any object as its argument and returns a dictionary of the object's attributes. Note that even for new-style classes in Python 2.2, vars does not consider inherited attributes, just the attributes defined or overridden directly in the object itself, as dir does only for classic classes.

5.3.4 See Also

Understanding method resolution order is a new challenge even for old Python hands. The best description is in Guido's essay describing the unification of types and classes (http://www.python.org/2.2/descrintro.html#mro), which was refined somewhat in PEP 253 (http://www.python.org/peps/pep-0253.html).



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2005
Pages: 346

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net