Asked Questions

The following is a list of questions that have been asked by PyCLIPS users. Its contents are almost the same as the FAQ file supplied with the source distribution. Optionally there can be comments and updates that in some cases may provide a better explanation.

SendCommand() is a function that has been provided to closely emulate a CLIPS shell environment. In fact, CLIPS does not provide this when used as a library, it has been coded implementing the same operations that are performed in the CLIPS command loop, except for the input part of course. This allows also to issue some commands that fail when called using the Eval() function. For an example, consider the following session:

>>> t = clips.BuildTemplate("car", "(slot make)(slot model)")
>>> f0 = t.BuildFact()
>>> f0.Slots['make'] = clips.Symbol('Chevy')
>>> f0.Slots['model'] = clips.Symbol('Prizm')
>>> f0.Assert()
>>> clips.PrintFacts()
f-0     (car (make Chevy) (model Prizm))
For a total of 1 fact.
>>> clips.Eval("(modify 0 (make Geo))")
Traceback (most recent call last):
    File "<pyshell#20>", line 1, in -toplevel-
        clips.Eval("(modify 0 (make Geo))")
    File ".../", line 3227, in Eval
        ClipsError: C10: unable to evaluate expression
>>> print clips.ErrorStream.Read().strip()
[TMPLTFUN1] Fact-indexes can only be used by modify as a top level command.

Whereas, if you use SendCommand(), the same expression actually becomes a top level command, and it is executed in the expected way:

>>> clips.SendCommand("(modify 0 (make Geo))")
>>> clips.PrintFacts()
f-1     (car (make Geo) (model Prizm))
For a total of 1 fact.

There are other actions that behave differently when "evaluated" compared to when executed at the CLIPS shell prompt or within a Load() call: the SendCommand() function can also be used as a shortcut in these cases.

Some users have noticed that, while many "basic" types (mostly numbers and strings) are automatically converted to their CLIPS counterpart when calling an external Python function, the 'bool' type is not. There is a reason for this behaviour, that is the absence of a real boolean type in the base classes of CLIPS. CLIPS uses the "special" SYMBOLs 'TRUE' and 'FALSE' as if they were booleans, but they remain SYMBOLs.

The issue is partially covered by letting the value clips.Symbol('FALSE') to actually evaluate to a False boolean. In this way, whenever a CLIPS function called from Python returns that particular SYMBOL, it is seen as False only in tests, while not loosing its SYMBOL nature.

However it seemed too arbitrary to convert a Python 'bool' to one of the two discussed SYMBOLs. In fact, the documentation states that it should not been taken for granted that PyCLIPS does all the conversion job, and that especially Python functions that return values back to CLIPS must be carefully written, and possibly return values wrapped in CLIPS types.

For functions that return booleans, this is even more needed: Python has a peculiar way to treat many things as True or False, depending on their values: an empty string is False, a non-empty one is True, for instance.

A solution to this issue could be to write a wrapper function in order to let test functions return the correct SYMBOL value to CLIPS:

def clips_test_wrapper(f):
    def wf(*a, **kwa):
        if f(*a, **kwa):
            return clips.Symbol("TRUE")
            return clips.Symbol("FALSE")
    wf.__name__ = f.__name__    # explanation is below
    return wf

The reason why we rewrite the __name__ attribute of the returned function is that in this way the function registration utility will register the wrapper function with the original name. An example follows:

>>> def py_true():
        return True
>>> clips.RegisterPythonFunction(clips_test_wrapper(py_true))
>>> clips.Eval("(python-call py_true)")
<Symbol 'TRUE'>

To unregister the function we only need to supply either the name of the function as it is known to CLIPS or any function whose __name__ attribute corresponds to this, such as the wrapped function or the original one.

There is not a direct way, but you can traverse the list of existing facts to come up to the one that has the required index. The following is an example of function that uses this technique:

def FindFactByIndex(idx):
    f = clips.InitialFact()
    while f is not None and f.Index != idx:
        f = f.Next()
    return f

This can easily be implemented in an environment-aware way. Of course the given implementation has a linear cost with respect to the total number of facts (which could be unknown, and significantly big), but at the moment and with the current CLIPS API, there is no other way to achieve the same goal more efficiently.

Not for now. There are other ways to deal with this: for instance you can create a dictionary containing the objects you'd like to be managed by the CLIPS subsystem mapped to strings, and then use the strings to reference the objects. It's not unlikely that there will be the possibility to use generic Python objects directly in one of the next releases.

Mostly for historical reasons. If you look for the term expert system shell on Wikipedia, you are redirected to the page titled Inference Engine, since the two concepts were tightly coupled in the past - probably no one thought that an inference engine could be implemented as a library, and almost all available engines were in the form of a command line interpreter. On the other hand, CLIPS provides both access methods: a simple command line (a shell, in other words) and the possibility to be embedded as a library.

PyCLIPS takes advantage of this second way of using CLIPS, but in order to provide also a means to use the engine more or less interactively, through the Python interpreter, it gives the possibility to send complete commands to the library as if it were a standalone interpreter using the SendCommand() function, and to read what CLIPS provides as output using the so-called streams mentioned in the documentation. Although this is a side effect, I consider it an useful feature.

Moreover, it is now possible to use a simple script to implement a basic CLIPS shell in PyCLIPS, and to enhance it to implement new features into CLIPS itself.

The short answer: not really, you only have to distribute the PyCLIPS module for the target architecture. Apart from other libraries or modules needed by your application, of course.

The long answer: PyCLIPS is a self-contained module. When it is compiled, it also recompiles the entire CLIPS library to fit into the module, using appropriate parameters and compile options. The resulting module can be distributed in binary form (even though you will have to make some considerations about the license) for the architecture that you intend to support. So you don't need to include a CLIPS executable of whatever type, or a CLIPS DLL: the library is hard compiled into the module from scratch.