当前位置:   article > 正文

Python 高级编程(第2版)--第5章 编写一个包_python 关于写成一个包并且能够解决路径问题

python 关于写成一个包并且能够解决路径问题

编写一个包

创建一个包

Python打包工具的混乱状态

从 1998 年引入的 distutils 包开始,随后在 2003 年 setuptools 对其进行改进。包括出现的其他新项目。最后,Python Packaging Authority (PyPA)组织,将秩序和组织性带回到打包生态系统中。PyPA 维护的 Python 打包用户指南(Python Packaging User Guide)是关于最新打包工具和最佳实践的权威信息来源。

  • 由于 PyPA 的存在,Python 打包的现状。

    PyPA 除了提供一份权威的打包指南之外,还维护着打包项目与新的官方打包的标准化过程。由于 PyPA 的参与,构建发行版已经正在逐步弃用 egg 格式,而是支持使用 wheel 格式。未来可能会为我们带来全新的方法(fresh breath)。

  • 工具推荐。

    Python 打包用户指南有关使用包的推荐工具给出了一些建议。这些工具大体可分为两组:用于安装包的工具和用于包的创建与分发的工具。PyPA 推荐的第一组实用工具如下:

    • 使用 pip 安装来自 PyPI 的包。
    • 将 virtualenv 或 venv 用于 Python 环境的应用级隔离。

    在 Python 包用户指南中,推荐包的创建与分发的工具如下:

    • 使用 setuptools 来定义项目并创建源代码发行版(source distributions)。
    • 使用 wheel 而不是 egg 来创建构建发行版(built distributions)。
    • 使用 twine 向 PyPI 上传包的发行版。

项目配置

很显然,组织大型应用的代码的最简单方法是将其分成几个包。这使得代码更加简单,也更容易理解、维护和修改。这样也使每个包的可复用性最大化。它们的作用就像组件一样。

  • setup.py

    对于一个需要被分发的包来说,其根目录包含一个 setup.py 脚本。它定义了 distutils 模块中描述的所有元数据,并将其合并为标准的 setup() 函数调用的参数。虽然 distutils 是一个标准库模块,但建议你使用 setuptools 包来代替,它对标准的 distutils 做了一些改进。

  • setup.cfg

    setup.cfg 文件包含 setup.py 脚本命令的默认选项。如果构建和分发包的过程更加复杂,并且需要向 setup.py 命令中传入许多可选参数,那么这个文件非常有用。setup.cfg 文件的语法与内置 configparser 模块提供的语法相同,因此它类似于常见的 Microsoft Windows INI 文件。

  • MANIFEST.in

    使用 sdist 命令构建发行版时,distutils 将浏览包的目录,查找需要包含在存档中的文件。distutils 将包含:

    • py_modules、packages 和 scripts 选项隐含的所有 Python 源文件。
    • ext_modules 选项列出的所有 C 源文件。

    匹配 glob 模式 test/test*.py 的文件包括:README、README.txt、setup.py 和 setup.cfg。此外,如果你的包是由 subversion 或 CVS 管理,那么 sdist 将浏览诸如 .svn 之类的文件夹,查找需要包含的文件。利用扩展也可以与其他版本控制系统集成。sdist 将构建一个 MANIFEST 文件,列出所有文件并将它们包含在存档中。假设你不使用这些版本控制系统,并且需要包含更多的文件。现在,在与 setup.py 相同的目录中,你可以为 MANIFEST 文件定义一个名为 MANIFEST.in 的模板,在其中你可以指定 sdist 要包含哪些文件。

  • 最重要的元数据。

    除了被分发包的名称和版本之外,setup 可以接受的最重要的参数包括:

    • description:包含描述包的几句话。
    • long_description:包含完整说明,可以使用 reStructuredText 格式。
    • keywords:定义包的关键字列表。
    • author:作者的姓名或组织。
    • author_email:联系人电子邮件地址。
    • url:项目的 URL。
    • license:许可证(GPL、LGPL等)。
    • packages:包中所有名称的列表;setuptools 提供了一个名为 find_packages 的小函数来计算它。
    • namespace_packages:命令空间包的列表。
  • trove 分类器。

    PyPI 和 distutils 为应用程序分类提供了一种解决方案,就是使用一套被称为 trove 分类器(trove classifiers)的分类器。所有分类器都形成一个树状结构。每个分类器都是字符串形式,其中用 :: 字符串分隔每个命名空间。分类器列表在包定义中是作为 setup() 函数的 classifiers 参数。trove 分类器还可以提供以下信息:支持的 Python 版本或系统、项目的开发阶段或发布代码所使用的许可证。

  • 常见模式。

    如果不考虑元数据可能在项目其他部分找到的事实,setuptools 或 distuitls 在 setup() 函数调用中接受的大多数元数据都可以手动输入。setuptools 和 distuitls 都不能从项目源代码中自动提取各种元数据信息,因此你需要自己提供这些信息。在 Python 社区中有一些常见模式可以解决最常见的问题,例如依赖管理、包含版本/自述文件等。

    • (1)自动包含包中的版本字符串。

      PEP 440(版本标识和依赖规范,Version Identification and DependencySpecification)文档规定了版本和依赖规范的标准。这是一份很长的文档,包含已接受的版本规范方案和Python打包工具中应该如何做版本匹配和比较。

      PEP 396(模块版本号,Module Version Numbers)解决了将包或模块的版本标识符包含在什么位置。根据 PEP 396,如果一个包或模块要指定一个版本,那么应该将其包含在包的根目录(__init__.py)或模块文件的 __version__ 属性中。另一个事实上的标准是,也要将包括版本元组的 VERSION 属性包含其中。PEP 396 的另一个建议是,在 distutils 的 setup() 函数中提供的版本应该从 __version__ 派生,反之亦然。

    • (2)README 文件。

      Python 包索引可以在PyPI门户的包页面中显示一个项目的 readme 或者 long_description 的值。最常见的选择是 Markdown,它是 GitHub 上默认的标记语言——目前大多数开源的 Python 开发都是在 GitHub 上。

    • (3)管理依赖。

      许多项目需要安装和/或使用一些外部包。如果依赖列表很长的话,就会出现一个问题:如何管理依赖?在大多数情况下答案很简单。不要过度设计(over-engineer)问题。保持简单,并在 setup.py 脚本中明确提供依赖列表。或者使用 requirements.txt 文件来追踪包的依赖列表。

自定义 setup 命令

利用 distutils 可以创建新的命令。新的命令可以用一个入口点(entry point)来注册,这是由 setuptools 引入的,是一种将包定义为插件的简单方法。

入口点是类或函数的命名链接,通过 setuptools 中的一些 API 变得可用。任何应用都可以扫描所有已注册的包,并且将链接代码作为插件使用。

所有命名链接都集中在已命名的部分(named section)。distutils 被加载时,它将扫描在 distutils.commands 中注册的链接。

在开发期间使用包

使用 setuptools 主要是用于构建并分发包。但是,你仍然需要知道如何使用它们直接从项目源代码安装包。其原因很简单。在向 PyPI 提交包之前,最好测试一下你的打包代码是否正常工作。最简单的测试方法就是安装它。

  • setup.py install

    install 命令可以将包安装到 Python 环境中。如果之前没有构建过的话,它会尝试构建包,然后将结果注入到 Python 树中。如果提供了源代码发行版,那么可以在临时文件夹中将其解压,然后用这个命令安装。install 命令还将安装在 install_requires 元数据中定义的依赖。安装一个包时,对 setup.py 脚本的一个替代方法是使用 pip。

  • 卸载包。

    setuptools 和 distutils 都没有 uninstall(卸载)命令,使用 pip 可以卸载任何 Python 包。

  • setup.py developpip -e

    setuptools 提供了一个额外的 develop 命令,允许我们在开发模式(development mod)下安装包。这个命令在部署目录(site-packages)中创建一个指向项目源代码的特殊链接,而不是将整个包复制过去。可以编辑包的源代码而无需重新安装,并且它在 sys.path 中可用,就像正常安装一样。

    pip 也可以用这种模式来安装包。这个安装选项叫作可编辑模式(editablemode),可以使用 install 命令的 -e 参数来启用。

命名空间包

Python 之禅中关于命名空间的说法如下:命名空间是一个绝妙的想法,我们要多加利用!

第一种是语言上下文中的命名空间。

  • 模块的全局命名空间。
  • 函数或方法调用的本地命名空间。
  • 内置名称的命名空间。

另一种命名空间可以在包的层面提供。它们就是命名空间包(namespace packages)。

作用

可以将命名空间包理解成在高于元包(meta-package)的层面对相关的包或模块进行分组的方法,其中每个包都可以单独安装。

PEP 420——隐式命名空间包

只使用 Python 3、也只面向 Python 3 的用户。PEP 420(隐式命名空间包,ImplicitNamespace Packages)引入了一种定义命名空间包的新方法。它是标准路径的一部分,并从3.3版开始成为语言官方内容的一部分。简而言之,对于每个包含 Python 包或模块(也包括命名空间包)的目录来说,如果它不包含 __init__.py 文件,那么它就被看作是命名空间包。

在 3.3 版以前的 Python 版本中,无法使用 PEP 420 布局中的命名空间包。最简单的方法就是为每个组件创建一个文件结构,类似于没有命名空间包的普通包布局,并将所有事情都留给 setuptools。

上传一个包

Python 包索引是 Python 社区开源包的主要来源。任何人都可以免费上传新的包,唯一的要求就是在 PyPI 网站上进行注册。

PyPI——Python 包索引

PyPI 是开源包发行版的官方来源。唯一需要的是一个包管理器,可以从 PyPI 下载新的发行版,首选应该是 pip。

  • 上传到 PyPI 或其他包索引。

    上传一个包的最简单方法就是使用 setup.py 脚本的upload命令:python setup.py <dist-commands> upload<dist-commands> 是创建要上传的发行版的命令列表。只有在相同的 setup.py 执行期间创建的发行版才会被上传到仓库中。如果你想要同时上传源代码发行版、构建发行版和wheel包,那么你需要使用下列命令:python setup.py sdist dbdist bdist_wheel upload。使用 setup.py 进行上传时,你不能重复使用已经构建的发行版,每次上传时都必须重新构建。setup.py upload 的另一个问题是,在某些 Python 版本中它可以使用纯文本 HTTP 连接或未验证的 HTTPS 连接。

    twine 是与 PyPI 交互的实用程序,它目前只有一个作用——将包安全地上传到仓库中。它支持任何打包格式,并始终确保连接安全。它还允许你上传已经创建的文件,这样你能够在发布之前对发行版进行测试。需要首先注册它,才能上传。可以使用 twine 来完成注册:twine register dist/*

  • .pypirc。

    .pypirc 是一个配置文件,其中保存有关 Python 包仓库的信息。它应该位于你的主目录中。所有为 Python 构建的打包工具都应该遵守 .pypirc 文件。

源代码包与构建包

通常来说,Python 包有两种类型的发行版:

  • 源代码发行版。
  • 构建(二进制)发行版。

源代码发行版是最简单的,也是最不依赖于平台的。这种发行版只包含 Python 源代码,应该已经是高度可移植的。

  • sdist。

    sdist 命令是最简单的命令。它创建一棵分发树,其中复制了运行一个包所需要的全部内容。然后这棵树被归档到一个或多个存档文件中(通常只创建一个 tar 文件)。这个存档基本上是源代码树的副本。

  • bdist 和 wheels。

    为了能够分发预构建的发行版,distutils 提供了 build 命令,可以通过 4 个步骤来编译包。

    • build_py:通过字节编译并将其复制到构建文件夹中来构建纯Python模块。
    • build_clib:如果包中包含任何 C 库,它会利用 C 编译器在构建文件夹中创建一个静态库来构建 C 库。
    • build_ext:构建 C 扩展,并像 build_clib 一样将结果放在构建文件夹中。
    • build_scripts:构建被标记为脚本的模块。如果第一行被设置为 !# 的话,它还会修改解释器路径并修改文件模式使其变为可执行文件。

    bdist 命令使用 build 命令来构建二进制发行版。它调用 build 和所有依赖的命令,然后用和 sdist 相同的方式创建一份存档。

    另一种构建发行版是 wheel 包提供的“wheel”。安装完 wheel 后(例如使用 pip),它会向 distutils 中添加一个新的 bdist_wheel 命令。使用 wheel 的优点:

    • 更快速地安装纯 Python 包和本地 C 扩展包。
    • 避免安装任意代码执行(避免 setup.py)。
    • 安装 C 扩展不需要 Windows 或 OS X 上的编译器。
    • 允许更好的缓存,用于测试和持续集成。
    • 创建 .pyc 文件作为安装的一部分,以确保它们匹配所使用的 Python 解释器。
    • 在跨平台和跨机器上更一致的安装。

    根据 PyPA 的推荐,wheel 应该是你的默认分发格式。Linux 平台特定的 wheel 还不可用,因此如果你必须分发带有 C 扩展的包,那么你需要为 Linux 用户创建 sdist 发行版。

独立可执行文件

Python 标准库中缺少合适的工具能够让程序员创建简单的可执行文件,创建独立可执行文件是经常被忽略的一个主题。

Python 代码作为一个包分发时,需要有 Python 解释器才能运行。而编译语言有一个很大的优点,就是它允许为给定的系统架构创建可执行的应用程序,用户不需要知道底层技术就可以运行。

对开发者友好的操作系统(例如 Mac OS X 或大多数 Linux 发行版)都预装了 Python。因此对于它们的用户来说,基于 Python 的应用仍然可以作为源代码包分发,依赖于主脚本文件中特定的解释器指令(interpreter directive),这一指令通常被称为 shebang。对于大多数 Python 应用而言,其格式如下所示:#! /usr/bin/env python。这种指令放在脚本的第一行,会将其标记为默认由指定环境的 Python 版本进行解释。shebang 在 Windows 上无法使用。

独立可执行文件何时有用

如果用户体验的简单性比用户与应用代码交互的能力更加重要,那么独立可执行文件非常有用。独立可执行文件应该是对非技术最终用户分发应用的首选方式,也可能是分发 Windows 上的 Python 应用的唯一合理方式。

独立可执行文件通常适用于以下情形。

  • 依赖于特定Python版本的应用,该版本在目标操作系统是可能不容易找到。
  • 依赖于修改过的预编译 CPython 源代码的应用。
  • 带有图形界面的应用。
  • 具有许多用不同语言编写的二进制扩展的应用。
  • 游戏。

常用工具

Python 没有任何内置库支持构建独立可执行文件。一些社区项目解决了这一问题,并取得了不同程度的成功。最有名的如:PyInstaller;cx_Freeze;py2exe;py2app。

  • PyInstaller。

    到目前为止,PyInstaller(http://www.pyinstaller.org)是将 Python 包冻结为独立可执行文件的最先进的程序。它在目前每种可用的解决方案中提供最广泛的多平台兼容性,所以它也是最受推荐的方法。PyInstaller支持的平台包括:

    • Windows(32 位和 64 位);
    • Linux(32 位和 64 位);
    • Mac OS X(32 位和 64 位);
    • FreeBSD、Solaris 和 AIX。

    支持的 Python 版本包括 Python 2.7 与 Python 3.3、3.4 和 3.5。它可以在 PyPI 上找到,所以可以利用 pip 在工作环境中安装它。它不支持跨平台构建(交叉编译),因此如果你想要为某个特定平台构建独立可执行文件,那么你需要在那个平台上执行构建。你可以随时使用 Vagrant,它会为你提供所需要的操作系统作为虚拟机。

    想要为 Windows 用户创建一个独立可执行文件,利用下面这个简短的命令可以将我们的应用打包:pyinstaller myscript.py,生成的目录和文件结构,其中 dist/myscript 目录包含构建应用,必须分发整个目录,它包含运行应用所需要的所有附加文件(DLL、编译扩展库等)。利用 pyinstaller 命令的 --onefile 开关可以得到更紧凑的发行版。如果使用 --onefile 选项进行构建,你唯一需要向用户分发的文件就是 dist 目录中找到的单一可执行文件(这里是 myscript.exe)。

    运行 pyinstaller 命令的一个副作用是创建了 *.spec 文件。这是一个自动生成的 Python 模块,其中包含如何从源代码创建可执行文件的说明。

  • cx_Freeze。

    cx_Freeze(http://cx-freeze.sourceforge.net)是另一种用于创建独立可执行文件的工具。它是一种比 PyInstaller 更加简单的解决方案,但也支持 3 个主要平台:Windows;Linux;Mac OS X。

    与 PyInstaller 一样,它不允许我们执行跨平台构建,因此你需要在想要分发的同一个操作系统中创建可执行文件。cx_Freeze 的主要缺点是它不允许我们创建真正的单文件可执行文件。用它构建的应用都需要与相关的 DLL 文件和库一起分发。用法:cxfreeze myscript.py

  • py2exe 和 py2app。

    py2exe 和 py2app 另外两种用于创建独立可执行文件的程序,通过 distutils 或 setuptools 与 Python 打包进行集成。它们的用法和限制都非常相似,只需要修改 setup.py 脚本。用法:python setup.py py2exepython setup.py py2app。py2exe 和 py2app 的主要缺点是它们只面向一种平台:py2exe 允许构建 Windows 可执行文件。py2app 允许构建 Mac OS X 应用。

可执行包中 Python 代码的安全性

独立可执行文件决不会让应用代码变得安全,知道这一点是很重要的。从这样的可执行文件中反编译嵌入代码并不是一件容易的任务,但它的确是可行的。更重要的是,这种反编译的结果(如果使用适当的工具)可能与原始源代码非常相似。

  • 使反编译更难。

    通常来说,反编译过程包括以下几个步骤,如下所示:

    • 从独立可执行文件中提取项目字节码的二进制表示。
    • 将二进制表示映射到特定 Python 版本的字节码。
    • 将字节码转换成 AST。
    • 从 AST 直接重新创建源代码。

    阻止反编译过程或者使其结果变得没有价值:

    • 运行时删除所有可用的代码元数据(文档字符串),从而稍微降低最终结果的可读性。
    • 修改 CPython 解释器使用的字节码,这样从二进制转换成字节码、随后再转换成 AST 需要花费更多精力。
    • 用复杂的方式修改 CPython 源代码版本,这样即使得到了应用的反编译源代码,没有对修改过的 CPython 库反编译也是没有用的。
    • 在将源代码打包成可执行文件之前对源代码使用混淆脚本,这将会使反编译后的源代码变得没有价值。

这些解决方案都会使开发过程变得更加困难,同时每一个想法都有许多陷阱和缺点。大多数情况下,它们只是将不可避免的结果推迟了而已。

要想不让闭源代码泄露到应用之外,唯一可靠的方法是就是不以任何形式将其直接发送给用户。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/505977
推荐阅读
相关标签
  

闽ICP备14008679号