Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

Python的构建工具setup.py

2019/09/15 Python
Word count: 1,962 | Reading time: 10min

Python的构建工具setup.py

setup.py各参数介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
--name 库名称,▲需要注意的是不要大写,不然会有坑
--version (-V) 包版本
--author 程序的作者
--author_email 程序的作者的邮箱地址
--maintainer 维护者
--maintainer_email 维护者的邮箱地址
--url 程序的官网地址
--license 程序的授权信息
--description 程序的简单描述
--long_description 程序的详细描述
--platforms 程序适用的软件平台列表
--classifiers 程序的所属分类列表
--keywords 程序的关键字列表
--packages 需要处理的包目录(包含__init__.py的文件夹)
--py_modules 需要打包的python文件列表
--download_url 程序的下载地址
--cmdclass
--data_files 打包时需要打包的数据文件,如图片,配置文件等
--scripts 安装时需要执行的脚步列表
--package_dir 告诉setuptools哪些目录下的文件被映射到哪个源码包。一个例子:package_dir = {'': 'lib'},表示“root package”中的模块都在lib 目录中。
--requires 定义依赖哪些模块
--provides定义可以为哪些模块提供依赖
--find_packages() 对于简单工程来说,手动增加packages参数很容易,刚刚我们用到了这个函数,它默认在和setup.py同一目录下搜索各个含有 __init__.py的包。
--install_requires = ["requests"] 需要安装的依赖包
--entry_points 动态发现服务和插件,下面详细讲

find_packages()还可以将包统一放在一个src目录中,另外,这个包内可能还有aaa.txt文件和data数据文件夹。另外,也可以排除一些特定的包 find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"])

实测操作

需要事前交代的内容

模板setup.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Note: To use the 'upload' functionality of this file, you must:
# $ pip install twine

import io
import os
import sys
from shutil import rmtree

from setuptools import find_packages, setup, Command

# Package meta-data.
NAME = 'cltest'
DESCRIPTION = 'know how to'
URL = ''
EMAIL = '10630529664@qq.com'
AUTHOR = 'Gao Liang'
REQUIRES_PYTHON = '>=3.0.0'
VERSION = '0.3.0'

# What packages are required for this module to be executed?
REQUIRED = [
'requests >= 2.20.0',
]

# The rest you shouldn't have to touch too much :)
# ------------------------------------------------
# Except, perhaps the License and Trove Classifiers!
# If you do change the License, remember to change the Trove Classifier for that!

here = os.path.abspath(os.path.dirname(__file__))

# Import the README and use it as the long-description.
# Note: this will only work if 'README.md' is present in your MANIFEST.in file!
try:
with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
long_description = '\n' + f.read()
except FileNotFoundError:
long_description = DESCRIPTION

# Load the package's __version__.py module as a dictionary.
about = {}
if not VERSION:
with open(os.path.join(here, NAME, '__version__.py')) as f:
exec(f.read(), about)
else:
about['__version__'] = VERSION


class UploadCommand(Command):
"""Support setup.py upload."""

description = 'Build and publish the package.'
user_options = []

@staticmethod
def status(s):
"""Prints things in bold."""
print('\033[1m{0}\033[0m'.format(s))

def initialize_options(self):
pass

def finalize_options(self):
pass

def run(self):
try:
self.status('Removing previous builds…')
rmtree(os.path.join(here, 'dist'))
except OSError:
pass

self.status('Building Source and Wheel (universal) distribution…')
os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))

self.status('Uploading the package to PyPI via Twine…')
os.system('twine upload dist/*')

self.status('Pushing git tags…')
os.system('git tag v{0}'.format(about['__version__']))
os.system('git push --tags')

sys.exit()


# Where the magic happens:
setup(
name=NAME,
version=about['__version__'],
description=DESCRIPTION,
long_description=long_description,
long_description_content_type='text/markdown',
author=AUTHOR,
author_email=EMAIL,
python_requires=REQUIRES_PYTHON,
url=URL,
packages=find_packages(exclude=('tests',)),
# If your package is a single module, use this instead of 'packages':
# py_modules=['mypackage'],

# entry_points={
# 'console_scripts': ['mycli=mymodule:cli'],
# },
install_requires=REQUIRED,
include_package_data=True,
license='MIT',
classifiers=[
# Trove classifiers
# Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy'
],
# $ setup.py publish support.
cmdclass={
'upload': UploadCommand,
},
)

待打包的目录树:

E:.
MANIFEST.in
│ out.json
README.md
setup.py

└─files
in.json
txt.txt
world.py
__init__.py

1
2
3
4
5
6
7
8
9
10
ACCOUNT = 'mrli'


def printHello():
print("hello {}".format(ACCOUNT))

def readTXT():
with open('txt.txt', 'r') as f:
print(f.read())
# print( os.listdir() )

MANIFEST.in内容

MANIFEST.in决定了除了Py文件外的什么说明、配置文件会被打包进去(如.txt、.json)

1
2
3
4
5
6
7
8
# Include the README
include *.md

# Include the txt file
include */*.txt

# Include the data files
# recursive-include xxxx/utils */*.json

注意我操作这边时并没有include json文件,所以生成的项目树中也没有任何的json文件(无论是in.json还是out.json),而txt.txt是包含的,并且之后甚至能用readTXT()函数,直接读取打包的txt.txt的内容。

关于MANIFEST.in

项目里会有一些非py文件,比如html和js等,这时候就要靠include_package_datapackage_datapackages来指定了。package_data一般写成 {'your_package_name': ["files"]}。keywords便于pypi索引。

▲.然而只设置了include_package_data还没完,还需要一个MANIFEST.in文件来明确指定哪些文件需要打到包中。===>如果include_package_data=True的话,那么还需要写个MANIFEST.in来明确。

python setup.py install安装

安装后的目录树

MANIFEST.in
│ out.json(这是原来就在的)
README.md
setup.py

├─build(生成的文件夹)
│ ├─bdist.win-amd64
│ └─lib
│ └─files
│ txt.txt
world.py
init.py

├─cltest.egg-info(生成的文件夹)
│ dependency_links.txt
│ PKG-INFO
│ requires.txt
│ SOURCES.txt
│ top_level.txt

├─dist(生成的文件夹)
│ cltest-0.3.0-py3.7.egg

└─files
in.json
txt.txt
world.py
__init__.py

一共生成了3个文件夹builddistcltest.egg-info

  • build\lib下的就是自己想要打包的库的内容,即files里的模块。

  • dist是加入到虚拟环境库下的文件venv\Lib\site-packages中可以找到cltest-0.3.0-py3.7.egg

  • cltest.egg-info中有生成很多文件

    • dependency_links.txt空的

    • PKG-INFO内容是setup.py中设置的,关于库的描述

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        Metadata-Version: 2.1
        Name: cltest
        Version: 0.3.0
        Summary: know how to
        Home-page: UNKNOWN
        Author: Gao Liang
        Author-email: 10630529664@qq.com
        License: MIT
        Description:
        # 测试Setup工具

        hhhh


        Platform: UNKNOWN
        Classifier: License :: OSI Approved :: MIT License
        Classifier: Programming Language :: Python
        Classifier: Programming Language :: Python :: 3
        Classifier: Programming Language :: Python :: 3.6
        Classifier: Programming Language :: Python :: Implementation :: CPython
        Classifier: Programming Language :: Python :: Implementation :: PyPy
        Requires-Python: >=3.0.0
        Description-Content-Type: text/markdown
    • requires.txt:内容是setup.pyinstall_requires=REQUIRED里设置的依赖内容,写法参看requirements.txt

    • SOURCES.txt:列出了所有被打包的文件

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        MANIFEST.in
        README.md
        setup.py
        cltest.egg-info/PKG-INFO
        cltest.egg-info/SOURCES.txt
        cltest.egg-info/dependency_links.txt
        cltest.egg-info/requires.txt
        cltest.egg-info/top_level.txt
        files/__init__.py
        files/txt.txt
        files/world.py
    • top_level.txt:列出了可用的模块,应该是由packages=find_packages(exclude=('tests',)),决定的,由于我只写了一个包files,所以内容也只有

      • 1
        files

如何使用?

这边需要注意的是在setup.py中设置name='cltest'是指的整个库的名字,但真正使用的是要看这个库下有哪些可用的包,包下有哪些模块。

在我的这个例子下面,可以通过库名cltest来找到、下载我这个库。在安装完以后,导入就得知道包名了,这边是files,所以使用得代码应该是

1
2
from files import world
world.readTXT()

这个现象其实还挺常见的,比如beautifulSoup4库,使用的时候是from bs4 import ..;以及python-opencv2库,需要import cv2 as cv。其实就是这边库名与包名的区别。

坑点记录:

1
2
3
4
5
6
7
from files import world
'''
def readTXT():
with open('txt.txt', 'r') as f:
print(f.read())
'''
world.readTXT()

由于’txt.txt’是相对路径,所以只有在运行脚本位置有txt.txt文件时才不会报错,一开始from files import world时报错txt.txt以为时txt.txt没有被打包进去(其实可以通过Sources.txt文件查看到底有没有被打包进去),后来才明白是使用readTXT()的当前目录下不存在而已。

Author: Mrli

Link: https://nymrli.top/2019/06/15/Python的构建工具setup-py/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
Base64编码原理与应用[转]
NextPost >
Oauth2原理、使用
CATALOG
  1. 1. Python的构建工具setup.py
    1. 1.1. setup.py各参数介绍:
    2. 1.2. 实测操作
      1. 1.2.1. 需要事前交代的内容
        1. 1.2.1.1. 模板setup.py
        2. 1.2.1.2. 待打包的目录树:
        3. 1.2.1.3. MANIFEST.in内容
          1. 1.2.1.3.1. 关于MANIFEST.in
      2. 1.2.2. python setup.py install安装
      3. 1.2.3. 如何使用?
      4. 1.2.4. 坑点记录: