## 問題
You have a small number of C functions that have been compiled into a shared libraryor DLL. You would like to call these functions purely from Python without having towrite additional C code or using a third-party extension tool.
## 解決方案
For small problems involving C code, it is often easy enough to use the ctypes modulethat is part of Python’s standard library. In order to use ctypes, you must first makesure the C code you want to access has been compiled into a shared library that iscompatible with the Python interpreter (e.g., same architecture, word size, compiler,etc.). For the purposes of this recipe, assume that a shared library, libsample.so, hasbeen created and that it contains nothing more than the code shown in the chapterintroduction. Further assume that the libsample.so file has been placed in the samedirectory as the sample.py file shown next.To access the resulting library, you make a Python module that wraps around it, suchas the following:# sample.pyimport ctypesimport os
# Try to locate the .so file in the same directory as this file_file = ‘libsample.so'_path = os.path.join([*](#)(os.path.split(__file__)[:-1] + (_file,)))_mod = ctypes.cdll.LoadLibrary(_path)
# int gcd(int, int)gcd = _mod.gcdgcd.argtypes = (ctypes.c_int, ctypes.c_int)gcd.restype = ctypes.c_int
# int in_mandel(double, double, int)in_mandel = _mod.in_mandelin_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int)in_mandel.restype = ctypes.c_int
# int divide(int, int, int [*](#))_divide = _mod.divide_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))_divide.restype = ctypes.c_int
def divide(x, y):
rem = ctypes.c_int()quot = _divide(x, y, rem)
return quot,rem.value
# void avg(double [*](#), int n)# Define a special type for the ‘double [*](#)‘ argumentclass DoubleArrayType:
> def from_param(self, param):> typename = type(param).__name__if hasattr(self, ‘[from_](#)‘ + typename):
> > return getattr(self, ‘[from_](#)‘ + typename)(param)
elif isinstance(param, ctypes.Array):return paramelse:raise TypeError(“Can't convert %s” % typename)> # Cast from array.array objectsdef from_array(self, param):
> > if param.typecode != ‘d':raise TypeError(‘must be an array of doubles')> > ptr, _ = param.buffer_info()return ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))
> # Cast from lists/tuplesdef from_list(self, param):
> > val = ((ctypes.c_double)*len(param))([*](#)param)return val
> from_tuple = from_list
> # Cast from a numpy arraydef from_ndarray(self, param):
> > return param.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
DoubleArray = DoubleArrayType()_avg = _mod.avg_avg.argtypes = (DoubleArray, ctypes.c_int)_avg.restype = ctypes.c_double
def avg(values):return _avg(values, len(values))
# struct Point { }class Point(ctypes.Structure):
> _fields_ = [(‘x', ctypes.c_double),(‘y', ctypes.c_double)]
# double distance(Point [*](#), Point [*](#))distance = _mod.distancedistance.argtypes = (ctypes.POINTER(Point), ctypes.POINTER(Point))distance.restype = ctypes.c_double
If all goes well, you should be able to load the module and use the resulting C functions.For example:
>>> import sample
>>> sample.gcd(35,42)
7
>>> sample.in_mandel(0,0,500)
1
>>> sample.in_mandel(2.0,1.0,500)
0
>>> sample.divide(42,8)
(5, 2)
>>> sample.avg([1,2,3])
2.0
>>> p1 = sample.Point(1,2)
>>> p2 = sample.Point(4,5)
>>> sample.distance(p1,p2)
4.242640687119285
>>>
## 討論
There are several aspects of this recipe that warrant some discussion. The first issueconcerns the overall packaging of C and Python code together. If you are using ctypesto access C code that you have compiled yourself, you will need to make sure that theshared library gets placed in a location where the sample.py module can find it. Onepossibility is to put the resulting .so file in the same directory as the supporting Pythoncode. This is what’s shown at the first part of this recipe—sample.py looks at the __file__variable to see where it has been installed, and then constructs a path that points to alibsample.so file in the same directory.If the C library is going to be installed elsewhere, then you’ll have to adjust the pathaccordingly. If the C library is installed as a standard library on your machine, you mightbe able to use the ctypes.util.find_library() function. For example:
>>> from ctypes.util import find_library
>>> find_library('m')
'/usr/lib/libm.dylib'
>>> find_library('pthread')
'/usr/lib/libpthread.dylib'
>>> find_library('sample')
'/usr/local/lib/libsample.so'
>>>
Again, ctypes won’t work at all if it can’t locate the library with the C code. Thus, you’llneed to spend a few minutes thinking about how you want to install things.Once you know where the C library is located, you use ctypes.cdll.LoadLibrary()to load it. The following statement in the solution does this where _path is the fullpathname to the shared library:
_mod = ctypes.cdll.LoadLibrary(_path)
Once a library has been loaded, you need to write statements that extract specific sym‐bols and put type signatures on them. This is what’s happening in code fragments suchas this:
# int in_mandel(double, double, int)in_mandel = _mod.in_mandelin_mandel.argtypes = (ctypes.c_double, ctypes.c_double, ctypes.c_int)in_mandel.restype = ctypes.c_int
In this code, the .argtypes attribute is a tuple containing the input arguments to afunction, and .restype is the return type. ctypes defines a variety of type objects (e.g.,c_double, c_int, c_short, c_float, etc.) that represent common C data types. Attach‐ing the type signatures is critical if you want to make Python pass the right kinds ofarguments and convert data correctly (if you don’t do this, not only will the code notwork, but you might cause the entire interpreter process to crash).A somewhat tricky part of using ctypes is that the original C code may use idioms thatdon’t map cleanly to Python. The divide() function is a good example because it returnsa value through one of its arguments. Although that’s a common C technique, it’s oftennot clear how it’s supposed to work in Python. For example, you can’t do anythingstraightforward like this:
>>> divide = _mod.divide
>>> divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))
>>> x = 0
>>> divide(10, 3, x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 3: <class 'TypeError'>: expected LP_c_int
instance instead of int
>>>
Even if this did work, it would violate Python’s immutability of integers and probablycause the entire interpreter to be sucked into a black hole. For arguments involvingpointers, you usually have to construct a compatible ctypes object and pass it in likethis:
>>> x = ctypes.c_int()
>>> divide(10, 3, x)
3
>>> x.value
1
>>>
Here an instance of a ctypes.c_int is created and passed in as the pointer object. Unlikea normal Python integer, a c_int object can be mutated. The .value attribute can beused to either retrieve or change the value as desired.
For cases where the C calling convention is “un-Pythonic,” it is common to write a smallwrapper function. In the solution, this code makes the divide() function return thetwo results using a tuple instead:# int divide(int, int, int [*](#))_divide = _mod.divide_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))_divide.restype = ctypes.c_int
def divide(x, y):rem = ctypes.c_int()quot = _divide(x,y,rem)return quot, rem.value
The avg() function presents a new kind of challenge. The underlying C code expectsto receive a pointer and a length representing an array. However, from the Python side,we must consider the following questions: What is an array? Is it a list? A tuple? Anarray from the array module? A numpy array? Is it all of these? In practice, a Python“array” could take many different forms, and maybe you would like to support multiplepossibilities.The DoubleArrayType class shows how to handle this situation. In this class, a singlemethod from_param() is defined. The role of this method is to take a single parameterand narrow it down to a compatible ctypes object (a pointer to a ctypes.c_double, inthe example). Within from_param(), you are free to do anything that you wish. In thesolution, the typename of the parameter is extracted and used to dispatch to a morespecialized method. For example, if a list is passed, the typename is list and a methodfrom_list() is invoked.For lists and tuples, the from_list() method performs a conversion to a ctypes arrayobject. This looks a little weird, but here is an interactive example of converting a list toa ctypes array:
>>> nums = [1, 2, 3]
>>> a = (ctypes.c_double * len(nums))(*nums)
>>> a
<__main__.c_double_Array_3 object at 0x10069cd40>
>>> a[0]
1.0
>>> a[1]
2.0
>>> a[2]
3.0
>>>
For array objects, the from_array() method extracts the underlying memory pointerand casts it to a ctypes pointer object. For example:
>>> import array
>>> a = array.array('d',[1,2,3])
>>> a
array('d', [1.0, 2.0, 3.0])
>>> ptr_ = a.buffer_info()
>>> ptr
4298687200
>>> ctypes.cast(ptr, ctypes.POINTER(ctypes.c_double))
<__main__.LP_c_double object at 0x10069cd40>
>>>
The from_ndarray() shows comparable conversion code for numpy arrays.By defining the DoubleArrayType class and using it in the type signature of avg(), asshown, the function can accept a variety of different array-like inputs:
>>> import sample
>>> sample.avg([1,2,3])
2.0
>>> sample.avg((1,2,3))
2.0
>>> import array
>>> sample.avg(array.array('d',[1,2,3]))
2.0
>>> import numpy
>>> sample.avg(numpy.array([1.0,2.0,3.0]))
2.0
>>>
The last part of this recipe shows how to work with a simple C structure. For structures,you simply define a class that contains the appropriate fields and types like this:
class Point(ctypes.Structure):_fields_ = [(‘x', ctypes.c_double),(‘y', ctypes.c_double)]
Once defined, you can use the class in type signatures as well as in code that needs toinstantiate and work with the structures. For example:
>>> p1 = sample.Point(1,2)
>>> p2 = sample.Point(4,5)
>>> p1.x
1.0
>>> p1.y
2.0
>>> sample.distance(p1,p2)
4.242640687119285
>>>
A few final comments: ctypes is a useful library to know about if all you’re doing isaccessing a few C functions from Python. However, if you’re trying to access a largelibrary, you might want to look at alternative approaches, such as Swig (described inRecipe 15.9) or Cython (described in Recipe 15.10).
The main problem with a large library is that since ctypes isn’t entirely automatic, you’llhave to spend a fair bit of time writing out all of the type signatures, as shown in theexample. Depending on the complexity of the library, you might also have to write alarge number of small wrapper functions and supporting classes. Also, unless you fullyunderstand all of the low-level details of the C interface, including memory managementand error handling, it is often quite easy to make Python catastrophically crash with asegmentation fault, access violation, or some similar error.As an alternative to ctypes, you might also look at CFFI. CFFI provides much of thesame functionality, but uses C syntax and supports more advanced kinds of C code. Asof this writing, CFFI is still a relatively new project, but its use has been growing rapidly.There has even been some discussion of including it in the Python standard library insome future release. Thus, it’s definitely something to keep an eye on.
- Copyright
- 前言
- 第一章:數據結構和算法
- 1.1 解壓序列賦值給多個變量
- 1.2 解壓可迭代對象賦值給多個變量
- 1.3 保留最后N個元素
- 1.4 查找最大或最小的N個元素
- 1.5 實現一個優先級隊列
- 1.6 字典中的鍵映射多個值
- 1.7 字典排序
- 1.8 字典的運算
- 1.9 查找兩字典的相同點
- 1.10 刪除序列相同元素并保持順序
- 1.11 命名切片
- 1.12 序列中出現次數最多的元素
- 1.13 通過某個關鍵字排序一個字典列表
- 1.14 排序不支持原生比較的對象
- 1.15 通過某個字段將記錄分組
- 1.16 過濾序列元素
- 1.17 從字典中提取子集
- 1.18 映射名稱到序列元素
- 1.19 轉換并同時計算數據
- 1.20 合并多個字典或映射
- 第二章:字符串和文本
- 2.1 使用多個界定符分割字符串
- 2.2 字符串開頭或結尾匹配
- 2.3 用Shell通配符匹配字符串
- 2.4 字符串匹配和搜索
- 2.5 字符串搜索和替換
- 2.6 字符串忽略大小寫的搜索替換
- 2.7 最短匹配模式
- 2.8 多行匹配模式
- 2.9 將Unicode文本標準化
- 2.10 在正則式中使用Unicode
- 2.11 刪除字符串中不需要的字符
- 2.12 審查清理文本字符串
- 2.13 字符串對齊
- 2.14 合并拼接字符串
- 2.15 字符串中插入變量
- 2.16 以指定列寬格式化字符串
- 2.17 在字符串中處理html和xml
- 2.18 字符串令牌解析
- 2.19 實現一個簡單的遞歸下降分析器
- 2.20 字節字符串上的字符串操作
- 第三章:數字日期和時間
- 3.1 數字的四舍五入
- 3.2 執行精確的浮點數運算
- 3.3 數字的格式化輸出
- 3.4 二八十六進制整數
- 3.5 字節到大整數的打包與解包
- 3.6 復數的數學運算
- 3.7 無窮大與NaN
- 3.8 分數運算
- 3.9 大型數組運算
- 3.10 矩陣與線性代數運算
- 3.11 隨機選擇
- 3.12 基本的日期與時間轉換
- 3.13 計算最后一個周五的日期
- 3.14 計算當前月份的日期范圍
- 3.15 字符串轉換為日期
- 3.16 結合時區的日期操作
- 第四章:迭代器與生成器
- 4.1 手動遍歷迭代器
- 4.2 代理迭代
- 4.3 使用生成器創建新的迭代模式
- 4.4 實現迭代器協議
- 4.5 反向迭代
- 4.6 帶有外部狀態的生成器函數
- 4.7 迭代器切片
- 4.8 跳過可迭代對象的開始部分
- 4.9 排列組合的迭代
- 4.10 序列上索引值迭代
- 4.11 同時迭代多個序列
- 4.12 不同集合上元素的迭代
- 4.13 創建數據處理管道
- 4.14 展開嵌套的序列
- 4.15 順序迭代合并后的排序迭代對象
- 4.16 迭代器代替while無限循環
- 第五章:文件與IO
- 5.1 讀寫文本數據
- 5.2 打印輸出至文件中
- 5.3 使用其他分隔符或行終止符打印
- 5.4 讀寫字節數據
- 5.5 文件不存在才能寫入
- 5.6 字符串的I/O操作
- 5.7 讀寫壓縮文件
- 5.8 固定大小記錄的文件迭代
- 5.9 讀取二進制數據到可變緩沖區中
- 5.10 內存映射的二進制文件
- 5.11 文件路徑名的操作
- 5.12 測試文件是否存在
- 5.13 獲取文件夾中的文件列表
- 5.14 忽略文件名編碼
- 5.15 打印不合法的文件名
- 5.16 增加或改變已打開文件的編碼
- 5.17 將字節寫入文本文件
- 5.18 將文件描述符包裝成文件對象
- 5.19 創建臨時文件和文件夾
- 5.20 與串行端口的數據通信
- 5.21 序列化Python對象
- 第六章:數據編碼和處理
- 6.1 讀寫CSV數據
- 6.2 讀寫JSON數據
- 6.3 解析簡單的XML數據
- 6.4 增量式解析大型XML文件
- 6.5 將字典轉換為XML
- 6.6 解析和修改XML
- 6.7 利用命名空間解析XML文檔
- 6.8 與關系型數據庫的交互
- 6.9 編碼和解碼十六進制數
- 6.10 編碼解碼Base64數據
- 6.11 讀寫二進制數組數據
- 6.12 讀取嵌套和可變長二進制數據
- 6.13 數據的累加與統計操作
- 第七章:函數
- 7.1 可接受任意數量參數的函數
- 7.2 只接受關鍵字參數的函數
- 7.3 給函數參數增加元信息
- 7.4 返回多個值的函數
- 7.5 定義有默認參數的函數
- 7.6 定義匿名或內聯函數
- 7.7 匿名函數捕獲變量值
- 7.8 減少可調用對象的參數個數
- 7.9 將單方法的類轉換為函數
- 7.10 帶額外狀態信息的回調函數
- 7.11 內聯回調函數
- 7.12 訪問閉包中定義的變量
- 第八章:類與對象
- 8.1 改變對象的字符串顯示
- 8.2 自定義字符串的格式化
- 8.3 讓對象支持上下文管理協議
- 8.4 創建大量對象時節省內存方法
- 8.5 在類中封裝屬性名
- 8.6 創建可管理的屬性
- 8.7 調用父類方法
- 8.8 子類中擴展property
- 8.9 創建新的類或實例屬性
- 8.10 使用延遲計算屬性
- 8.11 簡化數據結構的初始化
- 8.12 定義接口或者抽象基類
- 8.13 實現數據模型的類型約束
- 8.14 實現自定義容器
- 8.15 屬性的代理訪問
- 8.16 在類中定義多個構造器
- 8.17 創建不調用init方法的實例
- 8.18 利用Mixins擴展類功能
- 8.19 實現狀態對象或者狀態機
- 8.20 通過字符串調用對象方法
- 8.21 實現訪問者模式
- 8.22 不用遞歸實現訪問者模式
- 8.23 循環引用數據結構的內存管理
- 8.24 讓類支持比較操作
- 8.25 創建緩存實例
- 第九章:元編程
- 9.1 在函數上添加包裝器
- 9.2 創建裝飾器時保留函數元信息
- 9.3 解除一個裝飾器
- 9.4 定義一個帶參數的裝飾器
- 9.5 可自定義屬性的裝飾器
- 9.6 帶可選參數的裝飾器
- 9.7 利用裝飾器強制函數上的類型檢查
- 9.8 將裝飾器定義為類的一部分
- 9.9 將裝飾器定義為類
- 9.10 為類和靜態方法提供裝飾器
- 9.11 裝飾器為被包裝函數增加參數
- 9.12 使用裝飾器擴充類的功能
- 9.13 使用元類控制實例的創建
- 9.14 捕獲類的屬性定義順序
- 9.15 定義有可選參數的元類
- 9.16 *args和**kwargs的強制參數簽名
- 9.17 在類上強制使用編程規約
- 9.18 以編程方式定義類
- 9.19 在定義的時候初始化類的成員
- 9.20 利用函數注解實現方法重載
- 9.21 避免重復的屬性方法
- 9.22 定義上下文管理器的簡單方法
- 9.23 在局部變量域中執行代碼
- 9.24 解析與分析Python源碼
- 9.25 拆解Python字節碼
- 第十章:模塊與包
- 10.1 構建一個模塊的層級包
- 10.2 控制模塊被全部導入的內容
- 10.3 使用相對路徑名導入包中子模塊
- 10.4 將模塊分割成多個文件
- 10.5 利用命名空間導入目錄分散的代碼
- 10.6 重新加載模塊
- 10.7 運行目錄或壓縮文件
- 10.8 讀取位于包中的數據文件
- 10.9 將文件夾加入到sys.path
- 10.10 通過字符串名導入模塊
- 10.11 通過導入鉤子遠程加載模塊
- 10.12 導入模塊的同時修改模塊
- 10.13 安裝私有的包
- 10.14 創建新的Python環境
- 10.15 分發包
- 第十一章:網絡與Web編程
- 11.1 作為客戶端與HTTP服務交互
- 11.2 創建TCP服務器
- 11.3 創建UDP服務器
- 11.4 通過CIDR地址生成對應的IP地址集
- 11.5 生成一個簡單的REST接口
- 11.6 通過XML-RPC實現簡單的遠程調用
- 11.7 在不同的Python解釋器之間交互
- 11.8 實現遠程方法調用
- 11.9 簡單的客戶端認證
- 11.10 在網絡服務中加入SSL
- 11.11 進程間傳遞Socket文件描述符
- 11.12 理解事件驅動的IO
- 11.13 發送與接收大型數組
- 第十二章:并發編程
- 12.1 啟動與停止線程
- 12.2 判斷線程是否已經啟動
- 12.3 線程間的通信
- 12.4 給關鍵部分加鎖
- 12.5 防止死鎖的加鎖機制
- 12.6 保存線程的狀態信息
- 12.7 創建一個線程池
- 12.8 簡單的并行編程
- 12.9 Python的全局鎖問題
- 12.10 定義一個Actor任務
- 12.11 實現消息發布/訂閱模型
- 12.12 使用生成器代替線程
- 12.13 多個線程隊列輪詢
- 12.14 在Unix系統上面啟動守護進程
- 第十三章:腳本編程與系統管理
- 13.1 通過重定向/管道/文件接受輸入
- 13.2 終止程序并給出錯誤信息
- 13.3 解析命令行選項
- 13.4 運行時彈出密碼輸入提示
- 13.5 獲取終端的大小
- 13.6 執行外部命令并獲取它的輸出
- 13.7 復制或者移動文件和目錄
- 13.8 創建和解壓壓縮文件
- 13.9 通過文件名查找文件
- 13.10 讀取配置文件
- 13.11 給簡單腳本增加日志功能
- 13.12 給內庫增加日志功能
- 13.13 記錄程序執行的時間
- 13.14 限制內存和CPU的使用量
- 13.15 啟動一個WEB瀏覽器
- 第十四章:測試調試和異常
- 14.1 測試輸出到標準輸出上
- 14.2 在單元測試中給對象打補丁
- 14.3 在單元測試中測試異常情況
- 14.4 將測試輸出用日志記錄到文件中
- 14.5 忽略或者期望測試失敗
- 14.6 處理多個異常
- 14.7 捕獲所有異常
- 14.8 創建自定義異常
- 14.9 捕獲異常后拋出另外的異常
- 14.10 重新拋出最后的異常
- 14.11 輸出警告信息
- 14.12 調試基本的程序崩潰錯誤
- 14.13 給你的程序做基準測試
- 14.14 讓你的程序跑的更快
- 第十五章:C語言擴展
- 15.1 使用ctypes訪問C代碼
- 15.2 簡單的C擴展模塊
- 15.3 一個操作數組的擴展函數
- 15.4 在C擴展模塊中操作隱形指針
- 15.5 從擴張模塊中定義和導出C的API
- 15.6 從C語言中調用Python代碼
- 15.7 從C擴展中釋放全局鎖
- 15.8 C和Python中的線程混用
- 15.9 用WSIG包裝C代碼
- 15.10 用Cython包裝C代碼
- 15.11 用Cython寫高性能的數組操作
- 15.12 將函數指針轉換為可調用對象
- 15.13 傳遞NULL結尾的字符串給C函數庫
- 15.14 傳遞Unicode字符串給C函數庫
- 15.15 C字符串轉換為Python字符串
- 15.16 不確定編碼格式的C字符串
- 15.17 傳遞文件名給C擴展
- 15.18 傳遞已打開的文件給C擴展
- 15.19 從C語言中讀取類文件對象
- 15.20 處理C語言中的可迭代對象
- 15.21 診斷分析代碼錯誤
- 附錄A
- 關于譯者
- Roadmap