Python在提供傻白甜这样的编程方式的时候,对性能就是硬伤了,所以性能这块提供了C的扩展模块,实际上Python是与本地二进制可执行动态库是无缝调用的。上一个主题解释了Python调用动态库,但是那一种方式需要太多C的知识,其实Python还提供了一种无缝自动的调用方式,在Python成为built-in实现。这个主题就介绍Python的C扩展。顺便还介绍了怎么制作Python安装包
Python的C扩展的模式
- 提示:只争对Python3的情况说明,对Python2放弃了。
Python与C扩展动态库之间的工作关系
- Python调用C动态库的过程
- 编写Python代码
test.py
:import 模块名
直接加载模块; - Python虚拟机执行
test.py
代码,会根据import的模块名查找模块,模块可以是是python模块或者dll模块,dll模块查找规则是:模块名.其他任意描述.pyd
; - 查找到以后,执行
PyInit_模块名
初始化函数完成dll的初始化; -
PyInit_模块名
初始化函数完成python中函数名与扩展接口包装函数名的映射; - 当调用模块中函数的时候,python会根据python函数名找到C扩展包装函数名;
- C扩展包装函数调用任意C实现的代码。
- 编写Python代码
C动态库的编译
-
C的动态库使用python的C库与头文件
- 实际没有额外dll的库,所有代码都封装在h文件中。
Python.h
structmember.h
- 实际没有额外dll的库,所有代码都封装在h文件中。
-
不同的平台或者编译器编译的指令都有差异,但是编译的结构都是动态库
- gcc是编译so文件;(mac的clang编译器可能是dylib)
- link是编译dll文件;
-
说明:
- 尽管是动态库,但是Python扩展的工作规范是pyd扩展名,不使用so、dll、dylib文件名扩展名。
C扩展接口的编程模式
包装函数实现
-
包装函数可以自己实现,也可以调用C的其他实现,包含C动态库。下面是一个代码例子,重点是:
- 包装器函数形式三种:
- 一般参数:
typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
- 命名参数:
typedef PyObject *(*PyCFunctionWithKeywords)(PyObject *, PyObject *, PyObject *);
- 无参数:
typedef PyObject *(*PyNoArgsFunction)(PyObject *);
- 一般参数:
- 参数的转换处理(把
PyObject*
类型转换为C类型):- 解析命名参数:
PyArg_ParseTupleAndKeywords
- 解析一般参数:
PyArg_ParseTuple
- 解析命名参数:
- 返回值得转换处理:(如果已经是
PyObject*
类型则不需要转换)- 调用
Py_BuildValue
函数把C类型转换为PyObject*
类型
- 调用
- 包装器函数形式三种:
包装器函数可以随意命名,但是建议采用
模块名_函数名
,我们使用的是py_函数名
。
static char py_gcd_doc[] = "最大公约数";
static PyObject* py_gcd(PyObject*self, PyObject *args){
int r, x, y;
// 解析参数
if(!PyArg_ParseTuple(args, "ii:gcd", &x, &y)){
return NULL;
}
r = gcd(x, y);
return Py_BuildValue("i", r);
}
static char py_replace_doc[] = "字符替换";
static PyObject* py_replace(PyObject* self, PyObject *args, PyObject *kwargs){
char *s, *sdup;
char och, nch;
int nrep;
PyObject *result;
static char *argsname[] = {"s", "och", "nch", NULL};
// 解析参数
if(!PyArg_ParseTupleAndKeywords(args, kwargs, "scc:replace", argsname, &s, &och, &nch)){
return NULL;
}
sdup = (char *)malloc(strlen(s) + 1);
strcpy(sdup, s);
nrep = replace(sdup, och, nch);
result = Py_BuildValue("(is)", nrep, sdup);
free(sdup);
return result;
}
static char py_distance_doc[] = "计算距离";
static PyObject* py_distance(PyObject *self, PyObject *args){
PyErr_SetString(PyExc_NotImplementedError, "distance() not implements");
return NULL;
}
附录:上面调用的三个函数的C实现
头文件:C.h
#ifndef C_YQ_H
#define C_YQ_H
#include <stdio.h>
#include <string.h>
#include <math.h>
typedef struct Point{
double x;
double y;
} Point;
extern int gcd(int x, int y);
extern int replace(char *s, char och, char nch);
extern double distance(Point *a, Point *b);
#define MAGIC 0x31337
#endif
实现文件:C.c
#include "C.h"
int gcd(int x, int y){
/*
这个算法是来自最大公约数的一个性质
gcd(a,b)=gcd(b, a mod b)
gcd(a,b)=gcd(b, a-b)
*/
int g;
g = y;
while(x > 0){
g = x;
x = y % x;
y = g;
}
return g;
}
int replace(char *s, char och, char nch){
int nrep = 0;
while(s = strchr(s, och)){
*(s++) = nch;
nrep++;
}
return nrep;
}
double distance(Point *a, Point *b){
double dx, dy;
dx = a->x - b->x;
dy = a->y - b->y;
return sqrt(dx * dx + dy * dy);
}
// 测试代码调用的,编译成执行文件执行:cl /EHsc /MD /utf-8 /nologo C.c /link /MACHINE:X64 /NOLOGO /OUT:main.exe
// int main(){
// printf("公约数是:%d\n", gcd(100,254));
// char *str = "Hello this is a world!";
// printf("替换次数:%d\n", replace(str, 'i','X'));
// printf("替换结果:%s\n", str);
// Point a = {0, 1};
// Point b = {1, 0};
// printf("距离:%f\n", distance(&a, &b));
// return 0;
// }
定义模块
PyModuleDef结构体
- 模块定义在Python的C扩展中是一个结构体:
PyModuleDef
, 创建模块的使用,就是使用这个定义模块,这个结构体的定义如下:
typedef struct PyModuleDef{
PyModuleDef_Base m_base;
const char* m_name;
const char* m_doc;
Py_ssize_t m_size;
PyMethodDef *m_methods;
struct PyModuleDef_Slot* m_slots;
traverseproc m_traverse;
inquiry m_clear;
freefunc m_free;
} PyModuleDef;
- 其中的核心是前面5个成员的定义是必须。
-
PyModuleDef_Base m_base;
- 这是指定基,使用一个宏指定:
PyModuleDef_HEAD_INIT
- 这是指定基,使用一个宏指定:
-
const char* m_name;
- 模块名:这个名字也是编译后的dll/so的名字。
-
const char* m_doc;
- 文档,可以指定为NULL。
-
Py_ssize_t m_size;
- 使用-1标识自动指定大小。
-
PyMethodDef *m_methods;
- 模块中的方法/函数定义。这里指针是指的边长动态数组。
-
PyMethodDef结构体
- 模块中导出的方法/函数定义, 使用结构体定义:
PyMethodDef
struct PyMethodDef {
const char *ml_name; /* The name of the built-in function/method */
PyCFunction ml_meth; /* The C function that implements it */
int ml_flags; /* Combination of METH_xxx flags, which mostly
describe the args expected by the C func */
const char *ml_doc; /* The __doc__ attribute, or NULL */
};
typedef struct PyMethodDef PyMethodDef;
- PyMethodDef结构体成员说明:
-
const char *ml_name
:- python中调用的函数名:
-
PyCFunction ml_meth;
:- C扩展包装函数名,其真正类型是
PyCFunction + PyCFunctionWithKeywords + PyNoArgsFunction
,为了防止编译警告,需要类型转换为PyCFunction
- C扩展包装函数名,其真正类型是
-
int ml_flags
:- 指定参数类型:
PyCFunction + PyCFunctionWithKeywords + PyNoArgsFunction
,使用对应的宏#define METH_VARARGS 0x0001
#define METH_KEYWORDS 0x0002
#define METH_NOARGS 0x0004
- METH_VARARGS必须与METH_KEYWORDS联合使用。
- 指定参数类型:
-
const char *ml_doc
:- 函数的文档定义。
-
模块定义的例子
- 先定义:PyMethodDef,再定义:PyModuleDef
static PyMethodDef _mymethos[] = {
{"gcd", py_gcd, METH_VARARGS, py_gcd_doc},
{"replace", (PyCFunction)py_replace, METH_KEYWORDS | METH_VARARGS, py_replace_doc}, // 因为带三个参数,属于PyCFunctionWithKeywords
{"distance", py_distance, METH_VARARGS, py_distance_doc},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef _mymodule = {
PyModuleDef_HEAD_INIT,
"mymodule",
NULL,
-1,
_mymethos
};
创建模块
- 创建模块必须在导出函数中创建:
导出函数
-
导出函数规范:
-
PyMODINIT_FUNC PyInit_模块名(void)
- 返回值:
PyMODINIT_FUNC
,本质是PyObject指针类型,也就是是一个模块对象,使用:模块名.函数
可以在Python中调用。define PyMODINIT_FUNC __declspec(dllexport) PyObject*
- 参数:无
- 函数名:
PyInit_
作为前缀,后面加上模块名。模块名以PyModuleDef对象中定义的名字为准。
- 返回值:
-
导出函数的例子:
PyMODINIT_FUNC PyInit_mymodule(void){
return PyMODINIT_FUNC对象;
}
创建模块并返回
-
模块创建使用函数PyModule_Create创建,函数原型是:
-
PyAPI_FUNC(PyObject *) PyModule_Create2(struct PyModuleDef*, int apiver);
- 第2个参数使用api把把版本,是固定的值:
#define PYTHON_API_VERSION 1013
- 第一个参数类型是:
PyModuleDef *
- 返回值:PyObject指针。
- 第2个参数使用api把把版本,是固定的值:
-
模块撞见的例子:
PyMODINIT_FUNC PyInit_mymodule(void){
PyObject *mod;
mod = PyModule_Create(&_mymodule);
return mod;
}
Python的C扩展例子
- 提示:使用《Python参考手册(第4版)》中第26章的例子。
C功能实现
C头文件:C.h
#ifndef C_YQ_H
#define C_YQ_H
#include <stdio.h>
#include <string.h>
#include <math.h>
typedef struct Point{
double x;
double y;
} Point;
extern int gcd(int x, int y);
extern int replace(char *s, char och, char nch);
extern double distance(Point *a, Point *b);
#define MAGIC 0x31337
#endif
C实现文件:C.c
#include "C.h"
int gcd(int x, int y){
/*
这个算法是来自最大公约数的一个性质
gcd(a,b)=gcd(b, a mod b)
gcd(a,b)=gcd(b, a-b)
*/
int g;
g = y;
while(x > 0){
g = x;
x = y % x;
y = g;
}
return g;
}
int replace(char *s, char och, char nch){
int nrep = 0;
while(s = strchr(s, och)){
*(s++) = nch;
nrep++;
}
return nrep;
}
double distance(Point *a, Point *b){
double dx, dy;
dx = a->x - b->x;
dy = a->y - b->y;
return sqrt(dx * dx + dy * dy);
}
// 测试代码调用的,编译成执行文件执行:cl /EHsc /MD /utf-8 /nologo C.c /link /MACHINE:X64 /NOLOGO /OUT:main.exe
// int main(){
// printf("公约数是:%d\n", gcd(100,254));
// char *str = "Hello this is a world!";
// printf("替换次数:%d\n", replace(str, 'i','X'));
// printf("替换结果:%s\n", str);
// Point a = {0, 1};
// Point b = {1, 0};
// printf("距离:%f\n", distance(&a, &b));
// return 0;
// }
C扩展实现
#include <Python.h>
#include "C.h"
static char py_gcd_doc[] = "最大公约数";
static PyObject* py_gcd(PyObject*self, PyObject *args){
int r, x, y;
// 解析参数
if(!PyArg_ParseTuple(args, "ii:gcd", &x, &y)){
return NULL;
}
r = gcd(x, y);
return Py_BuildValue("i", r);
}
static char py_replace_doc[] = "字符替换";
static PyObject* py_replace(PyObject* self, PyObject *args, PyObject *kwargs){
char *s, *sdup;
char och, nch;
int nrep;
PyObject *result;
static char *argsname[] = {"s", "och", "nch", NULL};
// 解析参数
if(!PyArg_ParseTupleAndKeywords(args, kwargs, "scc:replace", argsname, &s, &och, &nch)){
return NULL;
}
sdup = (char *)malloc(strlen(s) + 1);
strcpy(sdup, s);
nrep = replace(sdup, och, nch);
result = Py_BuildValue("(is)", nrep, sdup);
free(sdup);
return result;
}
static char py_distance_doc[] = "计算距离";
static PyObject* py_distance(PyObject *self, PyObject *args){
PyErr_SetString(PyExc_NotImplementedError, "distance() not implements");
return NULL;
}
static PyMethodDef _mymethos[] = {
{"gcd", py_gcd, METH_VARARGS, py_gcd_doc},
{"replace", (PyCFunction)py_replace, METH_KEYWORDS | METH_VARARGS, py_replace_doc}, // 因为带三个参数,属于PyCFunctionWithKeywords
{"distance", py_distance, METH_VARARGS, py_distance_doc},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef _mymodule = {
PyModuleDef_HEAD_INIT,
"mymodule",
NULL,
-1,
_mymethos
};
PyMODINIT_FUNC PyInit_mymodule(void){
PyObject *mod;
mod = PyModule_Create(&_mymodule);
PyModule_AddIntMacro(mod, MAGIC);
return mod;
}
编译脚本
- 脚本:Makefile
pyd: C.c C.h C_ython.c
# 切换utf-8编码
@chcp 65001
# 编译C.c
@cl /c /EHsc /MD /utf-8 /nologo /Fo:C.obj C.c
# 编译C_ython.c
@cl /c /EHsc /MD /utf-8 /nologo "-IC:\Program Files\Python36\include" /Fo:C_ython.obj C_ython.c
# 链接动态库
@link /MACHINE:X64 /NOLOGO /DLL /OUT:mymodule.pyd /EXPORT:PyInit_mymodule C.obj C_ython.obj
clean:
@del *.obj *.dll *.pdb *.ilk *.exe *.lib *.exp *.pyd 2>/Nula
- 执行脚本:
vcvars64.bat
nmake pyd
- 注意:
- vcvars64.bat安装
Visual Studio
后就有的一个C++编译环境配置脚本。
- vcvars64.bat安装
- 编译效果
Python调用代码
- Python调用就像Python语法一样
# 模块导入
import mymodule
# 模块调用
print(mymodule.gcd(6, 8))
Python执行效果
- 执行python:
python test.py
- 在Linux下,直接使用脚本执行更加方便。
观察下pyd的导出信息
- 从此C的动态库可以作为Python模块的扩展形式存在,与Python模块一样的使用。
- 这也是所谓built-in内置函数与类的实现机制与来源。不见代码,只见模块。真是酸爽啊!
C扩展的核心
- C扩展都是有编程模式的,模式清楚后,剩余的就是参数与返回值的类型处理,这是最大的麻烦。后面单独开一个主题说明。
Python的扩展编译
- 上面的内容完全是利用C的编程技术,包含Python的C扩展接口技术,实际Python提供了更多自动化的工作机制
扩展编译
- 扩展编译就是不需要自己编写Makefile这样的编译脚本,而是Python自动提供编译配置。
setup.py文件模式
- setup.py文件一般编程模式
from distutils.core import setup, Extension
setup(
name="my",
version="1.1",
ext_modules=[
Extension("mymodule", sources=["C_ython.c", "C.c"],language="C"),
]
)
- 那么是包名
setup函数
- setup函数是一个setup脚本需要的工作设置。
from distutils.core import setup, Extension
help(setup)
Help on function setup in module distutils.core:
setup(**attrs)
The gateway to the Distutils: do everything your setup script needs
to do, in a highly flexible and user-driven way. Briefly: create a
Distribution instance; find and parse config files; parse the command
line; run each Distutils command found there, customized by the options
supplied to 'setup()' (as keyword arguments), in config files, and on
the command line.
The Distribution instance might be an instance of a class supplied via
the 'distclass' keyword argument to 'setup'; if no such class is
supplied, then the Distribution class (in dist.py) is instantiated.
All other arguments to 'setup' (except for 'cmdclass') are used to set
attributes of the Distribution instance.
The 'cmdclass' argument, if supplied, is a dictionary mapping command
names to command classes. Each command encountered on the command line
will be turned into a command class, which is in turn instantiated; any
class found in 'cmdclass' is used in place of the default, which is
(for command 'foo_bar') class 'foo_bar' in module
'distutils.command.foo_bar'. The command class must provide a
'user_options' attribute which is a list of option specifiers for
'distutils.fancy_getopt'. Any command-line options between the current
and the next command are used to set attributes of the current command
object.
When the entire command-line has been successfully parsed, calls the
'run()' method on each command object in turn. This method will be
driven entirely by the Distribution object (which each command object
has a reference to, thanks to its constructor), and the
command-specific options that became attributes of each command
object.
setup.py的常见任务
C:\01works\13python\codes\cython_py>python setup.py --help-commands
Standard commands:
build build everything needed to install
build_py "build" pure Python modules (copy to build directory)
build_ext build C/C++ extensions (compile/link to build directory)
build_clib build C/C++ libraries used by Python extensions
build_scripts "build" scripts (copy and fixup #! line)
clean clean up temporary files from 'build' command
install install everything from build directory
install_lib install all Python modules (extensions and pure Python)
install_headers install C/C++ header files
install_scripts install scripts (Python or otherwise)
install_data install data files
sdist create a source distribution (tarball, zip file, etc.)
register register the distribution with the Python package index
bdist create a built (binary) distribution
bdist_dumb create a "dumb" built distribution
bdist_rpm create an RPM distribution
bdist_wininst create an executable installer for MS Windows
check perform some checks on the package
upload upload binary package to PyPI
usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
or: setup.py --help [cmd1 cmd2 ...]
or: setup.py --help-commands
or: setup.py cmd --help
- 我们主要使用build_ext
build_ext的选项
C:\01works\13python\codes\cython_py>python setup.py build_ext --help
Common commands: (see '--help-commands' for more)
setup.py build will build the package underneath 'build/'
setup.py install will install the package
Global options:
--verbose (-v) run verbosely (default)
--quiet (-q) run quietly (turns verbosity off)
--dry-run (-n) don't actually do anything
--help (-h) show detailed help message
--no-user-cfg ignore pydistutils.cfg in your home directory
Options for 'build_ext' command:
--build-lib (-b) directory for compiled extension modules
--build-temp (-t) directory for temporary files (build by-products)
--plat-name (-p) platform name to cross-compile for, if supported
(default: win-amd64)
--inplace (-i) ignore build-lib and put compiled extensions into the
source directory alongside your pure Python modules
--include-dirs (-I) list of directories to search for header files
(separated by ';')
--define (-D) C preprocessor macros to define
--undef (-U) C preprocessor macros to undefine
--libraries (-l) external C libraries to link with
--library-dirs (-L) directories to search for external C libraries
(separated by ';')
--rpath (-R) directories to search for shared C libraries at runtime
--link-objects (-O) extra explicit link objects to include in the link
--debug (-g) compile/link with debugging information
--force (-f) forcibly build everything (ignore file timestamps)
--compiler (-c) specify the compiler type
--parallel (-j) number of parallel build jobs
--swig-cpp make SWIG create C++ files (default is C)
--swig-opts list of SWIG command line options
--swig path to the SWIG executable
--user add user include, library and rpath
--help-compiler list available compilers
usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
or: setup.py --help [cmd1 cmd2 ...]
or: setup.py --help-commands
or: setup.py cmd --help
Extension类
from distutils.core import setup, Extension
help(Extension)
Help on class Extension in module distutils.extension:
class Extension(builtins.object)
| Just a collection of attributes that describes an extension
| module and everything needed to build it (hopefully in a portable
| way, but there are hooks that let you be as unportable as you need).
|
| Instance attributes:
| name : string
| the full name of the extension, including any packages -- ie.
| *not* a filename or pathname, but Python dotted name
| sources : [string]
| list of source filenames, relative to the distribution root
| (where the setup script lives), in Unix form (slash-separated)
| for portability. Source files may be C, C++, SWIG (.i),
| platform-specific resource files, or whatever else is recognized
| by the "build_ext" command as source for a Python extension.
| include_dirs : [string]
| list of directories to search for C/C++ header files (in Unix
| form for portability)
| define_macros : [(name : string, value : string|None)]
| list of macros to define; each macro is defined using a 2-tuple,
| where 'value' is either the string to define it to or None to
| define it without a particular value (equivalent of "#define
| FOO" in source or -DFOO on Unix C compiler command line)
| undef_macros : [string]
| list of macros to undefine explicitly
| library_dirs : [string]
| list of directories to search for C/C++ libraries at link time
| libraries : [string]
| list of library names (not filenames or paths) to link against
| runtime_library_dirs : [string]
| list of directories to search for C/C++ libraries at run time
| (for shared extensions, this is when the extension is loaded)
| extra_objects : [string]
| list of extra files to link with (eg. object files not implied
| by 'sources', static library that must be explicitly specified,
| binary resource files, etc.)
| extra_compile_args : [string]
| any extra platform- and compiler-specific information to use
| when compiling the source files in 'sources'. For platforms and
| compilers where "command line" makes sense, this is typically a
| list of command-line arguments, but for other platforms it could
| be anything.
| extra_link_args : [string]
| any extra platform- and compiler-specific information to use
| when linking object files together to create the extension (or
| to create a new static Python interpreter). Similar
| interpretation as for 'extra_compile_args'.
| export_symbols : [string]
| list of symbols to be exported from a shared extension. Not
| used on all platforms, and not generally necessary for Python
| extensions, which typically export exactly one symbol: "init" +
| extension_name.
| swig_opts : [string]
| any extra options to pass to SWIG if a source file has the .i
| extension.
| depends : [string]
| list of files that the extension depends on
| language : string
| extension language (i.e. "c", "c++", "objc"). Will be detected
| from the source extensions if not provided.
| optional : boolean
| specifies that a build failure in the extension should not abort the
| build process, but simply not install the failing extension.
|
| Methods defined here:
|
| __init__(self, name, sources, include_dirs=None, define_macros=None, undef_macros=None, library_dirs=None, libraries=None, runtime_library_dirs=None, extra_objects=None, extra_compile_args=None, extra_link_args=None, export_symbols=None, swig_opts=None, depends=None, language=None, optional=None, **kw)
| Initialize self. See help(type(self)) for accurate signature.
|
| __repr__(self)
| Return repr(self).
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
扩展编译结果
- 执行命令:
python setup.py build_ext --inplace
这个编译不需要执行vcvars64,因为所有的选项都这是在命令行了。
然后执行test.py测试下效果,这个与直接用C编译器编译的调用一样。
-
更多的setup.py使用,请参考:
https://thomasnyberg.com/cpp_extension_modules.html
https://packaging.python.org/guides/packaging-binary-extensions/
https://packaging.python.org/tutorials/packaging-projects/
包装扩展模块的Python模块
- 一般不直接调用Python的C扩展模块,一般会使用Python包装一下。
- 包装模块
- 文件名:
my.py
- 文件名:
from mymodule import *
- 调用模块
test.py
import my
print(my.gcd(6, 8))
安装
安装的目录结构
一定要清楚自己需要安装什么,目录结构需要清楚。
-
安装包含两种类型的文件:
- Python的代码
- Python的扩展模块(直接执行Python模块也可以)
-
下面是需要安装的目录包
- 需要编译安装的Python的C扩展模块源代码
- C.h
- C.c
- C_ython.c
- python的封装代码(包方式,模块的包需要init.py文件,没有内容就空文件)
- my.py
- 注意:为了更好理解子包安装,这里画蛇添足的增加了有个子包mypkg
- 测试的脚本代码
- test.py
- 需要编译安装的Python的C扩展模块源代码
安装脚本文件setup.py
from distutils.core import setup, Extension
setup(
name="mydemos", # 用于pip安装中指定的名字
version="1.1",
packages=["mydemo", "mydemo.mypkg"], # 包路径下一定要有__init__.py
scripts=["test.py"],
ext_modules=[
Extension("mymodule", sources=["C_ython.c", "C.c"],language="C"), # 需要安装的python扩展模块
]
)
直接安装
命令:
python setup.py install
安装过程
- 安装结果
- Python的C扩展模块
- Python包与模块
- 使用pip list也能查看到安装的模块
- 执行测试
- 注意使用包的方式,访问扩展模块就需要多几个包了,下面是在IPython环境下测试,在非交互式编程也是一样的。
安装打包
- 命令:
python setup.py sdist
- 使用安装就非常简单了,直接解压,然后调用
python setup.py install
就ok。
附录
如果包路径比较分散:可以使用:package_dir选项配置。这里就不举例子了。
requires可以指定依赖模块。
data_files提供数据文件。