Python3基礎之構建setup.py

技術背景

在上一篇博客中,我們介紹了如何使用pyinstaller將python項目打包成一個可執行文件,並且放在系統目錄下,讓系統用戶可以直接識別到我們構造的項目。而python項目中常見的setup.py其實也是在執行類似的構建的功能,通過setup.py文件可以將python包按照指定的方案進行構建,構建出來的可執行文件是一個egg文件。最後將這個egg文件轉移到python包的統一管理路徑下,這樣我們就可以在系統內任一位置的python文件中調用我們構建好的這個python庫。

python項目示例

首先我們構造一個大概的目錄結構,項目的路徑如下所示:

[dechin@dechin-manjaro test_setup]$ tree
.
├── requirements.txt
├── setup.py
└── ts
    └── __init__.py

1 directory, 3 files

在一個名為test_setup的路徑下,作為我們最上層的項目根目錄。然後在根目錄下有需求配置文件requirements.txt,我們可以在這個文件中添加我們的python庫所依賴的其他python庫,如numpyscipy等。而setup.py就是我們這裡的安裝文件,在後面的章節中會着重提到。最後是我們的項目的核心路徑ts,裏面包含了我們的核心代碼。

用__init__.py文件構造的簡單項目

在一個普通的python項目中,我們可以用目錄.模塊名.函數名的形式來構造python項目的引用方法。但是對於一些比較簡單的庫而言,比如定義一個二叉樹的數據結構這種簡單的項目,我們可以直接在__init__.py文件裏面直接定義好所有的項目函數及內容。當然,對於一些比較大型的比較規範的項目而言,也會用__init__.py文件作為一個統一的函數入口,以提升模塊化項目的可用性。在本測試用例中,我們也定義了一個簡單的py核心代碼文件如下:

[dechin@dechin-manjaro test_setup]$ cat ts/__init__.py 
# __init__.py

def p2(number):
    return number ** 2

def p3(number):
    return number ** 3

這個名為ts的項目具有兩個函數功能:p2用於計算輸入參數的平方,以及p3用於計算輸入參數的立方。

構造setup文件

我們主要是基於setuptools來實現一個python項目的構建,以下直接展示本項目的構建方法:

# setup.py

import os
from setuptools import setup, find_packages

__version__ = '1.0' # 版本號
requirements = open('requirements.txt').readlines() # 依賴文件

setup(
    name = 'ts', # 在pip中顯示的項目名稱
    version = __version__,
    author = 'Dechin',
    author_email = '[email protected]',
    url = '',
    description = 'ts: Test Setup',
    packages = find_packages(exclude=["tests"]), # 項目中需要拷貝到指定路徑的文件夾
    python_requires = '>=3.5.0',
    install_requires = requirements # 安裝依賴
        )

在這個構建方法中,我們配置了項目的版本號(版本管理)、依賴庫、項目名稱以及需要進行構建的文件夾。比如這裡我們加了一個exclude的選項排除了tests目錄(雖然本項目中並沒有這個目錄,但是一般我們都要剔除測試目錄)。當然我們也可以用指定目錄進行構建的方法,但是這裡不做過多的贅述。

依賴包配置文件

python之所以這麼火,很大程度上就得益於其強大的生態,而這些生態都是靠別人搭建好的輪子來支撐起來的。因此大部分的python項目都會依賴於第三方的python包,在安裝的時候我們可以僅用一個文件就進行配置:

[dechin@dechin-manjaro test_setup]$ cat requirements.txt 
numpy==1.20.1

requirements.txt的配置文件中,我們最好是能夠指定一個固定的版本號,這樣可以確保軟件的兼容性。

執行安裝

按照上述的方法對我們的python項目進行編寫後,就可以開始執行構建,如果需要測試編譯可以先運行python3 setup.py build來進行測試,在安裝成功後再執行install指令,當然我們也可以直接一步執行python3 setup.py install指令來進行安裝:

[dechin@dechin-manjaro test_setup]$ python3 setup.py install
running install
running bdist_egg
running egg_info
writing ts.egg-info/PKG-INFO
writing dependency_links to ts.egg-info/dependency_links.txt
writing requirements to ts.egg-info/requires.txt
writing top-level names to ts.egg-info/top_level.txt
reading manifest file 'ts.egg-info/SOURCES.txt'
writing manifest file 'ts.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-x86_64/egg
running install_lib
running build_py
creating build/lib
creating build/lib/ts
copying ts/__init__.py -> build/lib/ts
creating build/bdist.linux-x86_64/egg
creating build/bdist.linux-x86_64/egg/ts
copying build/lib/ts/__init__.py -> build/bdist.linux-x86_64/egg/ts
byte-compiling build/bdist.linux-x86_64/egg/ts/__init__.py to __init__.cpython-38.pyc
creating build/bdist.linux-x86_64/egg/EGG-INFO
copying ts.egg-info/PKG-INFO -> build/bdist.linux-x86_64/egg/EGG-INFO
copying ts.egg-info/SOURCES.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying ts.egg-info/dependency_links.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying ts.egg-info/requires.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying ts.egg-info/top_level.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
zip_safe flag not set; analyzing archive contents...
creating 'dist/ts-1.0-py3.8.egg' and adding 'build/bdist.linux-x86_64/egg' to it
removing 'build/bdist.linux-x86_64/egg' (and everything under it)
Processing ts-1.0-py3.8.egg
Copying ts-1.0-py3.8.egg to /home/dechin/anaconda3/lib/python3.8/site-packages
Adding ts 1.0 to easy-install.pth file

Installed /home/dechin/anaconda3/lib/python3.8/site-packages/ts-1.0-py3.8.egg
Processing dependencies for ts==1.0
Searching for numpy==1.20.1
Best match: numpy 1.20.1
Adding numpy 1.20.1 to easy-install.pth file
Installing f2py script to /home/dechin/anaconda3/bin
Installing f2py3 script to /home/dechin/anaconda3/bin
Installing f2py3.8 script to /home/dechin/anaconda3/bin

Using /home/dechin/anaconda3/lib/python3.8/site-packages
Finished processing dependencies for ts==1.0

安裝完成後,我們可以在pip的管理包目錄下找到我們所構建的python包:

[dechin@dechin-manjaro test_setup]$ python3 -m pip list
Package                            Version
---------------------------------- -------------------
ts                                 1.0

同時在執行完build指令之後,本地目錄下會生成一系列的編譯構建目錄,如build和dist等:

[dechin@dechin-manjaro test_setup]$ tree
.
├── build
│   └── bdist.linux-x86_64
├── dist
│   └── ts-1.0-py3.8.egg
├── requirements.txt
├── setup.py
├── ts
│   ├── __init__.py
│   └── __pycache__
│       └── __init__.cpython-38.pyc
└── ts.egg-info
    ├── dependency_links.txt
    ├── PKG-INFO
    ├── requires.txt
    ├── SOURCES.txt
    └── top_level.txt

6 directories, 10 files

而dist目錄下的egg文件在執行完install指令之後,會被拷貝到系統指定的python包管理路徑下,我們可以在系統中搜索到這個文件:

[dechin-root test_setup]# find / -name *ts-1.0-py3.8.egg
/home/dechin/anaconda3/lib/python3.8/site-packages/ts-1.0-py3.8.egg
/home/dechin/projects/2021-python/setup/test_setup/dist/ts-1.0-py3.8.egg

這裡我們可以看到第一個路徑就是python包管理路徑。

軟件包功能測試

在安裝完成後,我們可以在任意的路徑下引用到我們構建好的ts項目,比如這裡我們可以用ipython來測試一下:

[dechin@dechin-manjaro test_setup]$ ipython
Python 3.8.5 (default, Sep  4 2020, 07:30:14) 
Type 'copyright', 'credits' or 'license' for more information
IPython 7.19.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from ts import p2, p3

In [2]: p2(4)
Out[2]: 16

In [3]: p3(4)
Out[3]: 64

測試結果表明,我們成功的從編譯構建好的ts項目中引用了平方和立方的計算函數。

安裝包的刪除

跟其他的python包一樣,我們可以用pip來統一管理,也可以用pip來直接刪除我們自己安裝的ts項目:

[dechin@dechin-manjaro test_setup]$ python3 -m pip uninstall ts
Found existing installation: ts 1.0
Uninstalling ts-1.0:
  Would remove:
    /home/dechin/anaconda3/lib/python3.8/site-packages/ts-1.0-py3.8.egg
Proceed (y/n)? y
  Successfully uninstalled ts-1.0

總結概要

一個完善的python項目,不僅需要梳理好核心代碼的軟件架構,還需要定義好依賴文件、編譯構建文件、API接口文檔、編碼規範門禁等。這裡我們介紹了如何用setup.py文件來完善一個最簡單的python項目,這也是每一個python開源項目所必須要具備的條件。

版權聲明

本文首發鏈接為://www.cnblogs.com/dechinphy/p/setup.html
作者ID:DechinPhy
更多原著文章請參考://www.cnblogs.com/dechinphy/