|ePolyglot: Examination and development of multilanguage programming using Eiffel, Python, and Haskell|
|Prev||Chapter 6. Wrapping the Python/C API||Next|
While the SWIG-generated PYTHON_CLIENT_WRAPPER class did expose much of the Python/C API, there were a number of slightly higher-level features we desired, mostly having to do with minor error checking and creation of Python primitive objects. While this could have been done with a mixin class, we decided that the easiest way to deal with it was to create a descendent of PYTHON_CLIENT_WRAPPER named PYTHON_CLIENT, and add some functionality to it.
Error checking. Most of the Python/C API functions, like many C functions, return a success/error code as a return value. In C, programmers are free to ignore the error code as they wish--a debatable decision, but one which is impossible in Eiffel. If the Eiffel compiler sees that you are using a function with a return value, some variable had better be there to accept the return value, as failure to do so generates a compiler error!
While this is not a bad thing, it did mean that client code was creating a lot of temporary variables. Since only one Python function was likely to be in use at a time, it seemed a better design choice to include helper functions around both the integer results and the pointer results which could be generated by Python functions:
Example 6-6. Python return value helpers in PYTHON_CLIENT
last_python_integer_result : INTEGER --stores the last integer result of a call to the Python API ----note that this will be replicated for all descendants last_python_integer_result_ok : BOOLEAN is --did last C call to Python API return an error condition? do Result := ( last_python_integer_result /= -1 ) end last_python_pointer_result : POINTER --stores the last pointer result of a call to the Python API --note that this will be replicated for all descendants last_python_pointer_result_ok : BOOLEAN is --did last pointer call to Python API return an error condition? do Result := ( last_python_pointer_result.is_not_null ) end
Client code, then, could use the functionality like so:
Example 6-7. Use of the Python return value helpers: feature PYTHON_OBJECT.del_attr_string
del_attr_string( attribute_name : STRING ) is do last_python_integer_result := py_object_del_attr_string( python_object_pointer, attribute_name ) ensure last_python_integer_result_ok end
This is a fairly naive way to handle erroneous return values, but it does make program flow very straightforward and eliminates the use of temporaries in every single function which uses the Python/C API. We considered moving all such 'error checking' into the PYTHON_CLIENT class in the form of an invariant, but decided that was too restrictive--it prevented clients from being able to accept erroneous return values and act upon them.
Other functionality was later added to the PYTHON_CLIENT class--a class-wide PYTHON_OBJECT_FACTORY class to handle creation of primitive Python objects and handle data conversion, some helpers to get the python global and local definitions, a wrapper around py_initialize (which may be expanded to fill in arguments), a wrapper to easily create Python primitives via the interpreter, and a wrapper which creates a SWIG-compatible pointer string from an Eiffel object. Many of these will be discussed further in the documentation writeup.