Is this considered black magic?

Skip Montanaro montanaro at tttech.com
Mon Nov 12 07:47:10 CET 2001


    Laura> I want to do something which is conceptually very simple.
    Laura> Given a list of objects, call make everybody call the same
    Laura> method you want to run.  Sort of like apply(), but for
    Laura> methods.

I'm used to thinking of a proxy that forwards attribute lookups to a
list of objects as a "collection".  That's what LYMB called it:

    http://www.crd.ge.com/esl/cgsp/fact_sheet/objorien/index.html

(that page is probably five years old - convert all verbs to past
tense...)  and I suspect that's what Smalltalk calls it.

In LYMB, the Collection class was used quite heavily.  I always
wondered why I never needed it in Python.  Maybe I did but just didn't
know it.  ;-) At any rate, here's a simple Collection class for Python
that does pretty much what you asked for.  It implements get (for
retrieving attribute values) and call (for calling attributes)
methods.  It uses the new 2.2 type/class stuff.  You should be able to
run it under 2.1 by subclassing from UserList.UserList instead of
list.

Note that it's not an error to attempt to forward a call to an object
that doesn't implement it.  The output simply doesn't include any
result, so unlike map(), the length of the collection and the output
produced by the get and call methods need not be the same.  That
behavior can easily be changed in a couple different ways, either by
lopping off the hasattr check in the get method or providing a default
value in the get method to substitute for the missing result.
Implementation is left as an exercise for the reader. ;-)

-- 
Skip Montanaro (skip at pobox.com)
http://www.mojam.com/
http://www.musi-cal.com/

#!/usr/bin/env python

class Collection(list):
    """Collections group objects together and forward attribute lookups.

    At least this is the Collection I remember from LYMB (an interpreted OO
    language developed by Bill Lorensen et al at GE CRD), which almost
    certainly got it from Smalltalk.

    I don't understand why I've never needed it in Python.  It was
    indispensable in LYMB.  (hmmm...)

    This doesn't look a lot like LYMB's collection class, but then Python
    doesn't look much like LYMB.  It's also a helluva lot easier to write
    in Python than LYMB because all LYMB's classes were written in C.  ;-)
    """

    def get(self, attr):
        """return a list of attributes from ourselves"""
        return Collection([getattr(x, attr)
	                     for x in self
                               if hasattr(x, attr)])

    def call(self, attr, *args, **kwds):
        """return the result of calling 'attr' for each of our elements"""
        attrs = self.get(attr)
        return Collection([x(*args, **kwds)
                             for x in attrs
                               if callable(x)])

import time

class Food:
    def __init__(self):
        self.t = time.time()
        time.sleep(0.5)

class Ham(Food):
    def say(self):
        print "I am Ham, and I don't want any arguments, thank you."
        return ()

    def speak_up(self, arg):
        print 'I am Ham, and my arguments are %s' % arg
        return arg

class Eggs(Food):
    def speak_up(self, arg='bacon'):
        print 'I am Eggs, and my arguments are %s' % arg
        return arg

class Spam(Food):
    def shout(self, arg1, arg2='sausage'):
        print 'I AM SPAM AND MY ARGUMENTS ARE %s AND %s' % (arg1, arg2)
        return (arg1, arg2)

def main():
    c = Collection()
    # kind of boring example...
    c.extend(range(3))
    print c.get("__hash__")
    print c.call("__hash__")
    print

    # slightly more interesting example...
    c = Collection([Ham(), Eggs(), Spam()])

    print "*** when was this food made? ***"
    times = c.get("t")
    print map(round, times, [2]*len(times))
    print

    print "*** what kind of food is it? ***"
    classcol = c.get("__class__")
    print classcol.get("__name__")
    print

    print "*** shouting ***"
    print c.call("shout", "nickel", "dime")
    print
    
    print "*** speaking up ***"
    print c.call("speak_up", "juice please")
    print
    
    print "*** saying ***"
    print c.call("say")
    print

if __name__ == "__main__":
    main()




More information about the Python-list mailing list