user975135
user975135

Reputation:

allow users to "extend" API functions

I'm writing a modding API for my game and I want to allow users to tell the game engine to run their mod's function immediately before or after one of the API functions, in other words to "extend" the functions.

Before now modders had to rewrite the function to add functionality to it which meant digging in the game's sourcecode, which I think sucks for them.

Right now I'm thinking of something like this:

def moddersFunction():
    pass

myAPI.extendFunction('functionName', 'before-or-after', moddersFunction, extraArgs=[])

Now what will be comparably clean way to implement this? In other words, what would the internals of myAPI.extendFunction look like if you were to write it?

I'm thinking of having a dictionary which myAPI.extendFunction will add functions to and the functions in my API will check and run the mod's function if it has been set ("registered"). But this means adding the code which checks the dictionary in every single function in my modding API which I want to allow to be extended (even if it's just a single function call which does the check and function calling itself, it seems to much itself).

I'm wondering if there's some neat Python trick which will allow such "extending" of functions without "butchering" (maybe I'm exaggerating) the existing sourcecode of my game's API.

Upvotes: 1

Views: 226

Answers (2)

NPE
NPE

Reputation: 500933

I would do something along the following lines:

class hookable(object):
  def __init__(self, fn):
    self.pre = []
    self.post = []
    self.fn = fn
  def add_pre(self, hook):
    self.pre.append(hook)
  def add_post(self, hook):
    self.post.append(hook)
  def __call__(self, *args, **kwargs):
    for hook in self.pre:
      hook(*args, **kwargs)
    ret = self.fn(*args, **kwargs)
    for hook in self.post:
      hook(*args, **kwargs)
    return ret

Any "extendable" function can now be decorated like so:

@hookable
def square(x):
  return x**2

Now you can use square.add_pre() and square.add_post() to register functions that would automatically get called before and after each call to square():

print square(2)

def pre(x): print 'pre', x
def post(x): print 'post', x

square.add_pre(pre)
print square(2)
print square(3)

square.add_post(post)
print square(4)
print square(5)

Upvotes: 2

Jakob Bowyer
Jakob Bowyer

Reputation: 34708

I would probably do something like this. Obviously what you actually do is specific to your requirements.

class Extend(object):
    def __init__(self, original_function):
        self.original_function = original_function

    def __call__(self, function):
        def _wrapped(*args, **kwargs):
            yield function(*args, **kwargs)
            yield self.original_function(*args, **kwargs)
        return _wrapped

import time

@Extend(time.asctime)
def funky_time():
    return "This is the time in the UK"

print list(funky_time())

Upvotes: 0

Related Questions