Python question: interception of arbitrary methods

In my current game, I would like to be able to detect calls to specified methods of specified objects, preferably without building in a callback variable for every method for which I might want to have such a callback. I think that I have a solution, but want to check that (A) no better (perhaps even built-in) solution exists and (B) my solution is sensible.

What I have in mind is this:
Given a class Cat, instance aCat, and instance method Cat.meow, the object that wants to receive a callback does the following:

    self.meowInCat = aCat.meow
    aCat.meow = self.myMeow

def myMeow(<the same parameters as Cat.meow>):
    # Do whatever one wants to do when Cat.meow is called.
    self.meowInCat(<pass in those same parameters>)

I would use a decorator. Simply decorate every method you want to have intercepted with the interceptor, and implement whatever you need to be done inside the interceptor. Here is an example:

from functools import wraps

def intercept(f):
  @wraps(f)
  def wrapper(*args, **kwds):
    print 'interception args=', args
    # do whatever you want to do before calling the intercepted function
    return f(*args, **kwds)
    # do whatever you want to do after calling the intercepted function
  return wrapper

class Cat:
  @intercept
  def meow(self, a, b):
    print 'meow({0}, {1})'.format(a, b)

cat = Cat()
cat.meow(1, 2)

Hmm… That looks as though it should work, but also appears to be more limited than what I had in mind.

Perhaps I should briefly describe what I have in mind:

My game is – more or less – an adventure game. In order to increase the potential variety of reactions by my game, I want to allow my game scripts to pick up on the firing of methods of individual instances of my various classes, rather than every firing of those methods from any instance of those classes.

For example, let’s say that I have two doors in my level, a and b, of class Door, and that Door has a method “open” which plays an animation of the door opening. Further, let’s say that a opens onto a particularly remarkable room, while b opens onto a boring room. I might want to be able to tell that a has been opened, and have the player character respond accordingly. To that end, I would like to detect the firing of a.open – but not b.open – and have some custom code run.

The advantage that I see in my code above that it it looks as though I don’t have to know ahead of time which methods of which classes I want to connect to; I can just have a script associated with my level make the connection with the relevant method of the relevant object.

Well, still possible:

class Cat:
  def meow(self, a, b):
    print 'meow({0}, {1})'.format(a, b)

cat = Cat()
cat.meow = intercept(cat.meow)
cat.meow(1, 2)

You might want to backup the original method cat.meow before replacing it with the intercepted method, similar to the way you did it in your own example. Just in case you want to remove the interceptor at runtime, or replace it with some other interceptor.

Decorators are usually much more powerful than plain wrapper methods, because they can have state. For example it would be easy to have the door play a sound if a certain player opens the door for the second time. With wrapper you need to resort to global variables (or abuse the first param of the unbound method, which will be “self” after binding a method to an instance. But this is everything but readable and might lead to unintenional overwriting of instance members…).

In your case decorators are probably an overkill. Simply replacing the bound instance method is perfectly fine if this suits all your requirements. I just wanted to point at this technique because it is really powerful, and allows good reuse of code and encapsulation.

Hmm… I’ll confess that I’m not familiar with decorators, but it actually still looks to me to be less powerful than my version, for my purposes, at least: from what I see there, I have to know ahead of time that I might want to intercept “meow” (since you add a decorator to it), and it looks as though I have to rewrite “intercept” for each different response that I want to an interception (does that call for a different decorator for each?), unless, I suppose, I provide some additional callback method as a parameter to “intercept” and use something like a dictionary to determine which response to use.

Bear in mind that I’m not necessarily talking about a single response to every interception; more likely each interception would have its own response. Opening door a might have the character wonder at the lovely room beyond, interacting with a cat for the first time might make the character reminisce about the cat that he left at home (I like cats. Can you tell? ;P), being teleported for the first time might make the character play a “throwing up” animation, etc.

I’ll admit that the state is desirable, and likely to be something that I’ll want; as it is, however, I already have a global dictionary that is saved with my game saves (unless I’ve inadvertantly broken that feature since then ^^; ), so state can be stored in that.

Please don’t get me wrong. I don’t want to convince you to use decorators. I consider this thread merely as a discussion of different solutions. I will throw in a third option at the bottom of this post. No solution is a “one-size-fits-all”.

–1--
You seem to be afraid that decorators can be used only for all instances of a class. This is not true. If you look at my second post you will see that the method Cat.meow dow NOT have a decorator; I add the decorator in the line after creating an instance of the class Cat.

The following two lines from your my example do almost the same. They replace the original Cat::meow-method with something else, for one instance and only one instance

aCat.meow = self.myMeow # wrapper
aCat.meow = intercept(cat.meow) # decorator

So far both techniques are equal. Yours is more lightweight, and thus prefered.

–2--
Let me show you some situations where decorators might be more handy. I pick up the sichness aspect you mentioned earlier. The player should get nausea when opening a door, when smashing a door, and when drinking a potion.

With your technique:

class Door:
  def open(self): pass
  def smash(self): pass

class Potion:
  def drink(self): pass

# -----

def myOpen(self):
  # globals.player.set_nausea()
  self.openInDoor()

def mySmash(self):
  # globals.player.set_nausea()
  self.smashInDoor()

def myDrink(self):
  # globals.player.set_nausea()
  self.drinkInPotion()

# -----

aDoor = Door()
aDoor.openInDoor = aDoor.open
aDoor.open = myOpen
aDoor.smashInDoor = aDoor.smash
aDoor.smash = mySmash

aPotion = Potion()
aPotion.drinkInPotion = aPotion.drink
aPotion.drink = myDrink

The decorator is reusable:

class Door:
  def open(self): pass
  def smash(self): pass

class Potion:
  def drink(self): pass

# ----

def nausea(f):
  @wraps(f)
  def wrapper(*args, **kwds):
    # globals.player.set_nausea()
    return f(*args, **kwds)
  return wrapper

# ----

aDoor = Door()
aDoor.open = nausa(aDoor.open)
aDoor.smash = nausa(aDoor.smash)

aPotion = Potion()
aPotion.drink = nausa(aPotion.drink)

Less code, because of better re-usability. Re-usability comes from the fact that the decorator does not need to know the name of the method to be called - the method is kept inside the decorator, in a generic way.

–3--
Now let’s assume you have mushrooms, and eating a mushroom should cause nausea. But every mushroom should have a different nausea duration. The duration might be part of the level data.

from functools import wraps

class Mushroom:
  def eat(self): pass

def nausea(f, duration):
  @wraps(f)
  def wrapper(*args, **kwds):
    # globals.player.set_nausea(duration)
    return f(*args, **kwds)
  return wrapper

mushroom1 = Mushroom()
mushroom1.eat = nausea(mushroom1.eat, 30)

mushroom2 = Mushroom()
mushroom2.eat = nausea(mushroom2.eat, 90)

mushroom3 = Mushroom() # no nausea!

mushroom1.eat()
mushroom2.eat()
mushroom3.eat()

How would you implement this using your way? One option would be to make use of a global dictionary where you register objects with their corresponding duration. Possible, but a general rule is that data and the functions acting upon data should be kept close together.

–4--
I promised to throw in a third technique. It is not based on exchanging the methods on a class, but a fundamental pattern of de-coupling: events.

The method Cat::meow would emit an event each time it is called. It might pack additional data inside the event, but it does not need to.

You first need to have an Event class, for example:

class Event:
  def __init__(self):
    self.handlers = set()

  def __iadd__(self, handler):
    self.handlers.add(handler)
    return self

  def __isub__(self, handler):
    try:
      self.handlers.remove(handler)
    except:
      raise ValueError('not handling this event!')
    return self

  def __call__(self, *args, **kargs):
    for handler in self.handlers:
      handler(*args, **kargs)

  def _(self):
    return len(self.handlers)

This is effectively a Python implementation of the C# event framework. not many lines :slight_smile:

Now the code for the Cat class and the creation of two Cat instance. We also need an handler which act upon the nausa event. the handler is registered for only one cat-instance.

class Cat:
  def __init__(self):
    self.nausea = Event()

  def meow(self):
    self.nausea()
    print 'meow'


def handler():
  print 'player got nausea!'


aCat = Cat()
aCat.nausea += handler

bCat = Cat()


aCat.meow() # player will get nausea
bCat.meow() # player won't get nausea

Fair enough – it’s an interesting discussion, at that. :slight_smile:

Ah, you’re right – my apologies for having missed that; I plead tiredness!

In fact, looking again, your two techniques are both rather impressive. That addition trick in the event example is especially neat!

Hmm…
I’ll confess that my technique seems to be weak here.

If the data were intended to be saved, then I’d probably use my global dictionary – such global flags are pretty much its purpose (although I realised after I started using it that I could also use the similar dictionary that my Player class holds, since the player object should have continuity between levels).

However, if the data were unchanging values that could simply be defined once, then I’d likely make them member variables – the scripts that I intend this for, you see, are all sub-classes of a “LevelScript” class.

I might have something like this:

class Mushroom:
  def eat(self): pass

class ALevelScript(LevelScript):
    def __init__(self):
        LevelScript.__init__(self)

        #  I've found that this method seems to involve calling the original method 
        # as though it were a class method, passing in the object as the first argument;
        # as a result I keep the object around to pass in.
        self.mushroom1 = #<code to get mushroom 1>
        self.mushroom2 = #<code to get mushroom 2>
        #  I don't fetch mushroom 3, since I don't want to intercept its "eat" method,
        # but presume that it exists
        self.mushroom1NauseaDuration = 30
        self.mushroom2NauseaDuration = 90

        if self.mushroom1 is not None: # In case fetching failed
            self.mushroom1Eat = self.mushroom1.eat
            self.mushroom1.eat = self.eatMushroom1

        if self.mushroom2 is not None: # In case fetching failed
            self.mushroom2Eat = self.mushroom2.eat
            self.mushroom2.eat = self.eatMushroom2

    def eatMushroom1(self):
        Mushroom.eat(self.mushroom1)
        self.nausea(self.mushroom1NauseaDuration)

    def eatMushroom2(self):
        Mushroom.eat(self.mushroom2)
        self.nausea(self.mushroom2NauseaDuration)

    def nausea(self, duration):
        # Make the player nauseous
        print "Ooh-ergh, I don't feel so good! D:"

A bit verbose, in all fairness.

If the nausea effect were something intended to cross between various levels, then I might instead make it a property of the Mushroom class, and not intercept at all.

For the test-/demo- level that I’m currently putting together I actually have a bit of state to keep: I’m only running the additional code when the methods in question are run for the first time. However, I want that state to persist if the player saves and reloads, so I’m storing a variable in the above-mentioned global dictionary and checking that before I run the additional code.