本文主要内容:
setup.py和setup.cfg的功能和作用
本文主要内容:setuptools的用法
setuptools是python自带的用来构建包的工具,构建出来的wheel(.whl)可供其他人pip install和import。
1 从一个小例子开始
1.1 初始文件(夹)结构
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ tree -L 2
.
├── main.py
└── venv
├── bin
├── include
├── lib
├── lib64 -> lib
└── pyvenv.cfg
5 directories, 2 files
解说:
当前系统Linux-ubuntu,当前终端所在文件夹为IDE-Pycharm构建的新项目文件夹
SetupLearning由于文件内容较多,所以使用命令
tree -L 2查看当前文件夹下,指定最多查看2级目录下的文件夹内容初始情况下,文件夹内部包括
.py文件一个main.py和一个文件夹venv
1.2 小例子
① 文件(夹)结构
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ tree -L 2
.
├── hello.py
├── main.py
└── venv
├── bin
├── include
├── lib
├── lib64 -> lib
└── pyvenv.cfg
5 directories, 3 files
② .py 文件内容
# hello.py
# version 1.0
def say(str):
print("hello", str)
# main.py
# version 1.0
import hello
hello.say('guys') # hello guys
③ 执行命令&结果
在文件夹
~/PycharmProjects/SetupLearning下:
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ python main.py hello guys
在文件夹
~/PycharmProjects下:
(base) t@t-virtual-machine:~/PycharmProjects$ python main.py python: can't open file '/home/t/PycharmProjects/main.py': [Errno 2] No such file or directory
④ 结论
在
main.py文件所在目录下,执行命令,有效输出
在其他文件夹下,执行命令,提示无法找到文件main.py此时,我们可以认为
hello.py相当于一个库函数,简单理解为:通过用户调用(例如用户代码中输入:import hello)才能进行相应的操作
1.3 改进的小例子
接下来,我们将继续修改文件内容,使其可以直接被执行,实现函数功能
① .py 文件内容
# hello.py
# version 2.0
import sys
def say(str):
print('hello', str)
if __name__ == '__main__':
say(sys.argv[1])
② 执行命令&结果
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ python hello.py guys hello guys
③ 解释(原理参见blog)
当
python命令直接运行目标时会把__name__设为字符串__main__,就能进入if所在的条件语句中;而通过导入模块包的形式,即
import时不会执行if所在的条件语句。注意
python hello.py guys的语法中,guys是提供的入口参数(即,say(sys.argv[1]))
2 setuptools 创建包
2.1 基础版
① 创建setup.py文件,键入如下代码块
# setup.py
# version 1.0
import setuptools
setuptools.setup(
name='hellopkg', # 包的名字,可随意取
py_modules=['hello'] # 对应hello.py,也是安装了包之后实际import的模块名字
)
② 运行命令(注意命令最后的点不能遗漏,它代表当前目录)
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ pip install .
③ 命令执行结果
Processing /home/t/PycharmProjects/SetupLearning
Building wheels for collected packages: hellopkg
Building wheel for hellopkg (setup.py) ... done
Created wheel for hellopkg: filename=hellopkg-0.0.0-py3-none-any.whl size=1242 sha256=df4a65d6ebbd11b9b86d2f20ec4b250d6bb3d56872bae79ea61e553140320c6f
Stored in directory: /tmp/pip-ephem-wheel-cache-fxboa8qy/wheels/41/dd/d9/e8cc80e75a9ddfbcd2934da1b5fa23e9c83fc077cdbd96483a
Successfully built hellopkg
Installing collected packages: hellopkg
Successfully installed hellopkg-0.0.0
④ 效果
名为
hellopkg的包已经安装完成
现在可以在任何地方执行包含代码
import hello; hello.say('guys')的.py文件了;也可以命令行调用
python -m hello guys。使用命令
pip list可以看到包的名字是hellopkg,且不存在名叫hello的包;
版本号没指定默认是0.0.0;
python -m hellopkg guys会失败,这说明包的名字(hellopkg,setuptools.setup的name参数值)与安装好了包后能获得的模块名(hello,setuptools.setup的py_modules参数值)是无关的。
⑤ 验证
在
SetupLearning文件夹下:
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ python
Python 3.9.2 (default, Mar 3 2021, 20:02:32)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
>>> hello.say('guys')
hello guys
>>> exit()
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ python -m hello guys
hello guys
在
PycharmProjects文件夹下:
(base) t@t-virtual-machine:~/PycharmProjects$ python -m hello guys
hello guys
(base) t@t-virtual-machine:~/PycharmProjects$ python
Python 3.9.2 (default, Mar 3 2021, 20:02:32)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
>>> hello.say('guys')
hello guys
⑥ 解释
注意
python -m hello guys的语法中,guys是提供的入口参数(即,say(sys.argv[1]))此时的
hello是包(hellopkg)内的模块,相当于(numpy,pandas等),已经被安装在当前项目所在的环境中,故而可以在任何位置使用。
2.2 改进版
① 在命令行中直接用hello xxx输出hello world(重点)
接下来我们实现在命令行中直接用
hi xxx输出hi xxx,为此需要单独把入口函数拿出来,为方便区分,将文件hello.py重命名为hi.py:
# hi.py
# update from hello.py version 2.0
import sys
def say(str):
print('hi', str)
def main():
say(sys.argv[1])
if __name__ == '__main__':
main()
print("Enter into if. i.e., running this file directly")
再修改文件
setup.py,即,添加一行:
# setup.py
# version 2.0
import setuptools
setuptools.setup(
name='hellopkg2', # 注意此时修改了包名为hellopkg2
py_modules=['hi'], # 对应hi.py,也是安装了包之后实际import的模块名字
entry_points={'console_scripts': ['pyhi = hi:main']}
# 定义终端入口点,将产生pyhi.exe,会执行hi模块的main函数
# 注意此时执行的是hi模块的main函数,而没有进入if语句
)
通过命令
pip install .再安装一遍。完成即可在命令行中用pyhi guys输出hi guys了,把pyhi改成hi再装一遍就是hi guys输出hi guys。
② 运行命令,注意那个点不能省,它代表当前目录
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ pip install .
③ 命令执行结果
Processing /home/t/PycharmProjects/SetupLearning
Building wheels for collected packages: hellopkg2
Building wheel for hellopkg2 (setup.py) ... done
Created wheel for hellopkg2: filename=hellopkg2-0.0.0-py3-none-any.whl size=1543 sha256=df95afb1d89ea748be107fcea4b0559993f47f741690ca9c61874eff96b3809a
Stored in directory: /tmp/pip-ephem-wheel-cache-g8sgqc81/wheels/41/dd/d9/e8cc80e75a9ddfbcd2934da1b5fa23e9c83fc077cdbd96483a
Successfully built hellopkg2
Installing collected packages: hellopkg2
Successfully installed hellopkg2-0.0.0
④ 效果
名为
hellopkg2的包已经安装完成
现在可以在任何地方执行命令
pyhi guys
⑤ 验证
在
SetupLearning文件夹下:
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ pyhi guys hi guys
在
PycharmProjects文件夹下:
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ cd .. (base) t@t-virtual-machine:~/PycharmProjects$ pyhi guys hi guys
查看当前所有的package,命令:
pip list
(base) t@t-virtual-machine:~/PycharmProjects$ pip list Package Version ---------------------- ------------------- brotlipy 0.7.0 certifi 2020.12.5 cffi 1.14.5 chardet 4.0.0 conda 4.9.2 conda-package-handling 1.7.2 cryptography 3.3.1 hellopkg 0.0.0 hellopkg2 0.0.0 idna 2.10 pip 21.0.1 pycosat 0.6.3 pycparser 2.20 pyOpenSSL 20.0.1 PySocks 1.7.1 requests 2.25.1 ruamel-yaml-conda 0.15.80 setuptools 52.0.0.post20210125 six 1.15.0 tqdm 4.56.0 urllib3 1.26.3 wheel 0.36.2
卸载名为
hellopkg2的包
(base) t@t-virtual-machine:~/PycharmProjects$ pip uninstall hellopkg2
Found existing installation: hellopkg2 0.0.0
Uninstalling hellopkg2-0.0.0:
Would remove:
/home/t/anaconda3/bin/pyhi
/home/t/anaconda3/lib/python3.9/site-packages/hellopkg2-0.0.0.dist-info/*
/home/t/anaconda3/lib/python3.9/site-packages/hi.py
Proceed (y/n)? y
Successfully uninstalled hellopkg2-0.0.0
2.3 setuptools功能实例
① 查看当前的文件夹结构
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ tree -L 2
.
├── hello.py
├── hi.py
├── main.py
├── __pycache__
│ ├── hello.cpython-39.pyc
│ └── hi.cpython-39.pyc
├── setup.py
└── venv
├── bin
├── include
├── lib
├── lib64 -> lib
└── pyvenv.cfg
6 directories, 7 files
② 执行命令以生成.whl文件
由于linux系统下,存在文件权限,大家也可在Pycharm的Terminal中进行操作
python -m venv .venv创建虚拟环境
cd .venv
cd bin
activate激活当前的虚拟环境
pip install -U pip setuptools wheel
cd ../../返回含有setup.py文件的目录
pip wheel .生成.whl文件
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ cd .venv (base) t@t-virtual-machine:~/PycharmProjects/SetupLearning/.venv$ cd bin (base) t@t-virtual-machine:~/PycharmProjects/SetupLearning/.venv/bin$ activate
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning/.venv/bin$ pip install -U pip setuptools wheel
Requirement already satisfied: pip in /home/t/anaconda3/lib/python3.9/site-packages (21.0.1)
Requirement already satisfied: setuptools in /home/t/anaconda3/lib/python3.9/site-packages (52.0.0.post20210125)
Collecting setuptools
Using cached setuptools-54.1.1-py3-none-any.whl (784 kB)
Requirement already satisfied: wheel in /home/t/anaconda3/lib/python3.9/site-packages (0.36.2)
Installing collected packages: setuptools
Attempting uninstall: setuptools
Found existing installation: setuptools 52.0.0.post20210125
Uninstalling setuptools-52.0.0.post20210125:
Successfully uninstalled setuptools-52.0.0.post20210125
Successfully installed setuptools-54.1.1
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning/.venv/bin$ cd ../../ (base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ pip wheel .
③ 运行结果
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ pip wheel . Processing /home/t/PycharmProjects/SetupLearning Building wheels for collected packages: hellopkg2 Building wheel for hellopkg2 (setup.py) ... done Created wheel for hellopkg2: filename=hellopkg2-0.0.0-py3-none-any.whl size=1543 sha256=5c3561e041d26fa0702649b8b7b0080105414a693132cd9c303a504aad3c91fb Stored in directory: /tmp/pip-ephem-wheel-cache-z5emqes3/wheels/41/dd/d9/e8cc80e75a9ddfbcd2934da1b5fa23e9c83fc077cdbd96483a Successfully built hellopkg2
此时,SetupLearning文件夹下,生成文件
hellopkg2-0.0.0-py3-none-any.whl,这就是打包好的包,用pip install hellopkg-0.0.0-py3-none-any.whl即可安装,也可以发给其他人或者上传到PyPI。传统方法是用
python setup.py bdist_wheel,会生成三个文件夹:
build和xxx.egg-info文件夹是构建时的临时文件及元数据,方便你检查哪些文件被怎样处理了;
dist文件夹中有whl文件,与上条命令一致,其实上调命令就是调用了本条命令。bdist_wheel是一条必须装了wheel包才能使用的verb,用--help-commands可查看所有verb,其中sdist生成源代码压缩包hellopkg2-0.0.0.tar.gz,也能被pip install安装。
3 setup.py & setup.cfg & setuptools
以上就是setuptools和setup.py的最简陋的用法。下面介绍简单但且正常的用法。
3.1 正常情况下源代码应该这样组织:
(base) t@t-virtual-machine:~/PycharmProjects/testSetupTools$ tree -L 2 . ├── hello │ ├── hello_imply.py │ ├── __init__.py │ └── __main__.py ├── main.py ├── Readme.md ├── setup.cfg └── setup.py
3.2 各个文件的内容 实现 setup.py & setup.cfg & __init__.py
# hello_imply.py
def say(str):
print('hi', str)
# __init__.py
from .hello_imply import say
# __main__.py
from . import say
import sys
def main():
print("Enter into [main()]...")
say(sys.argv[1])
if __name__ == '__main__':
print("Enter into [if]...")
main()
# setup.cfg;
# 所有条目见 https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html
[metadata]
name = helloPkgCompleteMain
version = 1.0
[options]
author = xxx
long_description = file: Readme.md # 从文件中读取
license = MIT
url = https://github.com/user/repo
# PyPI的分类,类似于标签,所有条目见 https://pypi.org/pypi?%3Aaction=list_classifiers
classifiers =
Development Status :: 3 - Alpha
Programming Language :: Python :: 3
# 自动搜索存在__init__.py的文件夹作为包
packages = find:
# 依赖,pip安装时靠的就是这个而不是requirements.txt
# install_requires =
# numpy ~= 1.16.1
[options.entry_points]
console_scripts =
# pyhelloOKMain = hello.__main__ # invalid
pyhelloOKMain = hello.__main__:main
# setup.py;
# 会自动读取setup.cfg中的设置
import setuptools
setuptools.setup() # 也可有参调用,则会覆盖.cfg的对应条目
print("Enter into [setup.py]...")
3.3 执行命令&效果
进入
setup.py文件所在目录下,执行命令pip install .或者python setup.py install即可安装包名为helloPkgCompleteMain的包
执行命令pip list可以查看当前所有已安装的包
执行命令pyhelloOKMain string即可运行程序
(base) t@t-virtual-machine:~/PycharmProjects/SetupLearning$ pyhelloOKMain t Enter into [main()]... hi t
3.4 相关提示
使用setup.cfg而不是setup.py的理由是,前者是声明式的配置文件,后者是实际的python代码,可能不安全。
推荐用setup.cfg,除非想结构最精简;未来用pyproject.toml
.egg已经deprecated了,用.whl;dependency_links也弃用了
不需要用distutils,有讨论将它从标准库中移除
打错字很可能没有任何提示,比如entry_points写成entry_point打包时不生效也没有任何报错
whl概念上的“包”和python概念上的“包”是两个概念,前者更类似于“软件包”,后者是存在init.py的文件夹;本文中“包的名字,可随意取”等句说的就是前者
有一些工具能从setup的依赖中生成requirements.txt,但是对于不锁定依赖的简单项目就无所谓了
pip install -e在开发时很有用
setup.py install用的是easy_install,是pip的前身,没必要用
最好在一开始就建立好虚拟环境,方便测试。
4 总结
本文首先利用普通的小例子逐渐引出
setup.py的基础配置;然后本文通过对小例子进行不断改进,梳理了
setup.py与setuptools的基础用法;最后通过一套较为完备的文件系统,展示了
setup.py,setup.cfg和setuptools的实际效用。