此系列文章:
Python和Go:第一部分-grpc
Python和Go:第二部分-使用Go扩展python
介绍
在上一篇文章中我们将Go代码编译到一个共享库中,并从Python交互式shell中使用了它。在本文中,我们将通过编写一个Python模块来完成开发过程,该模块将隐藏使用共享库的底层细节,然后将此代码打包为Python包。
回顾-体系结构和工作流程概述
图1
图1显示了从Python到Go再返回的数据流。
我们遵循的工作流程是:
- 编写Go代码(
CheckSignature
), - 导出到共享库(
verify
) - 在Python交互式提示中使用ctypes调用Go代码
- 编写并打包Python代码(
check_signatures
)
在上一篇博客文章中,我们已经完成了前三部分,在这一部分中,我们将实现Python模块并打包。我们将执行以下步骤:
- 编写Python模块(
checksig.py
) - 写入项目定义文件(
setup.py
) - 构建扩展
Python模块
让我们从编写Python模块开始。该模块将具有Pythonic API,并将隐藏使用Go代码构建的共享库的底层细节。
checksig.py
01 """Parallel check of files digital signature"""
02
03 import ctypes
04 from distutils.sysconfig import get_config_var
05 from pathlib import Path
06
07 # Location of shared library
08 here = Path(__file__).absolute().parent
09 ext_suffix = get_config_var('EXT_SUFFIX')
10 so_file = here / ('_checksig' + ext_suffix)
11
12 # Load functions from shared library set their signatures
13 so = ctypes.cdll.LoadLibrary(so_file)
14 verify = so.verify
15 verify.argtypes = [ctypes.c_char_p]
16 verify.restype = ctypes.c_void_p
17 free = so.free
18 free.argtypes = [ctypes.c_void_p]
19
20
21 def check_signatures(root_dir):
22 """Check (in parallel) digital signature of all files in root_dir.
23 We assume there's a sha1sum.txt file under root_dir
24 """
25 res = verify(root_dir.encode('utf-8'))
26 if res is not None:
27 msg = ctypes.string_at(res).decode('utf-8')
28 free(res)
29 raise ValueError(msg)
第12-18行的代码与我们在交互式提示中所做的非常相似。
第7-10行处理共享库文件名-当我们在下面讨论打包时,我们将了解为什么需要它。在第21-29行,我们定义了模块的API-一个称为check_signatures
的函数。ctypes会将C的NULL
转换为Python的 None
,因此将在第26行if
执行该语句。在第29行,我们通过抛出ValueError
异常来提示错误。
注意:Python的命名约定与Go不同。大多数Python代码都遵循PEP-8[1]中定义的标准。
安装和打包
在继续进行构建Python模块的最后一部分之前,我们需要先了解一下安装Python软件包的工作方式。你可以跳过此部分,直接转到下面的代码,但是我相信本节将帮助你理解我们为什么会在下面编写这些代码。
这是安装包时Python的pip[2]的简化工作流程:
图2
图2显示了安装Python软件包的简化流程图。如果有一个与当前OS /体系结构匹配的预构建二进制包(wheel),它将使用它。否则,它将下载源代码并构建。
我们将Python软件包大致分为“纯”和“非纯”两种。纯软件包仅用Python编写,而非纯软件包则用其他语言编写并编译到共享库中。由于非纯软件包包含二进制代码,因此需要专门针对OS /体系结构组合(例如Linux / amd64)构建它们。我们的软件包被认为是“非纯”的,因为它包含用Go编写的代码。
我们首先编写一个名为setup.py
的文件,该文件定义了项目并包含有关如何构建它的说明。
setup.py
01 """Setup for checksig package"""
02 from distutils.errors import CompileError
03 from subprocess import call
04
05 from setuptools import Extension, setup
06 from setuptools.command.build_ext import build_ext
07
08
09 class build_go_ext(build_ext):
10 """Custom command to build extension from Go source files"""
11 def build_extension(self, ext):
12 ext_path = self.get_ext_fullpath(ext.name)
13 cmd = ['go', 'build', '-buildmode=c-shared', '-o', ext_path]
14 cmd += ext.sources
15 out = call(cmd)
16 if out != 0:
17 raise CompileError('Go build failed')
18
19
20 setup(
21 name='checksig',
22 version='0.1.0',
23 py_modules=['checksig'],
24 ext_modules=[
25 Extension('_checksig', ['checksig.go', 'export.go'])
26 ],
27 cmdclass={'build_ext': build_go_ext},
28 zip_safe=False,
29 )
在第09行,我们定义了一个使用Go编译器构建扩展的命令。Python内置支持以C,C ++和SWIG[3]编写的扩展,但不支持Go。
第12-14行定义要运行的命令,第15行将此命令作为外部命令运行(Python的 subprocess
类似于Go os/exec
)。
在第20行,我们调用setup命令,在第21行中指定包名称,在第22行中指定版本。在第23行,我们定义Python模块名称,在第24-26行中,定义扩展模块(Go代码) 。在第27行,我们用构建Go代码的build_ext
命令覆盖了内置build_ext
命令。在第28行,我们指定该软件包不是zip安全的,因为它包含共享库。
我们需要创建的另一个文件是MANIFEST.in
。该文件定义了所有需要打包在源代码分发中的额外文件。
MANIFEST.in
01 include README.md
02 include *.go go.mod go.sum
构建Python包
$ python setup.py bdist_wheel
$ python setup.py sdist
软件包建立在名为dist
的子目录中。
dist
目录内容
$ ls dist
checksig-0.1.0-cp38-cp38-linux_x86_64.whl
checksig-0.1.0.tar.gz
我们使用ls
命令显示dist
目录的内容。
wheel二进制软件包(带有.whl
扩展名)的名称中包含平台信息:cp38
表示CPython版本3.8,linux_x86_64
是操作系统和体系结构-与Go的 GOOS
和GOARCH
相同。由于wheel文件的名称根据其所基于的体系结构而变化,因此我们不得不在checksig.py
的08-10行中编写一些逻辑。
现在,你可以使用Python的软件包管理器pip
安装这些软件包。如果你要发布出去,则可以使用诸如twine[4]之类的工具将其上传到PythonPyPI[5]。
有关完整的构建,安装和使用流程,请参见源代码存储库[6]中的example.py 和 Dockerfile.test-b
。
结论
你可以毫不费力地使用Go扩展Python,并发布具有Pythonic API的Python模块。打包是使你的代码可部署且可用的必要条件,请不要跳过此步骤。
如果你想从Go返回Python类型(例如list
或 dict
),则可以将Python的扩展C API[7]与cgo一起使用。你也可以看看go-python[8],它可以减少在Go中编写Python扩展的困难。
在下一部分中,我们将再次转换角色,我们将从Go调用Python-在相同的内存空间中几乎零序列化。
参考资料
[1]PEP-8: https://www.python.org/dev/peps/pep-0008/
[2]pip: https://pip.pypa.io/en/stable/
[3]SWIG: http://www.swig.org/
[4]twine: https://github.com/pypa/twine
[5]PyPI: https://pypi.org/
[6]源代码存储库: https://github.com/ardanlabs/python-go/tree/master/pyext
[7]扩展C API: https://docs.python.org/3/extending/index.html
[8]go-python: https://github.com/sbinet/go-python
你点的每个在看,都是对我最大的支持