跳转到主要内容

定义Lancaster天文观测小组的包

项目描述

Lancstro:创建Python包的示例

此存储库和以下文本旨在作为创建和发布Python包的基本教程。它是为兰卡斯特大学天文观测小组的研讨会而创建的,但可能更广泛适用。

什么是Python包

通常,当我们谈论Python包时,它意味着一组Python模块和/或脚本和/或数据,可以在公共命名空间(包的名称)下安装。包也可以称为库。这与您在文件夹中拥有的单个Python文件集合不同,这些文件不在公共命名空间下,并且只有当它们的路径在您的PYTHONPATH中或您从它们所在的目录中使用它们时才可访问。

以下是一些在物理科学研究中常用的Python包的示例

  1. NumPy
  2. SciPy

注意:“命名空间”基本上是指您导入时包的名称,例如,如果您使用import numpy导入numpy,那么您将通过numpy命名空间访问NumPy的所有函数/类/模块。

numpy.sin(2.3)

一个包可以包含单个命名空间内的所有内容,也可以包含各种子模块,例如包含共同功能且自然适合在其自身命名空间内一起使用的部分。例如,在NumPy中,random子模块包含生成随机数的函数和类。

import numpy
numpy.random.randn()  # generate a normally distributed random number

为什么要对代码进行打包?

那么,为什么你应该打包(并发布)你的Python代码,而不是仅仅拥有本地脚本呢?好吧,有几个原因。

  • 它创建了一个可安装的包,可以导入而不必在您的路径中具有Python脚本/文件。
  • 它创建了一个“版本化”的包,可以指定功能/依赖项。这对于结果的再现性非常重要,其中可以指向用于分析的具体代码版本。
  • 您可以与他人分享您的包(您可以通过PyPI使其pip installable,或通过conda-forge使其conda installable),这在与协作者一起工作时可能很重要。
  • 您将获得开发者的赞誉!软件开发是你在研究期间学会的重要技能之一,所以炫耀你所做的,并将其添加到你的简历中。

项目结构

要创建一个Python包,您应该以以下方式组织包含您代码的目录(包含此信息的目录名称不需要与包名称匹配,但通常它们会匹配)。

repo/
├── LICENSE
├── pyproject.toml
├── README.md
├── setup.cfg
├── setup.py
├── pkgname/
│   ├── __init__.py
│   └── example.py
└── bin/
    └── executable_script.py

还有其他一些小的变化,例如,使用一个src目录,其中包含您的包目录,如官方指南中所述。

在这个项目中,结构如下:

lancstro/
├── LICENSE
├── pyproject.toml
├── README.md
├── setup.cfg
├── setup.py
├── lancstro/
│   ├── __init__.py
│   ├── base.py
|   ├── members/
|   |   ├── __init__.py
|   |   └── staff.py
|   └── data/
|       └── office_numbers.txt
└── bin/
    └── favourite_object.py

在这里,主lancstro包中有一个名为members的子模块。

使用Github

您的包应该在一个版本控制系统中,并且最好托管在一个提供备份的地方。现在使用git进行版本控制非常普遍,并且将项目托管在Github/Gitlab/bitbucket或类似的地方是合理的。在Github上,您可以有公共或私有仓库。

如果您使用Github,最好是首先在那里创建新的仓库,然后克隆该仓库到您的机器上,然后再添加您的代码。在创建Github仓库(我可能会用"repo"来简称)时,您可以初始化它,包含一个许可证文件和一个README文件

注意:这不是一个关于如何使用git的教程,所以您需要在其他地方找到

许可证文件

您应该为您的代码提供一个描述使用条款和版权的许可证。通常您希望您的代码是开源的,因此一个好的选择是MIT许可证,它在代码重用方面非常宽容。有多种其他开源许可证可供选择,尽管这些许可证在宽容度上略有不同,即其他人是否可以在商业和非开源项目中使用您的代码。

LICENSE文件将包含许可证的纯ASCII文本副本。

pyproject.toml文件

此文件告诉用于安装包的pip工具如何构建包。在这个仓库中,我们使用了这里建议的文件内容,这意味着使用了setuptools包进行构建。

README.md文件

您目前正在阅读的文件!它应该提供对您的包的基本描述,可能包括有关如何安装它的信息。理想情况下,它应该简短,不应被视为替代其他地方的适当文档

在这种情况下,文件建议的格式是Markdown(代码扩展名.md),但它可以是纯ASCII文本文件或reStructuredText。如果您在例如Github上托管您的包,Markdown和reStructuredText将被自动渲染。

setup.cfg和setup.py文件

在许多包中,您可能只会看到一个setup.py文件,它是setuptools使用的构建脚本。然而,现在将关于您的包的“静态”元数据放入setup.cfg 配置文件是一种很好的做法。所谓“静态”,指的是在构建过程中无需动态定义的任何包信息(例如定义和构建Cython 扩展)。在许多情况下,例如在这个存储库中,这意味着setup.py文件可以非常简单,只需包含

from setuptools import setup

setup()

配置文件的布局描述在这里。下面我将重现该项目中的配置文件,并添加额外的内联注释

[metadata]
# the name of the package
name = lancstro

# the package author information (multiple authors can just be separated by commas)
author = Matthew Pitkin
author_email = m.pitkin@lancaster.ac.uk

# a brief description of the package
description = Package defining the Lancaster Observational Astronomy group

# the license type and license file
license = MIT
license_files = LICENSE

# a more in-depth description of the project that will appear on it's PyPI page,
# in this case read in from the README.md file
long_description = file: README.md
long_description_content_type = text/markdown

# the projects URL (often the Github repo URL)
url = https://github.com/mattpitkin/lancstro

# standard classifiers giving some information about the project
classifiers =
    Intended Audience :: Science/Research
    License :: OSI Approved :: MIT License
    Natural Language :: English
    Programming Language :: Python
    Programming Language :: Python :: 3
    Programming Language :: Python :: 3.6
    Programming Language :: Python :: 3.7
    Programming Language :: Python :: 3.8
    Programming Language :: Python :: 3.9
    Topic :: Scientific/Engineering
    Topic :: Scientific/Engineering :: Astronomy
    Topic :: Scientific/Engineering :: Physics

# the package's current version (this isn't actually in the file in this repo, see later!)
version = 0.0.1

[options]
# state the Python versions that the package requires/supports
python_requires = >=3.6

# state packages and versions (of necessary) required for running the setup
setup_requires =
    setuptools >= 43
    wheel

# state packages and versions (if necessary) required for installing and using the package
install_requires =
    astropy
    astroquery >= 0.4.3

# automatically find all modules within this package
packages = find:

# include data in the package defined below
include_package_data = True

# any executable scripts to include in the package
scripts =
    bin/favourite_object.py

[options.package_data]
# any data files to include in the package (lancsrto shows they are in the
# lancstro package and then the paths are given)
lancstro = 
    data/office_numbers.txt

有关您可以添加的标准“分类器”列表,请在这里查看。

在这个项目中,我们添加了一个随包捆绑的“数据”文件。在您的包中包含数据不是必需的。

添加包版本

在上面的例子中,包版本是在setup.cfg文件中手动设置的。您可以根据自己的方式定义版本字符串,但通常使用语义版本控制是很好的。在这个格式中,版本由三个由点分隔的完整数字组成:MAJOR.MINOR.PATCH。

语义版本控制网站给出了以下关于何时更改数字的定义

  1. MAJOR版本当您进行不兼容的API更改时,
  2. MINOR版本当您以向后兼容的方式添加功能时,
  3. PATCH版本当您进行向后兼容的错误修复时。

预发布和构建元数据的附加标签可以作为MAJOR.MINOR.PATCH格式的扩展使用。

要更新版本,只需编辑setup.cfg文件中的值。当您安装它时,这将成为包的版本。

这允许包管理器(例如pip)知道已安装的包的版本。然而,通常在包本身内提供一个版本号作为变量是有用的,以便用户在必要时可以检查它。通常您会找到一个名为__version__的变量,例如

import numpy
print(numpy.__version__)
1.21.2

有几种设置方法,但最好确保只有一个地方需要编辑版本号,而不是多个地方。一种方法(在本包中使用)是在包的主要__init__.py文件中包含版本号,通过添加以下行

__version__ = "0.0.1"

然后,在setup.cfg中,version行可以

version = attr: lancstro.__version__

在众多选项中,使用设置版本的工具(如setuptools-scm)是一个好方法,它从您的存储库中的git标签中收集版本信息。

MANIFEST.in文件

您可以使用MANIFEST.in文件来指定要捆绑到软件包源分布中的附加文件。在setuptools的现代版本(例如,大于43的版本)中,默认情况下会自动包括大部分标准文件,例如README文件、setup文件,以及setup.cfg中指定的任何许可文件。因此,在这个仓库中不需要包含MANIFEST.in文件。

然而,您可能还想包含其他文件。例如,如果您有一个包含多个Python测试脚本的test目录,并且希望在包中包含它,您可以添加一个包含以下内容的MANIFEST.in文件:

recursive-include test/ *.py

这将包括test目录下的所有.py文件。

软件包源目录

在这个项目中,包含软件包源代码的目录,即Python文件,被称为lancstro/。在这种情况下,其中有两个文件(尽管它可以包含任意数量的Python文件,每个文件都将是一个可在包中使用的模块)

  1. __init__.py
  2. base.py

base.py文件包含一些Python代码,在这种情况下是一个名为GroupMembers的类,它是我们包的一部分。

__init__.py文件非常重要。它告诉Python这个目录是一个__init__.py文件可以是完全空的,但它必须存在。它可以包含任何Python代码(如果您愿意,可以在__init__.py文件中定义整个包),但通常它用于将子模块/子包中的内容导入到包的命名空间中。在这个例子中,__init__.py文件包含以下代码:

from .base import GroupMember
from . import members

__version__ = "0.0.2"  # the version number of the code

第一行从base.py文件中导入GroupMember类,这样就可以从lancstro命名空间而不是lancstro.base命名空间中使用GroupMember类。例如,这意味着当使用这个包时,我们可以这样做:

from lancstro import GroupMember

而不是这样做:

from lancstro.base import GroupMember

尽管两种方法都可以工作。您可能想要对常用函数或类这样做,但这不是必需的。

lancstro/目录还包含名为members/的目录,它是包的子包(任何子包也必须包含它们自己的__init__.py文件)。__init__.py文件的第二行将members子模块导入到lancstro命名空间中。例如,如果我只做以下操作:

import lancstro

然后我可以使用以下方式访问来自members子包的内容:

lancstro.members.staff

而不是这样做:

from lancstro.members import staff

尽管(再次)两种方法都可以工作。

__init__.py文件的最后一行设置包的版本号

数据目录

您可能想在包中包含一些数据文件,例如用于计算的查找表、目录等。在这种情况下,我已在名为data/的目录(任何名称都可以,但data似乎相当合理)中添加了一个JSON文件,office_numbers.txt。此目录不需要__init__.py,因为它不是一个包。要将此文件包含在包中,您需要在您的setup.cfg文件中包含以下行,并在[options.package_data]部分中列出它,例如:

include_package_data = True

[options.package_data]
lancstro = 
    data/office_numbers.txt

包内引用

在您的包中,您可以使用.表示法从各种子模块/子包中导入内容。

例如,要导入包同一部分的Python文件之间的内容(例如,在lancstro/级别),您可以这样做:

from .base import GroupMember

这将从base.py文件中导入。

如果子包中的文件想要从下一级导入,例如,lancstro/members中的Python文件想要从lancstro/中的文件导入,您可以使用:

from ..base import GroupMember

即,使用两个点..来指定向下移动一个包级别。

bin目录

您可能想在您的软件包中包含可执行脚本。将它们放在名为,例如 bin/ 的目录下是很好的,位于您仓库的根目录中。要使这些脚本成为软件包的一部分,您需要在 setup.cfg 文件中的 scripts 部分列出它们,例如:

scripts =
    bin/favourite_object.py

一旦安装了软件包,这些脚本就应该在您的路径中,并且可以使用,例如:

$ favourite_object.py -h

安装软件包

使用 pip(Python的“包安装器”)安装Python软件包是最佳实践,因此您应该已经安装了它。一旦您有了上述结构,您就可以使用以下命令安装软件包(从其根目录):

pip install .

其中,. 仅指当前目录。标准安装位置在此描述,但我建议使用虚拟环境,例如通过 conda 提供的虚拟环境,在这种情况下,软件包将仅在环境中安装。

就这些!打开 Python 终端(从除软件包目录外的任何位置打开,否则它会感到困惑!)您应该能够做

import lancstro
print(lancstro.__version__)
0.0.1

或从命令行运行 favourite_object.py 脚本

$ favourite_object.py -h
usage: favourite_object.py [-h] name name

Get a staff member's favourite object

positional arguments:
  name        The staff member's full name

optional arguments:
  -h, --help  show this help message and exit

然后您可以告诉其他人克隆您的 Github 仓库并以相同的方式安装东西,或者甚至可以直接从仓库使用 pip install,例如:

$ pip install git+git://github.com/mattpitkin/lancstro.git#egg=lancstro

这些方法将安装来自仓库的最新代码,因此不一定是一个特定的版本(尽管如果已标记版本或从特定的 git hash 工作可以这样做)。

在 PyPI 上发布软件包

与其让人们直接从您的 Github 仓库安装代码,不如发布代码的版本化版本更好。您可以在 PyPI(Python 包索引)仓库上发布 Python 软件包,任何人都可以通过 pip install 安装!

首先,您需要在 PyPI 上 注册一个账户。任何人都可以这样做。其次,您需要安装 twine 软件包,该软件包用于将软件包上传到 PyPI。

在您的仓库根目录中(包含 setup.py)现在可以使用以下命令构建一个包含您的软件包的 Python 轮(软件包的压缩二进制格式,设计用于更快的安装):

python setup.py bdist_wheel sdist

注意:如果您的代码是纯 Python,创建轮应该直接进行,但如果不是,轮生成可能不会工作。在这些情况下,您可以使用以下命令仅构建包含软件包的 tarball:

python setup.py sdist

这应该创建一个包含扩展名为 .whl 的文件的 dist/ 目录(通过包括 bdist_wheel 参数构建)。这是 Python 轮。它还应包含软件包的 tarball(通过包括 sdist 参数构建)。

通常最好首先将这些产品上传到 PyPI 的测试仓库(为此您需要为测试仓库 注册一个单独的账户),这可以使用 twine 通过以下命令完成:

twine upload -r testpypi dist/*

注意:在生成带有 python setup.py bdist_wheel sdist 的新软件包版本之前,请确保 dist/ 目录为空,否则您可能会上传多个版本。

您应该会被提示输入用户名和密码,尽管有方法将这些设置成环境变量或使用keyringkeyring),这样您就不必每次都输入。如果上传成功,您应该能够在Test PyPI网站上看到项目,例如,在https://test.pypi.org/project/lancstro/0.0.2/

您可以通过运行以下命令(可能在新的虚拟环境中)来测试从Test PyPI仓库安装包是否正确:

pip install -i https://test.pypi.org/simple/ lancstro

如果您对包满意,可以使用以下命令将其上传到主PyPI仓库:

twine upload dist/*

Et voilà!现在您只需要告诉人们运行

pip install lancstro

来安装您的。如果他们想安装特定版本,可以使用,例如:

pip install lancstro==0.0.2

或者,如果必须使用特定版本(低于或高于),可以使用不等号运算符,例如:

pip install lancstro<=0.0.2

在conda-forge上发布包

您可以在与您正在工作的特定项目相关的虚拟环境中安装Python包。一个流行的虚拟环境和包管理工具是conda,它是Anaconda的一部分安装(Anaconda)。conda是一个多种软件的包管理器,不仅仅是Python包,因此如果为您的Python项目创建conda包,您可以使其依赖于特定版本的非Python库(也许您想使用GSL的特定版本)!

您可以在Anaconda.org上构建conda包并托管在您的账户中。然而,一个流行的项目托管仓库是conda-forge。将包托管在conda-forge上的一个优势是它将自动通过测试套件进行验证并由实际人员审查,因此希望对其他用户更健壮。

将包上传到conda-forge比上传到PyPI要复杂得多,尽管如果您已经在PyPI上有包,这是一个优势(以下示例将假设您已经有了包)。基本步骤在这里给出,但您需要一个GitHub账户。以下将详细介绍这些步骤。

注意:您需要将包源tarball上传到PyPI,这些说明才能正常工作。

  1. 前往https://github.com/conda-forge/staged-recipes,并将仓库fork到您的账户。

  2. 在您fork的仓库中创建一个新的分支。如果您已经克隆了您的fork,您可能会这样做:

    git checkout -b add_lancstro_to_conda_forge
    
  3. recipes/目录中创建一个新目录,其名称与您的包相同,并将example/目录中的meta.yaml文件复制到其中

    cd recipes
    mkdir lancstro
    cp example/meta.yaml lancstro
    
  4. 在文本编辑器中打开复制的meta.yaml文件,并更改它看起来像以下内容(我删除了很多注释)

{% set name = "lancstro" %}
{% set version = "0.0.1" %}

package:
  name: {{ name|lower }}
  version: {{ version }}

source:
  url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/{{ name }}-{{ version }}.tar.gz
  # get the SHA256 check sum of the file (on the PyPI page for the package
  # click on "Download files" and then "View" under the "Hashes" heading)
  sha256: 2873bb17f5e8cc84ac19e22307cc8567273fcdc57e5dd1f57fe52b2b1a6b1da3

build:
  noarch: python
  number: 0
  script: "{{ PYTHON }} -m pip install . -vv"

requirements:
  host:
    # packages required to build and install the package
    - python
    - pip
    - setuptools
  run:
    # packges required to run the package
    - astropy
    - astroquery >= 0.4.3
    - python

test:
  # make sure the package can at least be imported (other tests can be added)
  imports:
    - lancstro

about:
  home: https://github.com/mattpitkin/lancstro
  license: MIT
  license_family: MIT
  summary: 'My great package'
  description: |
    An example package for showing how to package a package.
  doc_url: https://lancstro.readthedocs.io/
  dev_url: https://github.com/mattpitkin/lancstro

extra:
  recipe-maintainers:
    # github ids for maintainers
    - mattpitkin
  1. 提交更改并将它们推送到您fork的staged-recipes仓库。
  2. 在您的分支和conda-forge的staged-recipes仓库之间打开一个拉取请求(PR)。将PR命名为“添加lancstro”。创建拉取请求。
  3. 一段时间后,检查PR中的测试构建是否成功完成。如果不成功,尝试通过编辑(forked)的meta.yaml文件来修复问题。
  4. 回答并回应分配给您的审阅者的问题/评论(您通常不需要分配审阅者,但有时您需要推动适当的渠道)。
  5. 等待审阅者签署并合并PR。

到这个时候,您的包应该可以从conda-forge安装,例如:

conda install -c conda-forge lancstro

文档

你应尽量避免只为自己编写代码。学术成果应该是透明和可复现的,因此你编写的和使用过的代码应该可供他人使用,所以请编写文档

为你的代码编写文档并不仅仅意味着你的代码应该包含注释(当然,它确实应该包含!),而且还应该在网站上提供关于如何安装和使用你的代码的文档。这应该包括关于代码的API(这只是个术语,意思是展示如何使用你的包中的函数和类)。同样重要的是要有使用案例的示例,因为“展示胜于讲述”通常是个好方法。你可以将文档源文件存储在与你的包相同的仓库中(例如,一个docs/文件夹)。

我不会详细描述如何向一个包中添加文档(我还没有在这个包中添加,但我可能将来会添加!),但我会指出一些资源。两个你可能想了解用于构建文档的包是

  1. Sphinx
  2. mkdocs

这两个都允许你使用Markdown或reStructuredText编写文档,并通过各种扩展/插件自动包含代码文档字符串。它们还可以包含Jupyter笔记本。

对于托管在GitHub上的仓库,你可以在Read the Docs上轻松免费地设置和托管文档构建。你还可以使用GitHub Pages直接在GitHub上发布你的文档

这里有一个使用Sphinx为包编写文档的示例在这里

贡献

你的代码可能是许多开发者的作品。如果是开源的,你也可能允许其他开发者对其进行贡献。因此,你应该提供有关人们如何贡献以及贡献者预期行为的指南。

通常你会在包仓库中看到一个CONTRIBUTING.md Markdown文件,其中描述了如何贡献。如果贡献者想添加/请求一个新功能或修复一个错误,他们可能想打开一个GitHub问题(或在一个适当的论坛上发帖)以查看该功能是否有用或错误是否已知。如果他们已经编写了一个错误修复/功能,那么将其添加到仓库通常涉及一个"分支和拉取请求"工作流程(这是许多项目的流程,例如NumPyastropy

  1. 将仓库分叉到你的GitHub账户
  2. 在你的分叉上创建一个新的分支进行开发
  3. 添加和提交你的更改,确保它们工作并且不会破坏包
  4. 将你的提交推送到你的分叉
  5. 向上游(即原始)仓库创建一个拉取请求
  6. 回应关于更改的任何评论
  7. 将请求合并到原始仓库

行为准则

你还应该考虑在你的项目中添加一个行为准则,概述开发者在互动中应遵守的预期行为。有许多行为准则的示例,你可以直接使用或根据需要修改(许多使用Creative Commons许可证)。

代码风格

您可能希望为您的代码强制执行特定的样式。许多项目遵循 PEP8 样式指南。有一些软件包可以在您的代码上运行,以自动使其符合此样式,例如 blackflake8,因此您应该告诉贡献者运行这些软件包以对提交的任何代码进行检查(并确保您自己运行它们!)您还可以在 Github 上添加 pep8speaks 应用程序,该应用程序将检查任何 pull request 是否符合 PEP8,并通知提交者任何违反风格的情况。

您可以通过使用 pre-commit 软件包将 "pre-commit" 钩子 添加到 git,以强制自动执行检查,例如在提交的代码上自动运行 black。

使代码可引用

您的代码是您学术成果的重要组成部分,因此使其可引用是很好的。这样,当人们使用它时,您可以得到适当的认可,并展示您成果的证据。有各种各样的方法来实现这一点(偏向于天体物理学/物理学)

  • 对于 Github 上的软件包,将您的仓库链接到 Zenodo,它将为您的项目提供可引用的 DOI
  • 将其链接到 天体物理学源代码库(ASCL)。它在 NASA ADS 上有索引,但不提供 DOI。
  • 开源软件杂志(JOSS)撰写一篇论文。这是一项非常轻触,但经过同行评审的出版物,也提供 DOI 并在 NASA ADS 上有索引。它要求您为您的软件包提供适当的文档,因为可接受的文档水平是审查的一部分。
  • 为标准期刊撰写论文。许多期刊(例如 MNRASApJPASP 等)现在接受关于软件的论文,尽管很可能它们也应该包括软件的实用案例描述。

未在此涵盖!

还有许多其他有用的东西我没有在此涵盖。这些包括

  • 使用入口点控制台脚本而不是或同时包括可执行脚本
  • 在您的软件包中包含 C/C++/FORTRAN 代码,或 Cython 化代码
  • 为您的软件包创建一个 测试套件(并检查其 覆盖率
  • 设置 持续集成 以构建和测试(并自动发布)您的代码(例如,使用 Github Actions、TravisCI 等)

我可能稍后会添加这些。

其他资源

有关创建您的 Python 代码的其他描述,请参阅

项目详情


下载文件

下载适合您的平台文件。如果您不确定选择哪个,请了解更多关于 安装软件包 的信息。

源代码分布

lancstro-0.0.2.tar.gz (38.8 kB 查看哈希

上传时间

构建版本

lancstro-0.0.2-py3-none-any.whl (17.3 kB 查看哈希

上传于 Python 3

由以下支持