Decorator to register Python methods in PyCLIPS

523 views Asked by At

I make use of PyCLIPS to integrate CLIPS into Python. Python methods are registered in CLIPS using clips.RegisterPythonFunction(method, optional-name). Since I have to register several functions and want to keep the code clear, I am looking for a decorator to do the registration.

This is how it is done now:

class CLIPS(object):
...
    def __init__(self, data):
        self.data = data
        clips.RegisterPythonFunction(self.pyprint, "pyprint")
    def pyprint(self, value):
        print self.data, "".join(map(str, value))

and this is how I would like to do it:

class CLIPS(object):
...
    def __init__(self, data):
        self.data = data
        #clips.RegisterPythonFunction(self.pyprint, "pyprint")
    @clips_callable
    def pyprint(self, value):
        print self.data, "".join(map(str, value))

It keeps the coding of the methods and registering them in one place.

NB: I use this in a multiprocessor set-up in which the CLIPS process runs in a separate process like this:

import clips
import multiprocessing

class CLIPS(object):
    def __init__(self, data):
        self.environment = clips.Environment()
        self.data = data
        clips.RegisterPythonFunction(self.pyprint, "pyprint")
        self.environment.Load("test.clp")
    def Run(self, cycles=None):
        self.environment.Reset()
        self.environment.Run()
    def pyprint(self, value):
        print self.data, "".join(map(str, value))

class CLIPSProcess(multiprocessing.Process):
    def run(self):
        p = multiprocessing.current_process()
        self.c = CLIPS("%s %s" % (p.name, p.pid))
        self.c.Run()

if __name__ == "__main__":
    p = multiprocessing.current_process()
    c = CLIPS("%s %s" % (p.name, p.pid))
    c.Run()
    # Now run CLIPS from another process
    cp = CLIPSProcess()
    cp.start()
3

There are 3 answers

0
Lemoi On BEST ANSWER

Got it working now by using a decorator to set an attribute on the method to be registered in CLIPS and using inspect in init to fetch the methods and register them. Could have used some naming strategy as well, but I prefer using a decorator to make the registering more explicit. Python functions can be registered before initializing a CLIPS environment. This is what I have done.

import inspect

def clips_callable(func):
    from functools import wraps
    @wraps(func)
    def wrapper(*__args,**__kw):
        return func(*__args,**__kw)
    setattr(wrapper, "clips_callable", True)
    return wrapper

class CLIPS(object):
    def __init__(self, data):
        members = inspect.getmembers(self, inspect.ismethod)
        for name, method in members:
            try:
                if method.clips_callable:
                    clips.RegisterPythonFunction(method, name)
            except:
                pass
...
    @clips_callable
    def pyprint(self, value):
        print self.data, "".join(map(str, value))

For completeness, the CLIPS code in test.clp is included below.

(defrule MAIN::start-me-up
    =>
    (python-call pyprint "Hello world")
)

If somebody knows a more elegant approach, please let me know.

4
mata On

it should be fairly simple to do like this:

# mock clips for testing
class clips:
    @staticmethod
    def RegisterPythonFunction(func, name):
        print "register: ", func, name

def clips_callable(fnc):
    clips.RegisterPythonFunction(fnc, fnc.__name__)
    return fnc

@clips_callable
def test(self):
    print "test"

test()

edit: if used on a class method it will register the unbound method only. So it won't work if the function will be called without an instance of the class as the first argument. Therefore this would be usable to register module level functions, but not class methods. To do that, you'll have to register them in __init__.

0
Amittai Shapira On

It seems that the elegant solution proposed by mata wouldn't work because the CLIPS environment should be initialized before registering methods to it.
I'm not a Python expert, but from some searching it seems that combination of inspect.getmembers() and hasattr() will do the trick for you - you could loop all members of your class, and register the ones that have the @clips_callable attribute to CLIPS.