python项目打包(二)------ 利用setup.py打包项目

python项目打包成可安装的package


作者:elfin   资料来源:setuptools



1、资源介绍

关于python如何将一个项目打包成安装包,官网有详细的教程,这里是基于此教程做的一个demo。

资料PyPA » Python Packaging User Guide » Guides » Packaging and distributing projects

项目打包与项目发布--Packaging and distributing projects

  • 本文介绍如何配置、打包和分发您自己的Python项目的基础知识。并假设您已经熟悉安装包的内容。

  • 本文的目的不是从整体上介绍Python项目开发的最佳实践。例如,它不为版本控制、文档或测试提供指导或工具建议。

  • 有关更多参考资料,请参阅setuptools文档中的Building and distribution Packages,但请注意,其中的一些建议内容可能已经过时。如果发生冲突,请参阅Python打包用户指南中的建议。


Top  ---  Bottom

2、环境准备

​ 要打包自己的python项目,那么肯定第一步得先有一个python环境,这里默认环境已经准备好了,关于这部分内容可以参考:

​ 如果要发布打包好的python项目,我们需要安装twine这个库:

  • pip install twine
    

Top  ---  Bottom

3、项目配置

在项目配置前,我们需要明确:以下文件是你需要进行创建的:

项目根路径

  • [setup.py](#3.1 setup.py简介)
  • [setup.cfg](#3.2 setup.cfg简介)
  • [README.rst / README.md](#3.3 README文件)
  • [MANIFEST.in](#3.4 MANIFEST.in)
  • [LICENSE.txt](#3.5 LICENSE.txt简介)
  • [<你的项目>](#3.6 <你的项目>)

3.1 setup.py简介

​ 最重要的文件是setup.py它存在于项目目录的根目录中。有关示例,请参见在PyPA sample project中的 setup.py 。

setup.py的两个主要功能:

  • 它是配置项目各个方面的文件。它的主要特点是它包含一个全局setup()函数。此函数的关键字参数是定义了项目的特定细节是什么样的。最相关的论点将在下面一节中解释。

  • 它是用于运行与打包任务相关的各种命令的命令行接口。要获取可用命令的列表,请运行python setup.py --help-commands命令。此命令是基于项目的setup.py进行help命令提示的!以下代码段是官方测试项目的help结果:

    >>> 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
    
    Extra commands:
      bdist_wheel       create a wheel distribution
      alias             define a shortcut to invoke one or more commands
      bdist_egg         create an "egg" distribution
      develop           install package in 'development mode'
      dist_info         create a .dist-info directory
      easy_install      Find/get/install Python packages
      egg_info          create a distribution's .egg-info directory
      install_egg_info  Install an .egg-info directory for the package
      rotate            delete older distributions, keeping N newest files
      saveopts          save supplied options to setup.cfg or other config file
      setopt            set an option in setup.cfg or another config file
      test              run unit tests after in-place build (deprecated)
      upload_docs       Upload documentation 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
    

    这一段项目并没有声明,应该是setuptools所做的标准工作!


Top  ---  Bottom

3.2 setup.cfg简介

​ setup.cfg是包含选项默认值的ini文件。如 PyPA sample project中的 setup.cfg 。

[metadata]
# This includes the license file(s) in the wheel.
# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file
license_files = LICENSE.txt

Top  ---  Bottom

3.3 README文件

​ 这个文件是项目的介绍文件,一般在Github上在主页显示,在python库中就是用于描述的长文本,会显示与库的主页。这里支持rstmd两种格式。

注意:如果项目使用的setuptools>=0.6.27,则有标准的自诉文件(README.rst, README.txt, or README),默认情况下包含在发布源中。内置的distutils库从python3.7开始采用这种方式。此外,setuptools 36.4.0+将包括README.md如果找到了。如果使用setuptools,你不需要将readme文件罗列到MANIFEST.in文件中。


Top  ---  Bottom

3.4 MANIFEST.in

​ 当您需要打包未自动包含在源分发中的其他文件时,需要清单文件。有关编写清单文件的详细信息,包括默认情况下包含的内容的列表,请参阅“Including files in source distributions with MANIFEST.in”。

​ 当我们对项目构建源代码发布版时,只有部分文件被默认包含在内。你很有可能希望包含其他文件在这个源代码中,如authors/contributors文件、介绍文件夹、数据集等。最初README文件没有默认添加时,对于长描述文字,我们还要手动将其添加到MANIFEST.in文件中。

​ 通过编写项目根路径下的MANIFEST.in文件,可以添加、删除文件(在打包时)。

项目打包时,默认包含的文件:

  • 通过setup()参数packages参数py_modules包含的所有的python文件;
  • 通过和 setup()参数libraries和参数ext_modules提到的所有的C文件;
  • 通过scripts setup()参数指定的脚本;
  • 通过package_datadata_files setup()参数指定的所有文件;
  • 通过setup.cfg (setuptools 40.8.0+)中的license_file 可选项指定的文件;
  • 通过setup.cfg (setuptools 42.0.0+)中的license_files 可选项指定的所有文件;
  • 所有满足test/test*.py模式的文件;
  • setup.py;
  • setup.cfg;
  • README;
  • README.txt;
  • README.rst (Python 3.7+ or setuptools 0.6.27+);
  • README.md (setuptools 36.4.0+);
  • pyproject.toml (setuptools 43.0.0+);
  • MANIFEST.in;

关于此文件的添加、删除命令可参考官网


Top  ---  Bottom

3.5 LICENSE.txt简介

​ 每个软件包都应该包含一个许可证文件,详细说明分发条款。在许多司法管辖区,没有明确许可证的软件包不能由版权持有人以外的任何人合法使用或分发。如果您不确定要选择哪个许可证,可以参考GitHub的“选择许可证”或咨询律师等资源。


Top  ---  Bottom

3.6 <你的项目>

​ 尽管这不是必需的,但最常见的做法是将Python模块和包放含在一个根目录下的包中,该包与您的项目同名或非常接近。


Top  ---  Bottom

4、setup函数的参数

​ 如上所述setup.py它包含一个全局setup()函数。此函数的关键参数本章详细介绍。更多可参考PyPA sample project

4.01 name

​ 定制项目的名字,它决定了你的项目在PyPI的陈列。名字必须满足下列要求:

  • 只能包含ASCII编码的字母数字下划线连字符(-)句号(.)
  • 并且只能由ASCII编码的字母和数字开头。

注意事项:

  • 名字不区分大小写;
  • 将任意长的下划线、连字符和/或句点视为相等。

Top  ---  Bottom

4.02 version

​ 这个关键字见名知义,是当前项目的版本。

​ 这是您的项目的当前版本,允许您的用户确定他们是否有最新版本,并指示他们已针对哪些特定版本测试了自己的软件。

​ 如果发布项目,则每个版本的release都会显示在PyPI上。

​ 有关如何使用version向用户传递兼容性信息的更多信息,请参见选择版本控制方案


Top  ---  Bottom

4.03 description(可选)

项目描述,如果你发布到PyPI,则会展示到项目主页。关于此描述,你可以随意配置。

一般我们会使用README文件,如:

from setuptools import setup, find_packages
here = pathlib.Path(__file__).parent.resolve()

long_description = (here / 'README.md').read_text(encoding='utf-8')
setup(
    ……
    description='A sample Python project',
    long_description=long_description,
    long_description_content_type='text/markdown',
    ……
)

4.04 url(可选)

你的项目主页,一般我们的项目都放在Github上面,根据你的实际情况设置就好了!

4.05 auther(可选)

注明开发者的相关信息

author='A. Random Developer',
author_email='author@example.com',

4.06 licence协议(可选)

这个参数建议一定要设置。你如果不想设置,也没多大个事!

4.07 classifiers(可选)

classifiers=[  # Optional
    # How mature is this project? Common values are
    #   3 - Alpha
    #   4 - Beta
    #   5 - Production/Stable
    'Development Status :: 3 - Alpha',

    # Indicate who your project is intended for
    'Intended Audience :: Developers',
    'Topic :: Software Development :: Build Tools',

    # Pick your license as you wish
    'License :: OSI Approved :: MIT License',

    # Specify the Python versions you support here. In particular, ensure
    # that you indicate you support Python 3. These classifiers are *not*
    # checked by 'pip install'. See instead 'python_requires' below.
    'Programming Language :: Python :: 3',
    'Programming Language :: Python :: 3.6',
    'Programming Language :: Python :: 3.7',
    'Programming Language :: Python :: 3.8',
    'Programming Language :: Python :: 3.9',
    'Programming Language :: Python :: 3 :: Only',
],

​ 提供对项目进行分类的分类器列表。关于项目可配置的的参数可以参考官网列表。官网说明了CUDA、系统、框架等基本参数说明。classifiers实际上是用于项目搜索时的一个配置参数,你在classifiers设置的关键字有助于别人搜索到你的项目。

​ 尽管分类器列表通常用于声明项目支持的Python版本,但此信息仅用于搜索和浏览PyPI上的项目,而不用于安装项目。要实际限制项目可以安装在哪些Python版本上,请使用Python_requires参数。

4.08 keywords(可选)

描述项目的关键字,如Github中右侧项目名下的关键字!

4.09 project_urls(可选)

project_urls={
    'Documentation': 'https://packaging.python.org/tutorials/distributing-packages/',
    'Funding': 'https://donate.pypi.org',
    'Say Thanks!': 'http://saythanks.io/to/example',
    'Source': 'https://github.com/pypa/sampleproject/',
    'Tracker': 'https://github.com/pypa/sampleproject/issues',
},

列出有关项目的其他相关URL。这是链接到bug跟踪器、源代码存储库或支持包开发的地方。key的字符串是将在PyPI上显示精确文本。

4.10 packages

packages=find_packages(include=['sample', 'sample.*']),

packages参数用于设置哪些包需要被打包!


Top  ---  Bottom

4.11py_modules

py_modules=["six"],

若你的项目中有单独的python文件,如在根目录下有py文件,你需要使用这个参数让setuptools知道你要打包这些文件。

4.12 install_requires(可选)

install_requires=['peppercorn'],

我们设置需要安装的依赖!如果你有依赖,这里根据实际情况设置就好了!

4.13 package_dir(可选)

package_dir={'': 'src'},  # Optional

若你的项目package是根目录下的一个文件夹,就需要设置这个参数

4.14 python_requires

python_requires='>=3',
# or 如果您的软件包是针对Python3.3及更高版本的,但您还不愿意承诺支持Python4
python_requires='~=3.3',

指定python的版本需求。

4.15 package_data(可选)

package_data={
    'sample': ['package_data.dat'],
},

​ 通常,需要在包中安装其他文件。这些文件通常是与包的实现密切相关的数据,或者是包含使用包的程序员可能感兴趣的文档的文本文件。这些文件称为“包数据”。

​ 该值必须是从包名到应复制到包中的相对路径名列表的映射。路径被解释为相对于包含包的目录。

4.16 data_files(可选)

data_files=[('my_data', ['data/data_file'])],

​ 如果你的数据位于项目之外,那么你就需要配置data_files参数来实现了!如果您需要安装其他程序使用的文件,而这些程序可能不知道Python包,那么它非常有用。

​ 如上面的配置会将my_data创建在库的根目录下。

4.17 scripts、entry_points、console_scripts(可选)

一般都是使用console_scripts

entry_points={  # Optional
    'console_scripts': [
        'sample=sample:main',
    ],
},

Top  ---  Bottom

5、案例

这里我们准备了一个绘制混淆矩阵的python项目,项目的依赖比较简单,只依赖于python库:sklearn、matplotlib、seaborn、pandas。
项目已经发布到github上,欢迎下载测试,地址:https://github.com/firstelfin/confusion

5.1 README.md准备

在Confusion项目根目录下准备README文件,这里我习惯使用MD文件,具体可参考项目Confusion。

5.2 书写setup.py文件

#!/usr/bin/python3
# -*- coding:utf-8 -*-
# Author: elfin-2020
# @Time: 2021/1/28 9:37
# project: confusion

from setuptools import setup, find_packages
import pathlib

here = pathlib.Path(__file__).parent.resolve()
# Get the long description from the README file
long_description = (here / 'README.md').read_text(encoding='utf-8')

setup(
    name="confusion",
    version="1.0.0",
    description='A confusion matrix display project for python',
    long_description=long_description,
    long_description_content_type='text/markdown',
    url="https://github.com/firstelfin/Confusion",
    author='firstelfin',
    classifiers=[
        "Natural Language :: English",
        "Natural Language :: Chinese(Simplified)",
        "Natural Language :: Chinese(Traditional)",
        "Programming Language :: python :: 3",
    ],
    keywords='ConfusionMatrix, Display',
    package_dir={'': 'confusion'},
    packages=find_packages(where='confusion'),
    py_modules=["display"],
    python_requires="~=3.6",
    install_requires=["sklearn", "matplotlib", "seaborn", "pandas"],
    entry_points={
        'console_scripts': [
            'confusion=confusion.display:main', # 这一行代码有问题后面会修改
        ],
    },
)

5.3 尝试在windows10上安装

先进行创建:

python setup.py build

运行结果:

running build
running build_py
creating build
creating build\lib
copying confusion\display.py -> build\lib
creating build\lib\utils
copying confusion\utils\Draw.py -> build\lib\utils
copying confusion\utils\__init__.py -> build\lib\utils

这里我在之前的虚拟环境上测试,python=3.6,依赖是已经满足的。如上创建setup.py后,项目的目录结构如下:

由setup.py可知,打包的项目package_dir={'': 'confusion'}是图中的蓝色文件夹,你安装后的库名就是设置的confusion,注意你如果指定的package_dir名字和你的项目name不一样,你使用name导入会报错!

5.4 查看build的目录结构

5.5 安装与测试

进入虚拟环境安装:

#安装命令:
python setup.py install

运行结果:

点击展开代码
running install
running bdist_egg
running egg_info
creating confusion\confusion.egg-info
writing confusion\confusion.egg-info\PKG-INFO
writing dependency_links to confusion\confusion.egg-info\dependency_links.txt
writing entry points to confusion\confusion.egg-info\entry_points.txt
writing requirements to confusion\confusion.egg-info\requires.txt
writing top-level names to confusion\confusion.egg-info\top_level.txt
writing manifest file 'confusion\confusion.egg-info\SOURCES.txt'
reading manifest file 'confusion\confusion.egg-info\SOURCES.txt'
writing manifest file 'confusion\confusion.egg-info\SOURCES.txt'
installing library code to build\bdist.win-amd64\egg
running install_lib
running build_py
creating build\bdist.win-amd64
creating build\bdist.win-amd64\egg
copying build\lib\display.py -> build\bdist.win-amd64\egg
creating build\bdist.win-amd64\egg\utils
copying build\lib\utils\Draw.py -> build\bdist.win-amd64\egg\utils
copying build\lib\utils\__init__.py -> build\bdist.win-amd64\egg\utils
byte-compiling build\bdist.win-amd64\egg\display.py to display.cpython-36.pyc
byte-compiling build\bdist.win-amd64\egg\utils\Draw.py to Draw.cpython-36.pyc
byte-compiling build\bdist.win-amd64\egg\utils\__init__.py to __init__.cpython-36.pyc
creating build\bdist.win-amd64\egg\EGG-INFO
copying confusion\confusion.egg-info\PKG-INFO -> build\bdist.win-amd64\egg\EGG-INFO
copying confusion\confusion.egg-info\SOURCES.txt -> build\bdist.win-amd64\egg\EGG-INFO
copying confusion\confusion.egg-info\dependency_links.txt -> build\bdist.win-amd64\egg\EGG-INFO
copying confusion\confusion.egg-info\entry_points.txt -> build\bdist.win-amd64\egg\EGG-INFO
copying confusion\confusion.egg-info\requires.txt -> build\bdist.win-amd64\egg\EGG-INFO
copying confusion\confusion.egg-info\top_level.txt -> build\bdist.win-amd64\egg\EGG-INFO
zip_safe flag not set; analyzing archive contents...
creating dist
creating 'dist\confusion-1.0.0-py3.6.egg' and adding 'build\bdist.win-amd64\egg' to it
removing 'build\bdist.win-amd64\egg' (and everything under it)
Processing confusion-1.0.0-py3.6.egg
Copying confusion-1.0.0-py3.6.egg to l:\elfin-project\confusiontest\venv\lib\site-packages
Adding confusion 1.0.0 to easy-install.pth file
Installing confusion-script.py script to L:\elfin-project\ConfusionTest\venv\Scripts
Installing confusion.exe script to L:\elfin-project\ConfusionTest\venv\ScriptsInstalled l:\elfin-project\confusiontest\venv\lib\site-packages\confusion-1.0.0-py3.6.egg
Processing dependencies for confusion==1.0.0
Searching for pandas==1.1.5
Best match: pandas 1.1.5
Adding pandas 1.1.5 to easy-install.pth fileUsing l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for seaborn==0.11.1
Best match: seaborn 0.11.1
Adding seaborn 0.11.1 to easy-install.pth fileUsing l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for matplotlib==3.3.3
Best match: matplotlib 3.3.3
Adding matplotlib 3.3.3 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for sklearn==0.0
Best match: sklearn 0.0
Adding sklearn 0.0 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for python-dateutil==2.8.1
Best match: python-dateutil 2.8.1
Adding python-dateutil 2.8.1 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for pytz==2020.5
Best match: pytz 2020.5
Adding pytz 2020.5 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for numpy==1.19.5
Best match: numpy 1.19.5
Adding numpy 1.19.5 to easy-install.pth file
Installing f2py-script.py script to L:\elfin-project\ConfusionTest\venv\Scripts
Installing f2py.exe script to L:\elfin-project\ConfusionTest\venv\Scripts
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for scipy==1.5.4
Best match: scipy 1.5.4
Adding scipy 1.5.4 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for kiwisolver==1.3.1
Best match: kiwisolver 1.3.1
Adding kiwisolver 1.3.1 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for cycler==0.10.0
Best match: cycler 0.10.0
Adding cycler 0.10.0 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for Pillow==8.1.0
Best match: Pillow 8.1.0
Adding Pillow 8.1.0 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for pyparsing==2.4.7
Best match: pyparsing 2.4.7
Adding pyparsing 2.4.7 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for scikit-learn==0.24.1
Best match: scikit-learn 0.24.1
Adding scikit-learn 0.24.1 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for six==1.15.0
Best match: six 1.15.0
Adding six 1.15.0 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for joblib==1.0.0
Best match: joblib 1.0.0
Adding joblib 1.0.0 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Searching for threadpoolctl==2.1.0
Best match: threadpoolctl 2.1.0
Adding threadpoolctl 2.1.0 to easy-install.pth file
Using l:\elfin-project\confusiontest\venv\lib\site-packages
Finished processing dependencies for confusion==1.0.0

这里安装了很多包,而且没有报错,最后显示Finished processing dependencies for confusion==1.0.0

测试:

这里截屏软件没有将绘制的图形截取到……

至此项目安装完成!

5.6 entry_points生成的exe测试

这里测试confusion.exe报错:

Traceback (most recent call last):
  File "L:\elfin-project\ConfusionTest\venv\Scripts\confusion-script.py", line 33, in <module>
    sys.exit(load_entry_point('confusion==1.0.0', 'console_scripts', 'confusion')())
  File "L:\elfin-project\ConfusionTest\venv\lib\site-packages\pkg_resources\__init__.py", line 474, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "L:\elfin-project\ConfusionTest\venv\lib\site-packages\pkg_resources\__init__.py", line 2846, in load_entry_point
    return ep.load()
  File "L:\elfin-project\ConfusionTest\venv\lib\site-packages\pkg_resources\__init__.py", line 2450, in load
    return self.resolve()
  File "L:\elfin-project\ConfusionTest\venv\lib\site-packages\pkg_resources\__init__.py", line 2456, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
ModuleNotFoundError: No module named 'confusion'

执行完confusion.exe,回到python环境,查看confusion库是存在的,但是不能导入了!!难道是两个指令有冲突?

修改entry_points里面的key:

entry_points={
    'console_scripts': [
        'confusionDraw=confusion.display:main',
    ],
},

将build步骤参数的文件删除干净,再次build:

​ 再次创建后仍然是这样的问题,安装好之后,使用confusion是正常的,但是使用confusionDraw.exe后就不能导入confusion库了,在pycharm里面测试(其他项目下)也不能使用。经过不断地测试,最后发现问题了,我们这里不能使用confusion.display:main,在项目的display文件中,导入包的时候也一并将confusion删除。即这里我进行了两个操作:

  • from confusion.utils.Draw import ConfusionMatrixHeatMap as CH修改为:from utils.Draw import ConfusionMatrixHeatMap as CH
  • 将entry_points中console_scripts的value修改为display:main

再次删除build产生的包,并再次安装:

其他项目中使用此虚拟环境测试:


Top  ---  Bottom

完!

posted @ 2021-01-28 16:46  巴蜀秀才  阅读(3551)  评论(0编辑  收藏  举报