When I try to use print without parentheses on a simple name in Python 3.4 I get:
>>> print max
Traceback (most recent call last):
...
File "<interactive input>", line 1
print max
^
SyntaxError: Missing parentheses in call to 'print'
Ok, now I get it, I just forgot to port my Python 2 code.
But now when I try to print the result of a function:
>>> print max([1,2])
Traceback (most recent call last):
...
print max([1,2])
^
SyntaxError: invalid syntax
Or:
print max.__call__(23)
^
SyntaxError: invalid syntax
(Note that the cursor is pointing to the character before the first dot in that case.)
The message is different (and slightly misleading, since the marker is below the max function).
Why isn't Python able to detect the problem earlier?
Note: This question was inspired by the confusion around this question: Pandas read.csv syntax error, where a few Python experts missed the real issue because of the misleading error message.
Looking at the source code for exceptions.c, right above _set_legacy_print_statement_msg there's this nice block comment:
/* To help with migration from Python 2, SyntaxError.__init__ applies some
* heuristics to try to report a more meaningful exception when print and
* exec are used like statements.
*
* The heuristics are currently expected to detect the following cases:
* - top level statement
* - statement in a nested suite
* - trailing section of a one line complex statement
*
* They're currently known not to trigger:
* - after a semi-colon
*
* The error message can be a bit odd in cases where the "arguments" are
* completely illegal syntactically, but that isn't worth the hassle of
* fixing.
*
* We also can't do anything about cases that are legal Python 3 syntax
* but mean something entirely different from what they did in Python 2
* (omitting the arguments entirely, printing items preceded by a unary plus
* or minus, using the stream redirection syntax).
*/
So there's some interesting info. In addition, in the SyntaxError_init method in the same file, we can see
/*
* Issue #21669: Custom error for 'print' & 'exec' as statements
*
* Only applies to SyntaxError instances, not to subclasses such
* as TabError or IndentationError (see issue #31161)
*/
if ((PyObject*)Py_TYPE(self) == PyExc_SyntaxError &&
self->text && PyUnicode_Check(self->text) &&
_report_missing_parentheses(self) < 0) {
return -1;
}
Note also that the above references issue #21669 on the python bugtracker with some discussion between the author and Guido about how to go about this. So we follow the rabbit (that is, _report_missing_parentheses) which is at the very bottom of the file, and see...
legacy_check_result = _check_for_legacy_statements(self, 0);
However, there are some cases where this is bypassed and the normal SyntaxError message is printed, see MSeifert's answer for more about that. If we go one function up to _check_for_legacy_statements we finally see the actual check for legacy print statements.
/* Check for legacy print statements */
if (print_prefix == NULL) {
print_prefix = PyUnicode_InternFromString("print ");
if (print_prefix == NULL) {
return -1;
}
}
if (PyUnicode_Tailmatch(self->text, print_prefix,
start, text_len, -1)) {
return _set_legacy_print_statement_msg(self, start);
}
So, to answer the question: "Why isn't Python able to detect the problem earlier?", I would say the problem with parentheses isn't what is detected; it is actually parsed after the syntax error. It's a syntax error the whole time, but the actual minor piece about parentheses is caught afterwards just to give an additional hint.
The special exception message for print used as statement instead of as function is actually implemented as a special case.
Roughly speaking when a SyntaxError is created it calls a special function that checks for a print statement based on the line the exception refers to.
However, the first test in this function (the one responsible for the "Missing parenthesis" error message) is if there is any opening parenthesis in the line. I copied the source code for that function (CPython 3.6.4) and I marked the relevant lines with "arrows":
static int
_report_missing_parentheses(PySyntaxErrorObject *self)
{
Py_UCS4 left_paren = 40;
Py_ssize_t left_paren_index;
Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text);
int legacy_check_result = 0;
/* Skip entirely if there is an opening parenthesis <---------------------------- */
left_paren_index = PyUnicode_FindChar(self->text, left_paren,
0, text_len, 1);
if (left_paren_index < -1) {
return -1;
}
if (left_paren_index != -1) {
/* Use default error message for any line with an opening parenthesis <------------ */
return 0;
}
/* Handle the simple statement case */
legacy_check_result = _check_for_legacy_statements(self, 0);
if (legacy_check_result < 0) {
return -1;
}
if (legacy_check_result == 0) {
/* Handle the one-line complex statement case */
Py_UCS4 colon = 58;
Py_ssize_t colon_index;
colon_index = PyUnicode_FindChar(self->text, colon,
0, text_len, 1);
if (colon_index < -1) {
return -1;
}
if (colon_index >= 0 && colon_index < text_len) {
/* Check again, starting from just after the colon */
if (_check_for_legacy_statements(self, colon_index+1) < 0) {
return -1;
}
}
}
return 0;
}
That means it won't trigger the "Missing parenthesis" message if there is any opening parenthesis in the line. That leads to the general SyntaxError message even if the opening parenthesis is in a comment:
print 10 # what(
print 10 # what(
^
SyntaxError: invalid syntax
Note that the cursor position for two names/variables separated by a white space is always the end of the second name:
>>> 10 100
10 100
^
SyntaxError: invalid syntax
>>> name1 name2
name1 name2
^
SyntaxError: invalid syntax
>>> name1 name2([1, 2])
name1 name2([1, 2])
^
SyntaxError: invalid syntax
So it is no wonder the cursor points to the x of max, because it's the last character in the second name. Everything that follows the second name (like ., (, [, ...) is ignored, because Python already found a SyntaxError, and it doesn't need to go further, because nothing could make it valid syntax.
Maybe I'm not understanding something, but I don't see why Python should point out the error earlier. print is a regular function, that is a variable referencing a function, so these are all valid statements:
print(10)
print, max, 2
str(print)
print.__doc__
[print] + ['a', 'b']
{print: 2}
As I understand it, the parser needs to read the next full token after print (max in this case) in order to determine whether there is a syntax error. It cannot just say "fail if there is no open parenthesis", because there are a number of different tokens that may go after print depending on the current context.
I don't think there is a case where print may be directly followed by another identifier or a literal, so you could argue that as soon as there is one letter, a number or quotes you should stop, but that would be mixing the parser's and the lexer's job.
in additions to those excellent answers, without even looking at the source code, we could have guessed that the print special error message was a kludge:
so:
print dfjdkf
^
SyntaxError: Missing parentheses in call to 'print'
but:
>>> a = print
>>> a dsds
Traceback (most recent call last):
File "<interactive input>", line 1
a dsds
^
SyntaxError: invalid syntax
even if a == print but at that stage, it isn't evaluated yet, so you get the generic invalid syntax message instead of the hacked print syntax message, which proves that there's a kludge when the first token is print.
another proof if needed:
>>> print = None
>>> print a
Traceback (most recent call last):
File "C:\Python34\lib\code.py", line 63, in runsource
print a
^
SyntaxError: Missing parentheses in call to 'print'
in that case print == None, but the specific message still appears.
Related
Just in recent times I went through one of my code (in Python) in which guard evaluation was badly missed. I shortened my code to make it a brief one
>>> x = 4
>>> y = 0
>>> x >= 1 and (x/y) > 2
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
x >= 2 and (x/y) > 2
ZeroDivisionError: integer division or modulo by zero
here we need to add guard
>>> x >= 1 and y != 0 and (x/y) > 2 # y!=0 is a guard
False
Now I have two questions:
I believe similar kind of scenario could have been best caught with
C++, as it compiles the code and will produce a warning at first. Let
me know if I am wrong here?
Other question is that we use py_compile.compile('file_name') and it
just verifies syntax. Don't we have any module in Python that can catch
these kind of misses?
Since python is a loosely typed language, thus it become difficult(impossible i believe) to analyze the code for the variable types. We can use pep8 or pylint to analyze the code. They can only inform us about the indenting and code writing as per PEP.
For below file guard_eval.py
sherry#Sherry-Linux:~$ cat guard_eval.py
x=6
y=0
if x >= 1 and (x/y) > 2:
print True
else:
print False
sherry#Sherry-Linux:~$ pep8 guard_eval.py
guard_eval.py:1:2: E225 missing whitespace around operator
guard_eval.py:2:2: E225 missing whitespace around operator
guard_eval.py:4:1: W191 indentation contains tabs
guard_eval.py:6:1: W191 indentation contains tabs
pylint provides code ratings as well :)
But in case of C++ we can modify the compiler to analyze code with the variable type and hint user to have guard expression for integer/integer division while compiling.
A while ago, I made a Python script which looked similar to this:
with open("somefile.txt", "r") as f, open("otherfile.txt", "a") as w:
for line in f:
w.write(line)
Which, of course, worked pretty slowly on a 100mb file.
However, I changed the program to do this
ls = []
with open("somefile.txt", "r") as f, open("otherfile.txt", "a") as w:
for line in f:
ls.append(line)
if len(ls) == 100000:
w.writelines(ls)
del ls[:]
And the file copied much faster. My question is, why does the second method work faster even though the program copies the same number of lines (albeit collects them and prints them one by one)?
I may have found a reason why write is slower than writelines. In looking through the CPython source (3.4.3) I found the code for the write function (took out irrelevent parts).
Modules/_io/fileio.c
static PyObject *
fileio_write(fileio *self, PyObject *args)
{
Py_buffer pbuf;
Py_ssize_t n, len;
int err;
...
n = write(self->fd, pbuf.buf, len);
...
PyBuffer_Release(&pbuf);
if (n < 0) {
if (err == EAGAIN)
Py_RETURN_NONE;
errno = err;
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
return PyLong_FromSsize_t(n);
}
If you notice, this function actually returns a value, the size of the string that has been written, which is another function call.
I tested this out to see if it actually had a return value, and it did.
with open('test.txt', 'w+') as f:
x = f.write("hello")
print(x)
>>> 5
The following is the code for the writelines function implementation in CPython (took out irrelevent parts).
Modules/_io/iobase.c
static PyObject *
iobase_writelines(PyObject *self, PyObject *args)
{
PyObject *lines, *iter, *res;
...
while (1) {
PyObject *line = PyIter_Next(iter);
...
res = NULL;
do {
res = PyObject_CallMethodObjArgs(self, _PyIO_str_write, line, NULL);
} while (res == NULL && _PyIO_trap_eintr());
Py_DECREF(line);
if (res == NULL) {
Py_DECREF(iter);
return NULL;
}
Py_DECREF(res);
}
Py_DECREF(iter);
Py_RETURN_NONE;
}
If you notice, there is no return value! It simply has Py_RETURN_NONE instead of another function call to calculate the size of the written value.
So, I went ahead and tested that there really wasn't a return value.
with open('test.txt', 'w+') as f:
x = f.writelines(["hello", "hello"])
print(x)
>>> None
The extra time that write takes seems to be due to the extra function call taken in the implementation to produce the return value. By using writelines, you skip that step and the fileio is the only bottleneck.
Edit: write documentation
I do not agree with the other answer here.
It is simply a coincidence. It highly depends on your environment:
What OS?
What HDD/CPU?
What HDD file system format?
How busy is your CPU/HDD?
What Python version?
Both pieces of code do the absolute same thing with tiny differences in performance.
For me personally .writelines() takes longer to execute then your first example using .write(). Tested with 110MB text file.
I will not post my machine specs on purpose.
Test .write(): ------copying took 0.934000015259 seconds (dashes for readability)
Test .writelines(): copying took 0.936999797821 seconds
Also tested with small and as large as 1.5GB files with the same results. (writelines always beeing slightly slower, up to 0.5sec difference for 1.5GB file).
That's because of that in first part you have to call the method write for all the lines in each iteration which makes your program take much time to run. But in second code although your waste more memory but it performs better because you have called the writelines() method each 100000 line.
Let see this is source,this is the source of writelines function :
def writelines(self, list_of_data):
"""Write a list (or any iterable) of data bytes to the transport.
The default implementation concatenates the arguments and
calls write() on the result.
"""
if not _PY34:
# In Python 3.3, bytes.join() doesn't handle memoryview.
list_of_data = (
bytes(data) if isinstance(data, memoryview) else data
for data in list_of_data)
self.write(b''.join(list_of_data))
As you can see it joins all the list items and calls the write function one time.
Note that joining the data here takes time but its less than the time for calling the write function for each line.But since you use python 3.4 in ,it writes the lines one at a time rather than joining them so it would be much faster than write in this case :
cStringIO.writelines() now accepts any iterable argument and writes
the lines one at a time rather than joining them and writing once.
Made a parallel change to StringIO.writelines(). Saves memory and
makes suitable for use with generator expressions.
I wrote this code on to observe the event of a keydown motion. The problem appears to be that when this script is run, certain programs will crash this program, spitting out this error message:
TypeError: KeyboardSwitch() missing 8 required positional arguments: 'msg', 'vk_
code', 'scan_code', 'ascii', 'flags', 'time', 'hwnd', and 'win_name'
Some programs observed to crash are: Skype, Sublime Text 2
After a few trials at debugging it, the problem appears to be occurring on the final line but I can't seem to narrow it down. I also don't understand the meaning of KeyboardSwitch() as returned by the compiler...
I have also found that the program would alternately return this error message
Traceback (most recent call last):
File "C:\Python34\lib\site-packages\pyHook\HookManager.py", line 351, in KeyboardSwitch
return func(event)
File "observe.py", line 6, in OnKeyboardEvent
print ('MessageName:',event.MessageName)
TypeError: an integer is required (got type NoneType)
What is the cause and how do I fix this, especially since it only appears for only 1 in 2 keys pressed
import pyHook, pythoncom
def OnKeyboardEvent(event):
# Source: http://code.activestate.com/recipes/553270-using-pyhook-to-block-windows-keys/
print ('MessageName:',event.MessageName)
print ('Message:',event.Message)
print ('Time:',event.Time)
print ('Window:',event.Window)
print ('WindowName:',event.WindowName)
print ('Ascii:', event.Ascii, chr(event.Ascii))
print ('Key:', event.Key)
print ('KeyID:', event.KeyID)
print ('ScanCode:', event.ScanCode)
print ('Extended:', event.Extended)
print ('Injected:', event.Injected)
print ('Alt', event.Alt)
print ('Transition', event.Transition)
print ('---')
hooks_manager = pyHook.HookManager()
hooks_manager.KeyDown = OnKeyboardEvent
hooks_manager.HookKeyboard()
pythoncom.PumpMessages()
P.S. As a beginner, I'm not very familiar with the function of pythoncom and the online definitions appear to be rather vague. An explanation on the function of pythoncom and PumpMessages would be greatly appreciated.
Thanks
I think the problem is that when pyHook gets called back by Windows, the first thing it does is get the window name for the window with focus.
PSTR win_name = NULL;
...
// grab the window name if possible
win_len = GetWindowTextLength(hwnd);
if(win_len > 0) {
win_name = (PSTR) malloc(sizeof(char) * win_len + 1);
GetWindowText(hwnd, win_name, win_len + 1);
}
So I think the problem here is that, even if GetWindowText is not returning wide characters, it can return non-ascii characters from an ANSI codepage. That won't fail, however, until we do this:
// pass the message on to the Python function
arglist = Py_BuildValue("(iiiiiiiz)", wParam, kbd->vkCode, kbd->scanCode, ascii,
kbd->flags, kbd->time, hwnd, win_name);
Here, because of the z in the format string, the data in the win_name variable is being converted to a unicode str with Py_BuildValue assuming it is ASCII. But it's not: and so it can trigger a UnicodeDecodeError. This then causes the arglist to be NULL and therefore your function to be called with no arguments.
So I'm not completely sure on the best fix here. But I just changed both bits of code to use wide characters and unicode instead of ascii, and rebuilt pyHook, and that seemed to fix it. I think it will only work in Python 3 versions, but for Python 2, I think the old pyHook still works anyway.
LPWSTR win_name = NULL;
...
// grab the window name if possible
win_len = GetWindowTextLengthW(hwnd);
if(win_len > 0) {
win_name = (LPWSTR) malloc(sizeof(wchar_t) * win_len + 1);
GetWindowTextW(hwnd, win_name, win_len + 1);
}
and
// pass the message on to the Python function
arglist = Py_BuildValue("(iiiiiiiu)", wParam, kbd->vkCode, kbd->scanCode, ascii,
kbd->flags, kbd->time, hwnd, win_name);
The problem occurs only with windows with non-ascii characters in their title: Skype is one.
If only 1 out of each 2 presses works, it's definetely a problem with missing return value. Try returning either True or False.
The TypeError: KeyboardSwitch() missing 8 required positional arguments: 'msg', 'vk_code', 'scan_code', 'ascii', 'flags', 'time', 'hwnd', and 'win_name' error message indicates that you are trying to call a function named KeyboardSwitch and have not provided values for all of the required arguments.
The error message lists the required arguments as 'msg', 'vk_code', 'scan_code', 'ascii', 'flags', 'time', 'hwnd', and 'win_name'. These are the names of the arguments that the KeyboardSwitch function expects to receive when it is called. You must provide a value for each of these arguments in the correct order when you call the function.
To fix this error, you will need to make sure that you are providing values for all of the required arguments when you call the KeyboardSwitch function. You may also want to check the documentation for the KeyboardSwitch function to make sure that you are using it correctly and understand what each of the required arguments represents.
There's a pyhook for python3: https://github.com/Answeror/pyhook_py3k
This bug has been fixed in this project.
First off, I found the following two similar questions:
Passing Structure to Windows API in python ctypes
ctypes and passing a by reference to a function
The first does not have an accepted answer, and I do not think that I'm doing anything in separate processes. The second simply points out pointer() and byref(), both of which I have tried using to no avail.
Now, on to my question:
I am trying to call the function WERReportCreate with my own pReportInformation (which is a pointer to a struct whose first data value is its own size). This fails in various ways, depending on how I go about it, but I'm not sure how to do it correctly. It is complicated by the fact that one of the requirements is that the structure know it's own size, which I'm not sure how to programatically determine (though if that was the only issue, I think I would have guessed the right value by now). The relevant information from the WER API is shown below:
HRESULT WINAPI WerReportCreate(
__in PCWSTR pwzEventType,
__in WER_REPORT_TYPE repType,
__in_opt PWER_REPORT_INFORMATION pReportInformation,
__out HREPORT *phReportHandle
);
(full info at http://msdn.microsoft.com/en-us/library/windows/desktop/bb513625%28v=vs.85%29.aspx)
typedef struct _WER_REPORT_INFORMATION {
DWORD dwSize;
HANDLE hProcess;
WCHAR wzConsentKey[64];
WCHAR wzFriendlyEventName[128];
WCHAR wzApplicationName[128];
WCHAR wzApplicationPath[MAX_PATH];
WCHAR wzDescription[512];
HWND hwndParent;
} WER_REPORT_INFORMATION, *PWER_REPORT_INFORMATION;
(full info at http://msdn.microsoft.com/en-us/library/windows/desktop/bb513637%28v=vs.85%29.aspx)
This is the code that I have tried:
import ctypes
import ctypes.wintypes
class ReportInfo( ctypes.Structure):
_fields_ = [ ("dwSize", ctypes.wintypes.DWORD),
("hProcess", ctypes.wintypes.HANDLE),
("wzConsentKey", ctypes.wintypes.WCHAR * 64),
("wzFriendlyEventName", ctypes.wintypes.WCHAR * 128),
("wzApplicationName", ctypes.wintypes.WCHAR * 128),
("wzApplicationPath", ctypes.wintypes.WCHAR * ctypes.wintypes.MAX_PATH),
("wzDescription", ctypes.wintypes.WCHAR * 512),
("hwndParent", ctypes.wintypes.HWND) ]
def genReportInfo():
import os
size = 32 #Complete SWAG, have tried many values
process = os.getpid()
parentwindow = ctypes.windll.user32.GetParent(process)
werreportinfopointer = ctypes.POINTER(ReportInfo)
p_werinfo = werreportinfopointer()
p_werinfo = ReportInfo(size, process, "consentkey", "friendlyeventname", "appname", "apppath", "desc", parentwindow)
return p_werinfo
if __name__ == '__main__':
reporthandle = ctypes.wintypes.HANDLE()
res = ctypes.wintypes.HRESULT()
### First pass NULL in as optional parameter to get default behavior ###
p_werinfo = None
res = ctypes.windll.wer.WerReportCreate(u'pwzEventType', 2, p_werinfo, ctypes.byref(reporthandle))
print "Return Code",res,"\nHandle",reporthandle #Return Code 0, reporthandle is correct (verified by submitting report in a different test)
p_werinfo = genReportInfo() # Create our own struct
### Try Again Using Our Own Struct (via 'byref') ###
res = ctypes.windll.wer.WerReportCreate(u'pwzEventType', 2, ctypes.byref(p_werinfo), ctypes.byref(reporthandle))
print "Return Code",res,"\nHandle",reporthandle #Return Code Nonzero, reporthandle is None
### Try Again Using Our Own Struct (directly) ###
res = ctypes.windll.wer.WerReportCreate(u'pwzEventType', 2, p_werinfo, ctypes.byref(reporthandle))
print "Return Code",res,"\nHandle",reporthandle #Exception Occurs, Execution halts
And this is the output I get:
Return Code 0
Handle c_void_p(26085328)
Return Code -2147024809
Handle c_void_p(None)
Traceback (most recent call last):
File "test.py", line 40, in <module>
res = ctypes.windll.wer.WerReportCreate(u'pwzEventType', s.byref(reporthandle))
WindowsError: exception: access violation writing 0x0000087C
The fact that it works when I pass in a null, but not when I actually pass my (reference to my?) structure suggests to me I have one of three problems: I am not creating the structure correctly (I'm not certain the wzConsentKey is correctly defined), or I am not correctly figuring out the struct's size (I'm actually using struct.calcsize with various options to get initial guesses, and adding and subtracting 1 randomly), or I am not correctly passing the (reference to the?) structure.
Here is where I've hit a deadend. Any help would be appreciated (as well as suggestions for how to improve the clarity, formatting, or quality of my question; this is my first post).
The general short answer to the posted question is: Be sure you are putting the correct information into the structure, other than that, the provided code is a good example of creating and passing a structure. Here's what solved my problem specifically:
There were two issues with the provided code: First, as Mark Tolonen pointed out, I was passing an incorrect size. Using ctypes.sizeof(ReportInfo) solved that problem. The second issue was that I was using a process ID where a process handle was required. Using OpenProcess to obtain a valid process handle in place of my "process" argument solved the second problem.
To anyone debugging similar issues in the future, printing the HRESULTS out as hex numbers rather than integers to make better sense of the return codes:
print "Return Code %08x" % (res & 0xffffffff)
This, in my case, produced the following results:
Return Code 80070057
for my original error, and
Return Code 80070006
for the second error. Using the information at http://msdn.microsoft.com/en-us/library/bb446131.aspx , I saw the first half was metadata, the second half was my actual error code.
After converting the Error Code part of the Hex number back to decimal, I used http://msdn.microsoft.com/en-us/library/bb202810.aspx to determine that
Error Code 87 (57 in hex) meant "Parameter Incorrect" (size was wrong)
and
Error Code 6 (6 in hex) meant "The Handle is Invalid" (I was passing in a process ID).
You can use ctypes.sizeof(ReportInfo) to obtains the size in bytes of the structure.
Simply create the ReportInfo instance with genReportInfo. You don't need a pointer at this point:
def genReportInfo():
import os
size = ctypes.sizeof(ReportInfo)
process = os.getpid()
parentwindow = ctypes.windll.user32.GetParent(process)
return ReportInfo(size, process, "consentkey", "friendlyeventname", "appname", "apppath", "desc", parentwindow)
Call WerReportCreate like this. byref passes the pointer to the ReportInfo instance.
werinfo = genReportInfo()
res = ctypes.windll.wer.WerReportCreate(u'pwzEventType', 2, ctypes.byref(werinfo), ctypes.byref(reporthandle))
I think that will work for you. I don't have wer.dll so I can't test it.
I'm finding it very difficult to develop with PyClips, because it appears to replace useful error messages thrown by Clips with a generic "syntax error" message. This makes debugging very laborious and practically impossible on large codebases when using PyClips.
Consider the following example. I wrote a very large expression, which contained the multiplication operator, but I mistakenly forgot to add the second argument. Instead of simply telling I was missing an argument, PyClips told me there was a syntax error. What should have taken me 1 second to correct, took me 5 minutes to correct as I hunted through my large expression, looking for the mistake.
Here's a condensed version:
In Clips, with a useful error message:
clips
CLIPS> (defrule myrule "" (myfact 123) => (bind ?prob (* (min 1 2))))
[ARGACCES4] Function * expected at least 2 argument(s)
ERROR:
(defrule MAIN::myrule ""
(myfact 123)
=>
(bind ?prob (* (min 1 2))
And in PyClips, with an unuseful error message:
python
>>> import clips
>>> clips.BuildRule('myrule','(myfact 123)','(bind ?prob (* (min 1 2)))','')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.6/dist-packages/clips/_clips_wrap.py", line 2839, in BuildRule
_c.build(construct)
_clips.ClipsError: C08: syntax error, or unable to parse expression
How can I get PyClips to give me the real error thrown by Clips?
Catch the ClipsError, then read ErrorStream for the details. For example:
engine = clips.Environment()
engine.Reset()
engine.Clear()
try:
engine.Load(os.path.abspath(rule_file))
except clips.ClipsError:
logging.error(clips.ErrorStream.Read())