|
5 | 5 | ----------
|
6 | 6 | 问题
|
7 | 7 | ----------
|
8 |
| -You have a small number of C functions that have been compiled into a shared library |
9 |
| -or DLL. You would like to call these functions purely from Python without having to |
10 |
| -write additional C code or using a third-party extension tool. |
| 8 | +你有一些C函数已经被编译到共享库或DLL中。你希望可以使用纯Python代码调用这些函数, |
| 9 | +而不用编写额外的C代码或使用第三方扩展工具。 |
11 | 10 |
|
12 | 11 | |
|
13 | 12 |
|
14 | 13 | ----------
|
15 | 14 | 解决方案
|
16 | 15 | ----------
|
17 |
| -For small problems involving C code, it is often easy enough to use the ctypes module |
18 |
| -that is part of Python’s standard library. In order to use ctypes, you must first make |
19 |
| -sure the C code you want to access has been compiled into a shared library that is |
20 |
| -compatible with the Python interpreter (e.g., same architecture, word size, compiler, |
21 |
| -etc.). For the purposes of this recipe, assume that a shared library, libsample.so, has |
22 |
| -been created and that it contains nothing more than the code shown in the chapter |
23 |
| -introduction. Further assume that the libsample.so file has been placed in the same |
24 |
| -directory as the sample.py file shown next. |
25 |
| -To access the resulting library, you make a Python module that wraps around it, such |
26 |
| -as the following: |
27 |
| -# sample.py |
28 |
| -import ctypes |
29 |
| -import os |
30 |
| - |
31 |
| -# Try to locate the .so file in the same directory as this file |
32 |
| -_file = 'libsample.so' |
33 |
| -_path = os.path.join(*(os.path.split(__file__)[:-1] + (_file,))) |
34 |
| -_mod = ctypes.cdll.LoadLibrary(_path) |
35 |
| -
|
36 |
| -# int gcd(int, int) |
37 |
| -gcd = _mod.gcd |
38 |
| -gcd.argtypes = (ctypes.c_int, ctypes.c_int) |
39 |
| -gcd.restype = ctypes.c_int |
40 |
| - |
41 |
| -# int in_mandel(double, double, int) |
42 |
| -in_mandel = _mod.in_mandel |
43 |
| -in_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int) |
44 |
| -in_mandel.restype = ctypes.c_int |
45 |
| - |
46 |
| -# int divide(int, int, int *) |
47 |
| -_divide = _mod.divide |
48 |
| -_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int)) |
49 |
| -_divide.restype = ctypes.c_int |
50 |
| -
|
51 |
| -def divide(x, y): |
52 |
| - rem = ctypes.c_int() |
53 |
| - quot = _divide(x, y, rem) |
54 |
| - |
55 |
| - return quot,rem.value |
56 |
| - |
57 |
| -# void avg(double *, int n) |
58 |
| -# Define a special type for the 'double *' argument |
59 |
| -class DoubleArrayType: |
60 |
| - def from_param(self, param): |
61 |
| - typename = type(param).__name__ |
62 |
| - if hasattr(self, 'from_' + typename): |
63 |
| - return getattr(self, 'from_' + typename)(param) |
64 |
| - elif isinstance(param, ctypes.Array): |
65 |
| - return param |
66 |
| - else: |
67 |
| - raise TypeError("Can't convert %s" % typename) |
68 |
| -
|
69 |
| - # Cast from array.array objects |
70 |
| - def from_array(self, param): |
71 |
| - if param.typecode != 'd': |
72 |
| - raise TypeError('must be an array of doubles') |
73 |
| - ptr, _ = param.buffer_info() |
74 |
| - return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double)) |
75 |
| - |
76 |
| - # Cast from lists/tuples |
77 |
| - def from_list(self, param): |
78 |
| - val = ((ctypes.c_double)*len(param))(*param) |
79 |
| - return val |
80 |
| -
|
81 |
| - from_tuple = from_list |
82 |
| - |
83 |
| - # Cast from a numpy array |
84 |
| - def from_ndarray(self, param): |
85 |
| - return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) |
86 |
| - |
87 |
| -DoubleArray = DoubleArrayType() |
88 |
| -_avg = _mod.avg |
89 |
| -_avg.argtypes = (DoubleArray, ctypes.c_int) |
90 |
| -_avg.restype = ctypes.c_double |
91 |
| - |
92 |
| -def avg(values): |
93 |
| - return _avg(values, len(values)) |
94 |
| - |
95 |
| -# struct Point { } |
96 |
| -class Point(ctypes.Structure): |
97 |
| - _fields_ = [('x', ctypes.c_double), |
98 |
| - ('y', ctypes.c_double)] |
99 |
| - |
100 |
| -# double distance(Point *, Point *) |
101 |
| -distance = _mod.distance |
102 |
| -distance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point)) |
103 |
| -distance.restype = ctypes.c_double |
| 16 | +对于需要调用C代码的一些小的问题,通常使用Python标准库中的 ``ctypes`` 模块就足够了。 |
| 17 | +要使用 ``ctypes`` ,你首先要确保你要访问的C代码已经被编译到和Python解释器兼容 |
| 18 | +(同样的架构、字大小、编译器等)的某个共享库中了。 |
| 19 | +为了进行本节的演示,假设你有一个共享库名字叫 ``libsample.so`` ,里面的内容就是15章介绍部分那样。 |
| 20 | +另外还假设这个 ``libsample.so`` 文件被放置到位于 ``sample.py`` 文件相同的目录中了。 |
| 21 | + |
| 22 | +要访问这个函数库,你要先构建一个包装它的Python模块,如下这样: |
| 23 | + |
| 24 | +.. code-block:: python |
| 25 | +
|
| 26 | + # sample.py |
| 27 | + import ctypes |
| 28 | + import os |
| 29 | +
|
| 30 | + # Try to locate the .so file in the same directory as this file |
| 31 | + _file = 'libsample.so' |
| 32 | + _path = os.path.join(*(os.path.split(__file__)[:-1] + (_file,))) |
| 33 | + _mod = ctypes.cdll.LoadLibrary(_path) |
| 34 | +
|
| 35 | + # int gcd(int, int) |
| 36 | + gcd = _mod.gcd |
| 37 | + gcd.argtypes = (ctypes.c_int, ctypes.c_int) |
| 38 | + gcd.restype = ctypes.c_int |
| 39 | +
|
| 40 | + # int in_mandel(double, double, int) |
| 41 | + in_mandel = _mod.in_mandel |
| 42 | + in_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int) |
| 43 | + in_mandel.restype = ctypes.c_int |
| 44 | +
|
| 45 | + # int divide(int, int, int *) |
| 46 | + _divide = _mod.divide |
| 47 | + _divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int)) |
| 48 | + _divide.restype = ctypes.c_int |
| 49 | +
|
| 50 | + def divide(x, y): |
| 51 | + rem = ctypes.c_int() |
| 52 | + quot = _divide(x, y, rem) |
| 53 | +
|
| 54 | + return quot,rem.value |
| 55 | +
|
| 56 | + # void avg(double *, int n) |
| 57 | + # Define a special type for the 'double *' argument |
| 58 | + class DoubleArrayType: |
| 59 | + def from_param(self, param): |
| 60 | + typename = type(param).__name__ |
| 61 | + if hasattr(self, 'from_' + typename): |
| 62 | + return getattr(self, 'from_' + typename)(param) |
| 63 | + elif isinstance(param, ctypes.Array): |
| 64 | + return param |
| 65 | + else: |
| 66 | + raise TypeError("Can't convert %s" % typename) |
| 67 | +
|
| 68 | + # Cast from array.array objects |
| 69 | + def from_array(self, param): |
| 70 | + if param.typecode != 'd': |
| 71 | + raise TypeError('must be an array of doubles') |
| 72 | + ptr, _ = param.buffer_info() |
| 73 | + return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double)) |
| 74 | +
|
| 75 | + # Cast from lists/tuples |
| 76 | + def from_list(self, param): |
| 77 | + val = ((ctypes.c_double)*len(param))(*param) |
| 78 | + return val |
| 79 | +
|
| 80 | + from_tuple = from_list |
| 81 | +
|
| 82 | + # Cast from a numpy array |
| 83 | + def from_ndarray(self, param): |
| 84 | + return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) |
| 85 | +
|
| 86 | + DoubleArray = DoubleArrayType() |
| 87 | + _avg = _mod.avg |
| 88 | + _avg.argtypes = (DoubleArray, ctypes.c_int) |
| 89 | + _avg.restype = ctypes.c_double |
| 90 | +
|
| 91 | + def avg(values): |
| 92 | + return _avg(values, len(values)) |
| 93 | +
|
| 94 | + # struct Point { } |
| 95 | + class Point(ctypes.Structure): |
| 96 | + _fields_ = [('x', ctypes.c_double), |
| 97 | + ('y', ctypes.c_double)] |
| 98 | +
|
| 99 | + # double distance(Point *, Point *) |
| 100 | + distance = _mod.distance |
| 101 | + distance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point)) |
| 102 | + distance.restype = ctypes.c_double |
104 | 103 |
|
105 | 104 | If all goes well, you should be able to load the module and use the resulting C functions.
|
106 | 105 | For example:
|
107 |
| - |
108 |
| ->>> import sample |
109 |
| ->>> sample.gcd(35,42) |
110 |
| -7 |
111 |
| ->>> sample.in_mandel(0,0,500) |
112 |
| -1 |
113 |
| ->>> sample.in_mandel(2.0,1.0,500) |
114 |
| -0 |
115 |
| ->>> sample.divide(42,8) |
116 |
| -(5, 2) |
117 |
| ->>> sample.avg([1,2,3]) |
118 |
| -2.0 |
119 |
| ->>> p1 = sample.Point(1,2) |
120 |
| ->>> p2 = sample.Point(4,5) |
121 |
| ->>> sample.distance(p1,p2) |
122 |
| -4.242640687119285 |
123 |
| ->>> |
| 106 | +如果一切正常,你就可以加载并使用里面定义的C函数了。例如: |
| 107 | + |
| 108 | +:: |
| 109 | + |
| 110 | + >>> import sample |
| 111 | + >>> sample.gcd(35,42) |
| 112 | + 7 |
| 113 | + >>> sample.in_mandel(0,0,500) |
| 114 | + 1 |
| 115 | + >>> sample.in_mandel(2.0,1.0,500) |
| 116 | + 0 |
| 117 | + >>> sample.divide(42,8) |
| 118 | + (5, 2) |
| 119 | + >>> sample.avg([1,2,3]) |
| 120 | + 2.0 |
| 121 | + >>> p1 = sample.Point(1,2) |
| 122 | + >>> p2 = sample.Point(4,5) |
| 123 | + >>> sample.distance(p1,p2) |
| 124 | + 4.242640687119285 |
| 125 | + >>> |
124 | 126 |
|
125 | 127 | |
|
126 | 128 |
|
127 | 129 | ----------
|
128 | 130 | 讨论
|
129 | 131 | ----------
|
130 |
| -There are several aspects of this recipe that warrant some discussion. The first issue |
131 |
| -concerns the overall packaging of C and Python code together. If you are using ctypes |
132 |
| -to access C code that you have compiled yourself, you will need to make sure that the |
133 |
| -shared library gets placed in a location where the sample.py module can find it. One |
134 |
| -possibility is to put the resulting .so file in the same directory as the supporting Python |
135 |
| -code. This is what’s shown at the first part of this recipe—sample.py looks at the __file__ |
136 |
| -variable to see where it has been installed, and then constructs a path that points to a |
137 |
| -libsample.so file in the same directory. |
138 |
| -If the C library is going to be installed elsewhere, then you’ll have to adjust the path |
139 |
| -accordingly. If the C library is installed as a standard library on your machine, you might |
140 |
| -be able to use the ctypes.util.find_library() function. For example: |
141 |
| - |
142 |
| ->>> from ctypes.util import find_library |
143 |
| ->>> find_library('m') |
144 |
| -'/usr/lib/libm.dylib' |
145 |
| ->>> find_library('pthread') |
146 |
| -'/usr/lib/libpthread.dylib' |
147 |
| ->>> find_library('sample') |
148 |
| -'/usr/local/lib/libsample.so' |
149 |
| ->>> |
| 132 | +本小节有很多值得我们详细讨论的地方。 |
| 133 | +首先是对于C和Python代码一起打包的问题,如果你在使用 ``ctypes`` 来访问编译后的C代码, |
| 134 | +那么需要确保这个共享库放在 ``sample.py`` 模块同一个地方。 |
| 135 | +一种可能是将生成的 ``.so`` 文件放置在要使用它的Python代码同一个目录下。 |
| 136 | +我们在 ``recipe—sample.py`` 中使用 ``__file__`` 变量来查看它被安装的位置, |
| 137 | +然后构造一个指向同一个目录中的 ``libsample.so`` 文件的路径。 |
| 138 | + |
| 139 | +如果C函数库被安装到其他地方,那么你就要修改相应的路径。 |
| 140 | +如果C函数库在你机器上被安装为一个标准库了, |
| 141 | +那么可以使用 ``ctypes.util.find_library()`` 函数来查找: |
| 142 | + |
| 143 | +:: |
| 144 | + |
| 145 | + >>> from ctypes.util import find_library |
| 146 | + >>> find_library('m') |
| 147 | + '/usr/lib/libm.dylib' |
| 148 | + >>> find_library('pthread') |
| 149 | + '/usr/lib/libpthread.dylib' |
| 150 | + >>> find_library('sample') |
| 151 | + '/usr/local/lib/libsample.so' |
| 152 | + >>> |
| 153 | + |
| 154 | +一旦你知道了C函数库的位置,那么就可以像下面这样使用 ``ctypes.cdll.LoadLibrary()`` 来加载它, |
| 155 | +其中 ``_path`` 是标准库的全路径: |
150 | 156 |
|
151 |
| -Again, ctypes won’t work at all if it can’t locate the library with the C code. Thus, you’ll |
152 |
| -need to spend a few minutes thinking about how you want to install things. |
153 |
| -Once you know where the C library is located, you use ctypes.cdll.LoadLibrary() |
154 |
| -to load it. The following statement in the solution does this where _path is the full |
155 |
| -pathname to the shared library: |
156 | 157 |
|
157 | 158 | _mod = ctypes.cdll.LoadLibrary(_path)
|
158 | 159 |
|
|
0 commit comments