How to build Hierarchies of dict's? (Prototypes in Python?)

Toby etatoby at gmail.com
Sun Feb 25 16:12:21 EST 2007


Charles D Hixson wrote:
> I want to access the values via instances of the class, so your D().a
> approach isn't suitable.  More like:  "t = D(); t[a]"

Well, D() is an instance of D, so  D().a  is the same as  t = D(); t.a
In fact the various "a" attributes I'm accessing are class attributes of
the various classes, just as you asked.

If you'd rather use t[a], then things are simpler, because you won't
need to override __getattribute__, with all the ugliness that comes with
it: __getitem__ will suffice.


> Your approach uses techniques that I'm going to need to study
> carefully before I can hope to understand them.

Don't worry, here's a heavily commented version, just because it's
sunday and I have some free time ;-)


# inheriting from object so that we can use __getattribute__
# see http://www.python.org/download/releases/2.2.3/descrintro/
class AttrSearch(object):  
  '''Base class that customizes attribute access in all of its derived
  classes, choosing between all the candidates in a custom way.'''


  @classmethod
  def getclassattrset(cls, name):
    '''Class method (= polymorphic static method) that collects a set
    of the attributes with a given name from itself and from all its
    base classes (determined at runtime.)
    
    The only argument is the name of the attribute to look up; the
    return value is a list of (class_name, attribute_value) listing all
    the candidates found.'''

    # note: the first parameter 'cls' is the class of the runtime object
    # on which this class method is called, much like 'self' is the
    # object instance in instance methods
    #
    # notice: this method is defined on class AttrSearch, but if I call
    # it on B (subclass of AttrSearch), then 'cls' is class B!

    s = set()  # a set is an unordered list without duplicates

    try:
      # __dict__ is the internal dictionary which holds all of a class
      # or of an instance's attributes

      # creating a tuple with the name of the runtime class 'cls'
      # and the value of cls's attribute named name, and adding the
      # tuple to the set (unless an equal tuple is already there)
      s.add((cls.__name__, cls.__dict__[name]))
    except KeyError:
      # ...unless the class cls has no such attribute
      pass

    # for every base class, from which the runtime class inherits:
    for base in cls.__bases__:
      try:
        # call this same method on them and add their results to the set
        s.update(base.getclassattrset(name))
      except AttributeError:
        # except for the base classes which don't implement this method
        # (for example the class object)
        pass

    return s  # returning the collected set


  def getattrset(self, name):
    '''Instance method that collects a set of the attributes with a
    given name from itself, from its class and from all the base classes.
    
    The only argument is the name of the attribute to look up; the
    return value is a list of (class_name, attribute_value) listing all
    the candidates found.  In case the attribute is also found in the
    object itself, element 0 of the tuple is set to null.'''

    # saving references to a couple of attributes we need to access
    # directly, bypassing all this machinery; to achieve it, we
    # explicitly call object's implementation of __getattribute__

    self_dict = object.__getattribute__(self, '__dict__')
    self_class = object.__getattribute__(self, '__class__')

    s = set()

    try:
      # adding to the set the attribute named name in this very intance,
      # if it exists, with None in place of the class name
      s.add((None, self_dict[name]))
    except KeyError:
      # unless the instance doesn't have an attribute named name
      pass

    # addig to the set all the class attributes named name, from this
    # object's class and all its base classes
    s.update(self_class.getclassattrset(name))

    return s


  def __getattribute__(self, name):
    '''Customized version of attribute fetching, that uses getattrset
    (and thus getclassattrset) to get a list of all the attributes named
    name before choosing which one to return.'''

    # saving references to the attributes we need to access directly
    self_class = object.__getattribute__(self, '__class__')

    # calling AttrSearch's getattrset to do the dirty work
    found = AttrSearch.getattrset(self, name)

    # here is where you should examine 'found' and choose what to
    # return; I only print what is available and return None
    print 'Looking for "%s" in a %s instance, found %d candidates:' \
        % (name, self_class.__name__, len(found))
    print '  class  value'
    print '  =====  ====='
    print '\n'.join([ "  %-6s '%s'" % x for x in found ])
    print '(now choose wisely what to return)'

    return None


# example use of AttrSearch in a class hierarchy:

class A(AttrSearch):
  a = 'attribute "a" of class A'

class B(A):
  a = 'attribute "a" of class B'

class C(A):
  a = 'attribute "a" of class C'

class D(B, C):
  a = 'attribute "a" of class D'

t = D()
t.a = 'attribute "a" of instance t'

# --- end ---

Now if you ask for t.a, for example in a print statement, you get None,
but not before the following lines are printed to stdout:


Looking for "a" in a D instance, found 5 candidates:
  class  value
  =====  =====
  D      'attribute "a" of class D'
  B      'attribute "a" of class B'
  A      'attribute "a" of class A'
  None   'attribute "a" of instance t'
  C      'attribute "a" of class C'
(now choose wisely what to return)



HTH
Toby

PS: I couldn't make out what you meant with your code... I fear it's
because of the hideous formatting :-)



More information about the Python-list mailing list