Python是一种很棒的编程语言,但是打包是它的最弱点之一。 这是社区中众所周知的事实。 多年来,安装,导入,使用和创建程序包已有很大改进,但仍与Go和Rust等较新的语言相提并论,后者从Python和其他成熟语言的斗争中学到了很多东西。

在本教程中,您将学到有关编写,打包和分发自己的程序包所需的所有知识。

如何编写Python库

Python库是组织为Python包的Python模块的相关集合。 通常,这意味着所有模块都位于同一目录下,并且该目录位于Python搜索路径上。

让我们快速编写一个Python 3小程序包并说明所有这些概念。

病理包

Python 3具有出色的Path对象,这是对Python 2笨拙的os.path模块的巨大改进。 但是,它缺少一项关键功能-查找当前脚本的路径。 当您要查找相对于当前脚本的访问文件时,这非常重要。

在许多情况下,脚本可以安装在任何位置,因此您不能使用绝对路径,并且工作目录可以设置为任何值,因此您不能使用相对路径。 如果要访问子目录或父目录中的文件,则必须能够找出当前脚本目录。

使用Python的方法如下:

import pathlib

script_dir = pathlib.Path(__file__).parent.resolve()

要访问当前脚本目录的“数据”子目录中的“ file.txt”文件,可以使用以下代码: print(open(str(script_dir/'data/file.txt').read())

有了病理包,您就有了一个内置的script_dir方法,您可以像这样使用它:

from pathology.Path import script_dir

print(open(str(script_dir()/'data/file.txt').read())

是的,这是一口。 病理包非常简单。 它从pathlib的Path派生自己的Path类,并添加一个始终返回调用脚本路径的静态script_dir()

这是实现:

import pathlib
import inspect

class Path(type(pathlib.Path())):
    @staticmethod
    def script_dir():
        print(inspect.stack()[1].filename)
        p = pathlib.Path(inspect.stack()[1].filename)
        return p.parent.resolve()

由于pathlib.Path的跨平台实现,您可以直接从它派生,并且必须从特定的子类( PosixPathWindowsPath )派生。 脚本目录解析使用检查模块找到调用方,然后找到其文件名属性。

测试病理包

每当您编写的内容不只是一次性脚本时,都应该对其进行测试。 病理模块也不例外。 以下是使用标准单元测试框架的测试:

import os
import shutil 
from unittest import TestCase
from pathology.path import Path


class PathTest(TestCase):
    def test_script_dir(self):
        expected = os.path.abspath(os.path.dirname(__file__))
        actual = str(Path.script_dir())
        self.assertEqual(expected, actual)

    def test_file_access(self):
        script_dir = os.path.abspath(os.path.dirname(__file__))
        subdir = os.path.join(script_dir, 'test_data')
        if Path(subdir).is_dir():
            shutil.rmtree(subdir)
        os.makedirs(subdir)
        file_path = str(Path(subdir)/'file.txt')
        content = '123'
        open(file_path, 'w').write(content)
        test_path = Path.script_dir()/subdir/'file.txt'
        actual = open(str(test_path)).read()

        self.assertEqual(content, actual)

Python路径

必须将Python软件包安装在Python搜索路径上的某个位置,以供Python模块导入。 Python搜索路径是目录列表,并且始终在sys.path可用。 这是我当前的sys.path:

>>> print('\n'.join(sys.path))

/Users/gigi.sayfan/miniconda3/envs/py3/lib/python36.zip
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/lib-dynload
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/site-packages
/Users/gigi.sayfan/miniconda3/envs/py3/lib/python3.6/site-packages/setuptools-27.2.0-py3.6.egg

请注意,输出的第一个空行代表当前目录,因此您可以从当前工作目录导入模块,无论它是什么。 您可以直接在sys.path中添加目录或从中删除目录。

您还可以定义PYTHONPATH环境变量,还有其他几种控制它的方法。 默认情况下包括标准site-packages ,这是您通过pip go安装软件包的地方。

如何打包Python库

现在我们有了代码和测试,让我们将它们打包到一个适当的库中。 Python通过设置模块提供了一种简便的方法。 您在包的根目录中创建一个名为setup.py的文件。 然后,要创建源代码分发,请运行: python setup.py sdist

要创建一个名为wheel的二进制发行版,请运行: python setup.py bdist_wheel

这是病理软件包的setup.py文件:

from setuptools import setup, find_packages

setup(name='pathology',
      version='0.1',
      url='https://github.com/the-gigi/pathology',
      license='MIT',
      author='Gigi Sayfan',
      author_email='the.gigi@gmail.com',
      description='Add static script_dir() method to Path',
      packages=find_packages(exclude=['tests']),
      long_description=open('README.md').read(),
      zip_safe=False)

除了使用从setuptools导入的find_packages()函数查找子软件包的“ packages”项之外,它还包含许多元数据。

让我们建立一个源代码发行版:

$ python setup.py sdist
running sdist
running egg_info
creating pathology.egg-info
writing pathology.egg-info/PKG-INFO
writing dependency_links to pathology.egg-info/dependency_links.txt
writing top-level names to pathology.egg-info/top_level.txt
writing manifest file 'pathology.egg-info/SOURCES.txt'
reading manifest file 'pathology.egg-info/SOURCES.txt'
writing manifest file 'pathology.egg-info/SOURCES.txt'
warning: sdist: standard file not found: should have one of README, README.rst, README.txt

running check
creating pathology-0.1
creating pathology-0.1/pathology
creating pathology-0.1/pathology.egg-info
copying files to pathology-0.1...
copying setup.py -> pathology-0.1
copying pathology/__init__.py -> pathology-0.1/pathology
copying pathology/path.py -> pathology-0.1/pathology
copying pathology.egg-info/PKG-INFO -> pathology-0.1/pathology.egg-info
copying pathology.egg-info/SOURCES.txt -> pathology-0.1/pathology.egg-info
copying pathology.egg-info/dependency_links.txt -> pathology-0.1/pathology.egg-info
copying pathology.egg-info/not-zip-safe -> pathology-0.1/pathology.egg-info
copying pathology.egg-info/top_level.txt -> pathology-0.1/pathology.egg-info
Writing pathology-0.1/setup.cfg
creating dist
Creating tar archive
removing 'pathology-0.1' (and everything under it)

该警告是因为我使用了非标准的README.md文件。 可以忽略。 结果是dist目录下的tar压缩文件:

$ ls -la dist
total 8
drwxr-xr-x   3 gigi.sayfan  gigi.sayfan   102 Apr 18 21:20 .
drwxr-xr-x  12 gigi.sayfan  gigi.sayfan   408 Apr 18 21:20 ..
-rw-r--r--   1 gigi.sayfan  gigi.sayfan  1223 Apr 18 21:20 pathology-0.1.tar.gz

这是一个二进制分布:

$ python setup.py bdist_wheel
running bdist_wheel
running build
running build_py
creating build
creating build/lib
creating build/lib/pathology
copying pathology/__init__.py -> build/lib/pathology
copying pathology/path.py -> build/lib/pathology
installing to build/bdist.macosx-10.7-x86_64/wheel
running install
running install_lib
creating build/bdist.macosx-10.7-x86_64
creating build/bdist.macosx-10.7-x86_64/wheel
creating build/bdist.macosx-10.7-x86_64/wheel/pathology
copying build/lib/pathology/__init__.py -> build/bdist.macosx-10.7-x86_64/wheel/pathology
copying build/lib/pathology/path.py -> build/bdist.macosx-10.7-x86_64/wheel/pathology
running install_egg_info
running egg_info
writing pathology.egg-info/PKG-INFO
writing dependency_links to pathology.egg-info/dependency_links.txt
writing top-level names to pathology.egg-info/top_level.txt
reading manifest file 'pathology.egg-info/SOURCES.txt'
writing manifest file 'pathology.egg-info/SOURCES.txt'
Copying pathology.egg-info to build/bdist.macosx-10.7-x86_64/wheel/pathology-0.1-py3.6.egg-info
running install_scripts
creating build/bdist.macosx-10.7-x86_64/wheel/pathology-0.1.dist-info/WHEEL

病理软件包仅包含纯Python模块,因此可以构建通用软件包。 如果您的软件包包括C扩展名,则必须为每个平台构建一个单独的轮子:

$ ls -la dist
total 16
drwxr-xr-x   4 gigi.sayfan  gigi.sayfan   136 Apr 18 21:24 .
drwxr-xr-x  13 gigi.sayfan  gigi.sayfan   442 Apr 18 21:24 ..
-rw-r--r--   1 gigi.sayfan  gigi.sayfan  2695 Apr 18 21:24 pathology-0.1-py3-none-any.whl
-rw-r--r--   1 gigi.sayfan  gigi.sayfan  1223 Apr 18 21:20 pathology-0.1.tar.gz

要深入了解打包Python库的主题,请参阅如何编写自己的Python包

如何分发Python软件包

Python有一个称为PyPI(Python包索引)的中央包存储库。 当您使用pip安装Python软件包时,它将从PyPI下载该软件包(除非您指定其他存储库)。 要分发病理包,我们需要将其上传到PyPI并提供PyPI所需的一些其他元数据。 这些步骤是:

  • 在PyPI上创建一个帐户(仅一次)。
  • 注册您的包裹。
  • 上载您的包裹。

创建一个帐户

您可以在PyPI网站上创建一个帐户。 然后在您的主目录中创建一个.pypirc文件:

[distutils] 
index-servers=pypi
 
[pypi]
repository = https://pypi.python.org/pypi
username = the_gigi

出于测试目的,您可以在中添加一个“ pypitest”索引服务器。 pypirc文件:

[distutils]
index-servers=
    pypi
    pypitest

[pypitest]
repository = https://testpypi.python.org/pypi
username = the_gigi

[pypi]
repository = https://pypi.python.org/pypi
username = the_gigi

注册您的包裹

如果这是软件包的第一个版本,则需要在PyPI中注册。 使用setup.py的register命令。 它将要求您输入密码。 请注意,我在这里将其指向测试存储库:

$ python setup.py register -r pypitest
running register
running egg_info
writing pathology.egg-info/PKG-INFO
writing dependency_links to pathology.egg-info/dependency_links.txt
writing top-level names to pathology.egg-info/top_level.txt
reading manifest file 'pathology.egg-info/SOURCES.txt'
writing manifest file 'pathology.egg-info/SOURCES.txt'
running check
Password:
Registering pathology to https://testpypi.python.org/pypi
Server response (200): OK

上传您的包裹

现在已经注册了软件包,我们可以上传它了。 我建议使用twine ,它更安全。 使用pip install twine照常pip install twine 。 然后使用麻线上传您的包裹并提供密码(如下所示):

$ twine upload -r pypitest -p <redacted> dist/*
Uploading distributions to https://testpypi.python.org/pypi
Uploading pathology-0.1-py3-none-any.whl
[================================] 5679/5679 - 00:00:02
Uploading pathology-0.1.tar.gz
[================================] 4185/4185 - 00:00:01

要更深入地了解分发软件包的主题,请查看如何共享Python软件包

结论

在本教程中,我们经历了编写Python库,打包并通过PyPI分发的完整过程。 此时,您应该拥有所有工具来与世界一起编写和共享您的库。

此外,请不要犹豫,看看我们在市场上有哪些出售和研究的产品 ,请使用下面的提要来问任何问题并提供宝贵的反馈。

翻译自: https://code.tutsplus.com/tutorials/how-to-write-package-and-distribute-a-library-in-python--cms-28693