Original: https://akehrer.github.io/nim/2015/01/24/connecting-nim-to-python.html
At the end of the previous article, I asked about the code for the Nim connection to the Python interface, and I was able to do some work after some experimentation. So, let's take a look.
Compiling a library
The first thing we want to talk about is the Nim compiler. In most cases you Compile the Nim code into an executable file to run this command.
Nim C Projectfile.nim
But to write the code in a library, we'll need to look at the compiler's documentation. There are many options here, but --app is of interest to us, and in our case we will use the -app command to create a shared library. ( .dll
in Windows, in .so
Linux, and in .dylib
OSX).
Nim C--app:lib Projectfile.nim
Run the above command to create a projectfile.dll or libprojectfile.so file in the directory under the . Nim file . It was fantastic, but it didn't give us what we wanted. This library has been created, but there are no functional functions that we have exposed. is not very useful.
The
exportc
&
dynlib
Pragmas
Nim has a special way of accessing pragmas, which gets additional information from the compiler when it parses a particular block of code. We have seen their use in previous articles, remember that the following code loads the isNaN function from MATH.H.
Import Mathproc Cisnan (x:float): int {. IMPORTC: "isNaN", Header: "<math.h>".} # # returns Non-zero if X is not a number
In the above code {. and.} between include IMPORTC statement, which is to tell the compiler to Nim in the Cisnan the process needs to be used math.h in the isNaN function.
So, if Nim has a way to import C Code, then there should be a way to export the code to C ; it's EXPORTC . . With EXPORTC can tell the compiler to show our function to the outside. Add the dynlib statement to ensure that we can access our process from the Access library.
Let's start with a few simple examples:
Proc summer* (x, y:float): float {. EXPORTC, dynlib.} = result = x + y
Save it as Test1.nim, Run the command under Windows Nim C--app:lib Test1.nim will get a test1.dll file. Now let's see if we can use it. ( libtest1.so files are generated under Linux )
Python
ctypes
In order to use the compiled library, we will use the cTYPES module in Python , which is already part of the standard library since the 2.5 version. It allows us to use C -based code that is compiled in the library to convert between the Python type and the C type. The code here can access our summer function.
From cTYPES import *def main (): test_lib = Cdll (' test1 ') # Function parameter Types test_ Lib.summer.argtypes = [C_float, c_float] # Function return types test_lib.summer.restype = c_float sum_res = Test_lib.summer (1.0, 3.0) print (' The sum of 1.0 and 3.0 is:%f '%sum_res) if __name__ = = ' __main__ ' : Main ()
We can see that after the library is loaded, the return type of the parameter and function is set, and then two arguments are passed to call the function. Let's see what's going on.
C:\workspaces\nim-tests>python test1.pythe Sum of 1.0 and 3.0 is:32.000008
Well, that's not true. It seems that we may not be using the correct parameters and return types. Let's compareNimof thefloatType andPython ctypesof thec_floattype. According toNimManuals,floatType is set to the fastest floating-point of the processor. Python ctypesmanual saysc_floatis withCin thefloatThe same type. is because I use +-bit version ofNimrun this code and2.7version ofPythonin the -bitWindowsMachine isNimthe compiler makes itsfloatto beDouble?
From cTYPES import *def main (): test_lib = Cdll (' test1 ') # Function parameter Types test_ Lib.summer.argtypes = [C_double, c_double] # Function return types test_lib.summer.restype = c_double Sum_res = Test_lib.summer (1.0, 3.0) print (' The sum of 1.0 and 3.0 is:%f '%sum_res) if __name__ = = ' __main__ ': mai N ()
C:\workspaces\nim-tests>python test1.pythe Sum of 1.0 and 3.0 is:4.000000
It seems to have been solved. When we know that we will use EXPORTC or create a shared library,Nim has a type that lets us add more constraints, reducing these types of confusion (e.g. cfloat,cint) .
openArray
Arguments & the header file
Now let's try something more complicated, using the median function in the statistics module written in the previous two articles .
Nim Code:
Proc median* (X:openarray[float]): float {. EXPORTC, dynlib.} = # computes the median of the elements in ' x '. # # If ' x ' is empty and NaN is returned. If X.len = = 0: return NAN var sx = @x # Convert to a sequence since sort () won ' t take an openarray sx.sort (sys Tem.cmp[float]) if Sx.len mod 2 = = 0: var n1 = sx[(sx.len-1) Div 2] var n2 = Sx[sx.len Div 2] result = ( N1 + N2)/2.0 Else: result = sx[(sx.len-1) Div 2]
Python Code:
1 from ctypes Import * 2 3 def Main (): 4 test_lib = Cdll (' test1 ') 5 6 # Function parameter types 7 tes T_lib.summer.argtypes = [C_double, c_double] 8 test_lib.median.argtypes = [POINTER (c_double), C_int] 9 # Function return types11 test_lib.summer.restype = c_double12 test_lib.median.restype = c_double13 14 # Calc Some numbers15 nums = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]16 Nums_arr = (c_double * len (nums)) ( ) i,v in Enumerate (nums): nums_arr[i] = c_double (v) sum_res = Test_lib.summer (1.0, 3.0) print (' The sum of 1.0 and 3.0 is:%f '%sum_res) med_res = Test_lib.median (Nums_arr, C_int (Len (nums_a RR)), print (' The median of%s is:%f '% (Nums, med_res)) "If __name__ = = ' __main__ ': Main ()
There are some interesting things happening here that allow us to invoke MEDIAN&NBSP; process. In this nim In the code you can see that it just needs a parameter, a float openarray OPENARRAYS&NBSP; makes it possible to transfer arrays of different sizes to the process. But how to convert it into c What about the library? (You may be from PYTHON&NBSP; nim additional parameters of the compiler.
Nim C--app:lib--header Test1.nim
This --header option will generate a C header file in the folder Nimcache that the module is compiled in . If we see the header file will look like this:
1/* Generated by Nim Compiler v0.10.2 * /2/* (c) Andreas Rumpf */3 */The Generated code is subject to the Ori Ginal License. */4 */Compiled for:windows, i386, GCC */5/Command for C compiler:6 gcc.exe-c- w -ic:\nim\lib-o C:\wo RKSPACES\NIM-TESTS\NIMCACHE\TEST1.O C:\workspaces\nim-tests\nimcache\test1.h */7 #ifndef __test1__ 8 #define __test1_ _ 9 #define Nim_intbits 3210 #include "nimbase.h" one N_noconv (void, signalhandler) (int sig), N_nimcall (NI, Getrefcount) ( void* p); N_lib_import n_cdecl (NF, median) (nf* x, NI xLen0), N_lib_import n_cdecl (NF, Summer) (NF X, NF y), N_lib_imp ORT n_cdecl (void, nimmain) (void); #endif/* __test1__ */
It is important that the first -and the -OK, the process of our two outputs is aroused. Can seeSummerIt's about two .NFtype of parameters, we can assume that theNimin thefloattype. on the otherMedianIt's not like we 'reNima parameter defined in the procedure, but two, one is a pointer to theNFpointer, and the other isNimthe integral type inNI. There is a hint about an integral type that is the length of a value. So thisOpenarrayparameters have been converted to theCin the standard way of passing an array, a pointer to the array and the length of an array.
In python code you can see that we set the correct parameters ([POINTER (c_double), c_int) and return type (c_double) and we map a python list to a double array of C in rows 15 through 18. Then when we call this function on line 23, make sure the length of the list is converted to a c_int. Let's check the results.
C:\workspaces\nim-tests>python test1.pythe Sum of 1.0 and 3.0 is:4.000000the median of [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] is:4.500000
This looks good, it's enough for this article. In future articles we will see more types of interfaces, like strings, tuples, and custom objects.
Reference
Nim Compiler User Guide
Nim Manual
Python cTYPES
Python ctypes Tutorial
(Thanks to DOM96 for corrections.)
Nim connects to Python