Adventures in Meta programming in Python: im_class, __import__, __getattribute__

With my recent work in Python win32 programming I’ve had a real need for AAA style mocking framework. Unable to find anything that I’ve been completely happy with I started my own simple mocking framework and got to learn some Python style meta programming  in the process. I’ve found out a lot about the depth of the Python 2.x object model over the last 2 weeks and here are some of the nicer things I’ve found (updated as per Chris Taveres below):

function_class = MyClass.foo.im_class

This is useful if you pass in an instance method  somewhere and you want to be able to access its attached class.  Decorators on instance methods normally use this, but I needed it to get a nicer syntax on my asserts, I wanted this:

 

assertCalled(FakeRepo.get)

 

So now when I want to track what class is passed in I just have to start with the following:

def assertCalled(method):
    classdef = method.im_class #I get the method and the class in one parameter!

module = __import__(“foomodule”)

this creates a module import by string. This becomes super powerful say if you wanted to replace functionality on a module, but didn’t know its name before hand.  In my case I wanted to be able to patch in a replacement method for mocking and then put it back when I was done, and I didn’t want to have to keep passing in the module name on resets. The only downside to it so far is it behaves a little quirky with packages and you have walk your module hierarchy to get it to do what you want

example of patching originals back into the module:

for mod, original in _originals.items():  #key is a combination of module name and methodname, value is the original function itself
    modulename = mod[0]
    methodname = mod[1]
    module = __import__(modulename)  #this is the magic. if module name has no package aka : “barmod” this will work. if its “foopackage.barmod” it’ll import the “foopackage” 
    for i in modulename.split(“.”)[1:]: #works down the chain 
        module = getattr(module, i) #resets the module variable with the lower hierarchy module 
    setattr(module, methodname, original) #actually pass the original function object back onto the module.

 

MyClass.__getattribute__ = interception

__getattribute__ is called when any attribute is accessed on a class including __getattribute__ itself which is a bit silly.  This was the primary engine of my mocking framework as it allowed me to record all calls to the methods on a mocked class.  Just remember when you use this to have it not call itself or you’ll be in endless recursion!

example:

class MyClass(object):

    def stuff(self):
        pass

def interceptor(self, name):
    if name == ”__getattribute__”: #guard condition against calling itself
        return object.__getattribute__(self, name)
        #whatever interception logic you need here

MyClass.__getattribute__ = interceptor
= MyClass()
m.stuff() #interception logic will be called here

 

I’m just starting to dip into the Python object model now and I’ll try and share what I find over the next few weeks.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Ryan Svihla

I consider myself a full stack polyglot, and I have been writing a lot of JS and Ruby as of late. Currently, I'm a solutions architect at DataStax
This entry was posted in Python. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • bluehavana

    Hmm… Love the __getattribute__, its like method_missing? but not.

  • http://www.tavaresstudios.com Chris Tavares

    Technically, __getattribute__ isn’t called when a method is called, it’s called when a method (or any attribute of the object) is accessed. It’s original intention was to allow you to build dictionary-like objects, but turns out to be very useful for other things, as you discovered.

    The net result is that if you’ve got a class, Foo, that implements __getattribute__, then:

    o = Foo()
    m = o.SomeMethod # __getattribute__ fires here
    m() # not here

  • http://www.lostechies.com/members/rssvihla/default.aspx Ryan Svihla

    @bluehavana they have that too wait a blog post :)

    @chris tavares
    The __call__ and __init__ methods and I figure several other special methods are not accessed.
    As you said just object attributes seem to be all thats accessed.
    I’ll update with the correction and thanks again!