作者:chen_h


1. setuptools 是什么

setuptools 与 disutils

我们通常所知道的 Python 分发工具是 Python distutils, setuptools 可以说是它的增强版,它能帮助我们更好的创建和分发 Python 的包,尤其是具有复杂依赖关系的包。对于开发者来说,能够更好的组织自己项目的分发和发布;对于用户来说,不需要安装 setuptools 也可以使用由它创建的包,只需要一个启动模块即可。

实现这样的的包管理机制主要由两部分构成:

  • 一个存储在 Python 官方网站的集中式仓库,名叫 Python Package Index(PyPI)
  • 另外就是基于 distutils 开发的 setuptools 包管理系统

它提供的内容包括:

  • 用来提供标准元数据字段:诸如作者名、版权类型等信息的骨架
  • 一组用来将包中的代码来构建软件安装包的辅助工具

distutils 仅仅适用于包,它无法定义包之间的依赖关系。但是 setuptools 通过添加一个基本的依赖系统以及许多相关功能,弥补了该缺陷。他还提供了自动包查询程序,用来自动获取包之间的依赖关系,并完成这些包的安装,大大降低了安装各种包的难度,使之更加方便。

2. 简单尝试

首先,我们先来定义一个最简单的目录结构,如下:

myapp
├── myapp
│   └── __init__.py
└── setup.py

我们在 __init__.py 文件中写入以下内容:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

def hello():
    return 'This is a test'

setup.py 文件中写入以下内容:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from setuptools import setup 

setup(name = 'myapp',
    version = '1.0',
    author = 'chen_h',
    author_email = 'naivechen@foxmail.com',
    license = 'MIT',
    )

之后,我们可以为模块创建一个源码包,运行如下命令:

$ python setup.py sdist

running sdist
running egg_info
creating myapp.egg-info
writing myapp.egg-info/PKG-INFO
writing top-level names to myapp.egg-info/top_level.txt
writing dependency_links to myapp.egg-info/dependency_links.txt
writing manifest file 'myapp.egg-info/SOURCES.txt'
reading manifest file 'myapp.egg-info/SOURCES.txt'
writing manifest file 'myapp.egg-info/SOURCES.txt'
warning: sdist: standard file not found: should have one of README, README.rst, README.txt

running check
warning: check: missing required meta-data: url

creating myapp-1.0
creating myapp-1.0/myapp.egg-info
making hard links in myapp-1.0...
hard linking setup.py -> myapp-1.0
hard linking myapp.egg-info/PKG-INFO -> myapp-1.0/myapp.egg-info
hard linking myapp.egg-info/SOURCES.txt -> myapp-1.0/myapp.egg-info
hard linking myapp.egg-info/dependency_links.txt -> myapp-1.0/myapp.egg-info
hard linking myapp.egg-info/top_level.txt -> myapp-1.0/myapp.egg-info
Writing myapp-1.0/setup.cfg
creating dist
Creating tar archive
removing 'myapp-1.0' (and everything under it)

现在我们的目录结构如下:

myapp/
├── dist
│   └── myapp-1.0.tar.gz
├── myapp
│   ├── __init__.py
│   └── __init__.pyc
├── myapp.egg-info
│   ├── PKG-INFO
│   ├── SOURCES.txt
│   ├── dependency_links.txt
│   └── top_level.txt
└── setup.py

dist/ 文件夹下面的文件就是我们的源码包,源码包的命名格式为 “应用名 - 版本号 - .tar.gz” 。

之后我们需要去安装这个应用,使用如下命令:

$ python setup.py install

该命令会将当前的 Python 应用安装到当前 Python 环境的 “site-packages” 目录下,这样其他程序就可以像导入标准库一样导入该应用的代码了。

但我们在开发过程中,我们也可以采用开发方式安装,如下命令:

$ python setup.py develop

如果应用在开发过程中会频繁变更,每次安装还需要先把原来的版本卸载,很麻烦。使用 “develop” 开发方式安装的话,应用代码不会真的被拷贝到本地 Python 环境的 “site-packages” 目录下,而是在 “site-packages” 目录里创建一个指向当前应用位置的连接。这样如果当前位置的源码被改动,就会马上反映到 “site-packages” 里。

至此,我们就可以学会如何构建一个最简单的 python 包文件了。

接下来,我们学习一些 setup 别的参数。

3. zip_safe 参数

该参数决定应用是否作为一个 zip 压缩后的 egg 文件安装在当前 Python 环境中,还是作为一个以 .egg 结尾的目录安装在当前环境中。因为有些工具不支持 zip 压缩文件,而且压缩后的包也不方便调试,所以建议将其设为 False ,即 “zip_safe = False” 。

4. 依赖关系和元数据

如果你使用 Python ,很自然就会使用其他人的包。setuptools 给我们提供了很方便的工具来说明依赖关系,而且在安装我们的包的时候回自动安装依赖包。比如我们给刚刚的代码添加一些包。

我们添加一个 mymath.py 文件,如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import numpy as np 

def add(a,b):
    return np.add(a,b)

之后,我们在 __init__.py 文件中添加包名,如下:

from mymath import add

我们也需要向 setup.py 文件中添加一些内容,如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from setuptools import setup 

setup(name = 'myapp',
    version = '1.0',
    author = 'chen_h',
    author_email = 'naivechen@foxmail.com',
    license = 'MIT',
    zip_safe = False,
    description = 'This is a test for setuptools',
    packages = ['myapp'],
    install_requires = [
        'numpy >= 1.8',
    ],
    )

因为我们是采用 develop 模式来操作的,所以我们不用再次进行安装。直接在终端导入包就好了,如下:

>>> import myapp
>>> myapp.add(2,3)
5

参考:
Python 分发工具初探之 setuptools
python-packaging
Python打包分发工具setuptools简介
Python 分发工具初探之 setuptools