3

I'm trying to hijack any calls made to my class and pass them to a method of my choice on the class.

My class so far:

class Klass(object):
    def __getattribute__(self, name):
        if name == '_dummy_func':
            return object.__getattribute__(self, name)
        return object.__getattribute__(self, 'dummy_func')

    def _dummy_func(self):
        print 'dummy func called!'

which works when I do this:

cls = Klass()
cls.foo()

but falls over when trying to do this:

cls = Klass()
cls.foo.bar()

as dummy_func has no attribute bar.

I looked at trying to catch this nested behaviour in the __getattribute__() by checking to see if name is a function as described here, however it's a string not the actual variable.

Is there a way to catch this from inside Klass?

Community
  • 1
  • 1
ghickman
  • 5,893
  • 9
  • 42
  • 51

1 Answers1

5

Instead of returning a dummy function, just return a dummy object which is callable through the __call__ method and in turn defines __getattr__ to return another dummy object if an attribute is requested:

class Dummy(object):
  def __init__(self, klass, path):
    self.klass = klass
    self.path = path

  def __getattr__(self, attr):
    return Dummy(self.klass, self.path + [attr])

  def __call__(self, *args, **kw):
    self.klass._dummy_func(self.path, *args, **kw)

class Klass(object):
  def __getattr__(self, attr):
    return Dummy(self, [attr])

  def _dummy_func(self, path, *args, **kw):
    print "Dummy function %s called with %d arguments!" \
                      % ('.'.join(path), len(args))

Klass().foo.bar.func(1, 2)  
# => "Dummy function foo.bar.func called with 2 arguments!"

You can now apply your logic depending on the full path given to _dummy_func. You even get the supplied arguments and keywords to work with.

Niklas B.
  • 92,950
  • 18
  • 194
  • 224
  • That is brilliant, thanks. It also covers further nesting of variables, which is perfect. – ghickman Mar 03 '12 at 15:13
  • Nice use of `path` to remember the attribute lookup chain; however, `Dummy.klass` is not actually a class, but an instance (it also is unused). – Ethan Furman Mar 03 '12 at 15:24
  • @Ethan: Look again. It's not named `klass` because it's a class, but because it's an instance of type `Klass`. It's also not unused (hint: line 10). – Niklas B. Mar 03 '12 at 15:30
  • 2
    I am amazed. When I first saw your code I thought it was convoluted and unnecessary, with mine being much simpler; now I see that you are using `Dummy` as a memory for the lookup chain, and when Python finally decides to call it simply passing that memory back to the original `Klass` object. Beautiful. I take my hat off to you. – Ethan Furman Mar 03 '12 at 15:43
  • @Ethan Furman: I've rarely seen a comment like this on this site. I have to take *my* hat off to you for your fairness :) – Niklas B. Mar 03 '12 at 16:10