The Artima Developer Community
Sponsored Link

Python Buzz Forum
Calling Occupants

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Ben Last

Posts: 247
Nickname: benlast
Registered: May, 2004

Ben Last is no longer using Python.
Calling Occupants Posted: Jun 3, 2004 4:17 AM
Reply to this message Reply

This post originated from an RSS feed registered with Python Buzz by Ben Last.
Original Post: Calling Occupants
Feed Title: The Law Of Unintended Consequences
Feed URL: http://benlast.livejournal.com/data/rss
Feed Description: The Law Of Unintended Consequences
Latest Python Buzz Posts
Latest Python Buzz Posts by Ben Last
Latest Posts From The Law Of Unintended Consequences

Advertisement
I've just been faced with adding appropriate superclass calls to eight functions of a Zope product. The main class for the product inherits from three rather important superclasses that all need to have their various manage_ methods poked appropriately. So, inevitably, rather than hardwire in a whole load of calls (which could be done and finished in ten minutes) I choose to invest a while in looking at ways to solve the general problem (which takes a lot longer but which is more fun). Thus the inner nature of the geek is shown.

Let's construct a context for this; non-co-operative superclasses thus:
class A:
    def meth(self):
        print "A"

class B:
    def meth(self):
        print "B"


Now let's write a class C that calls the superclasses. Here's the first pass:
class C(A,B):
    def meth(self):
        for b in self.__class__.__bases__:
            if hasattr(b,'meth'):
                b.meth(self)
        print "C"

That's ok, but there're interesting optimizations to be applied, some of which are even worth doing (some of which are just for fun). Second pass:

class C(A,B):
    def meth(self):
        for b in [x for x in self.__class__.__bases__ if hasattr(x,'meth')]:
            b.meth(self)
        print "C"

Nicer, in that it gives us an excuse to use a list comprehension. However, we're running that whole for, including the expensive hasattr for every call, which offends my sense of efficiency. We could omit the hasattr and use a try: except AttributeError[1], but I'm assuming that we'd use this where we expect the method to exist. Caching is what's wanted:

class C(A,B):
    superdict={}
    
    def meth(self):
        try:
            #retrieve cached list of method objects
            methods = self.__class__.superdict['meth']
        except KeyError:
            #oops, load the cache
            methods = [getattr(x,'meth') for x in self.__class__.__bases__ if hasattr(x,'meth')]
            self.__class__.superdict['meth'] = methods
        #call any superclass methods in appropriate order
        for method in methods: method(self)
        print "C"


So here we keep a dict that maps a method name to a list of superclass methods (as objects). I've left it as a first change from the previous one to show the reasoning, but those hardwired 'meth' constants bug me even now, so let's zap them away. And whilst we're at it, we'd better generalise the arguments a bit too, and make sure we use only callables.
class C(A,B):
    superdict={}

    def callsuper(self, name, *args, **kwargs):
        try:
            #retrieve cached list of method objects
            methods = self.__class__.superdict[name]
        except KeyError:
            #load the cache.
            methods=[]
            for x in self.__class__.__bases__:
                m = getattr(x,name,None)
                if callable(m): methods.append(m)
                
            self.__class__.superdict[name] = methods
        #call any superclass methods in appropriate order.
        for method in methods:
            method(*[self]+list(args), **kwargs)
    
    def meth(self):
        self.callsuper('meth')
        print "C"

Note this uses the Python 2 extended call syntax rather than apply().

There's still one source of error; we have to remember to change the method name parameter for every call to callsuper. To make it extra-super-clever, look at this redefined version:
    def callsuper(self, *args, **kwargs):
        #derive the name of the calling function.
        frame = inspect.currentframe(1)
        name = frame.f_code.co_name
        try:
            #retrieve cached list of method objects
            methods = self.__class__.superdict[name]
        except KeyError:
            #load the cache.
            methods=[]
            for x in self.__class__.__bases__:
                m = getattr(x,name,None)
                if callable(m): methods.append(m)
                
            self.__class__.superdict[name] = methods
        #call any superclass methods in appropriate order.
        for method in methods:
            method(*[self]+list(args), **kwargs)

Here, rather than pass in the name of the method, we use the inspect module to derive it by looking down the stack frame to get the name of the method that called callsuper. Thus our meth method becomes:
    def meth(self):
        self.callsuper()
        print "C"


And that's enough exotic stuff for one day.


[1] Actually, according to at least one source, hasattr is implemented using a getattr and a try: block, so it's no more efficient.

Read: Calling Occupants

Topic: Something Wicked This Way Comes Previous Topic   Next Topic Topic: Same As It Ever Was

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use