Inspirel banner

Ada-Python Demo

This article is a continuation (or rather completion) of the earlier article Ada-Python Binding, which presents the very basics of extending the Python interpreter with modules implemented in Ada.

This article shows the other side of the story - that is, extending the Ada program with plugins or scripts written in Python. In this case the Ada program has to include (it is called embedding here) a real Python interpreter, which will be asked to execute dynamically provided scripts.

These two approaches have different motivations:

Even though both problems relate to the integration between two languages, there are some very important differences in what has to be done. Namely, in the case of modules implemented in Ada, the set of Ada subprograms has to be exported and recognized by the main Python program - these subprograms are provided by the loadable module and are directly related to the module's functionality. That is, if it is an image-processing utility, the exported functions will have something to do with image processing and this is what the Python script will deal with. Things are different with embedded interpreters (our second case), as the language integration happens between the main program and the interpreter and this is more or less similar independently on what is the actual script doing. That is, the functions that the Ada program has to access are related to Python's object model, package importing, type conversion, etc. - and this will be similar in all applications that embed Python interpreters. This means that it makes sense to think about a general-purpose package for embedding Python interpreters in Ada applications and use this single package independently on the actual problem domain.

This article is accompanied by a demo program (download it from here) that contains such general-purpose package with the following specification skeleton:

package Python is

   Interpreter_Error : exception;
   
   type Module is private;

   procedure Initialize (Program_Name : in String := "");
   procedure Finalize;
    
   procedure Execute_String (Script : in String);

   function Import_File (File_Name : in String) return Module;
   procedure Close_Module (M : in Module);
   
   --  Overloads for "all" needed combinations of parameters and return types:
   
   procedure Call (M : in Module; Function_Name : in String);
   function  Call (M : in Module; Function_Name : in String; A : in Integer; B : Integer) return Integer;
   --  ...
   
private
   --  not shown
end Python;

The simple package above allows to embed the Python interpreter in a general way, no matter what the actual scripts are supposed to do. The subprograms should be easy to understand:

The most interesting are, however, the Call subprograms as they encapsulate the machinery that is necessary to invoke the Python function by name. In the simplest case the invocation looks like this (assuming the module was already loaded from Python file):

   Python.Call (M, "do_something");

The above line is enough to call do_something Python function that does not expect any parameters and that returns nothing.

The demo program refers also to several Python functions that take two integers and return an integer (they perform four basic calculations on the given arguments) and their use is equally simple:

   Result := Python.Call (M, "add", A, B);

where Result, A and B are Integer variables in Ada.

This approach aims at very close integration between Ada and Python layers, so that the application code does not have to deal with dynamic discovery of types or data structures, but has the following drawback: the set of Call subprograms has to be extended to cover all intended function signatures (that is, all expected combinations of parameter and return types). It might look like a massive work, but in fact it is not - the set of such subprograms will rather quickly converge to what is actually needed in the particular application domain.

Several tips can be of use to those programmers who will attempt to extend the example Python package:

The demo program (download it from here) that accompanies this article has two parts, in separate directories:

Follow the instructions given in README files in each part.

The demo program was prepared with Python 2.x in mind. Unfortunately, the Python 3.x branch is not compatible with 2.x at the level of interpreter API and porting the demo program to Python 3.x would require some tweaks. Another porting issue can be related to the particular location where the Python library files are installed - on a Linux system with Python 2.7 installed, the library file in question is libpython2.7.so and usually it is installed where the GNAT project builder can find it. Otherwise (and on other systems, for example on Windows) the GNAT linker options will have to be changed appropriately.

In any case - feel free to contact us if you would like to use the principles from this demo program and you need help.