注意: 虽然 JavaScript 对于本网站不是必需的,但您与内容的互动将受到限制。请开启 JavaScript 以获得完整的体验。

Python Distutils-SIG: 设计提案

Python Distutils-SIG

(先决条件:在尝试阅读此设计文档之前,请先阅读提议的接口;它很大程度上是接口文档的续篇。)

设计提案

Distutils 的观点

setup.py 只需导入一个模块,即 distutils.core。此模块负责解析 setup.py 的所有命令行参数(尽管选项的解释分布在各种 Distutils 命令中,并且可能分布在客户端 setup.py 中)。它还负责接收来自 setup.py 的控制,并将其适当地传递给 Distutils 命令。最重要的是,distutils.core 定义了 Distribution 类,它是 Distutils 的核心。客户端 (setup.py) 主要用于为 Distribution 实例提供属性,所有 Distutils 命令都操作该实例。distutils.core 还定义了 Command 类,这对于实现 Distutils 命令非常有用。

说到 Distutils 命令:每个命令都作为 Python 模块实现,例如 build 命令由 distutils.build 模块实现。每个命令模块都需要定义一个类,该类也以命令命名——例如 distutils.build.Build。这些命令类将继承自 Command 类,该类(至少)将提供处理命令特定选项的方法。(Command 将提供一个构造函数,该构造函数接受 Distribution 类和此命令的可选参数列表,并通过检查 Command 派生实例中的 getopt 样式选项说明符来解析参数列表。)每个命令类必须提供一个 run 方法,该方法使用 Distribution 实例中的信息和命令选项来“执行其操作”。编写良好的命令类会将此任务分解为几个定义明确(且有文档说明)的方法,以便客户端 setup.py 可以继承并覆盖 Distutils 命令类的特定行为。这也意味着 Distribution 类必须有一种方法将覆盖的命令类传达给主调度程序。

客户端的观点

如上所述,客户端 (setup.py) 只需导入 distutils.core —— 其他所有 Distutils 相关的功能都由这个核心模块处理。然而,客户端需要一种方法将其特定选项传递给 Distutils 核心(并传递给命令模块)。

有两种可能的方案:一种简短方便(但扩展性不强),另一种有点冗长笨拙(但更面向对象且扩展性强)。我们没有理由不能两者兼得;方便的接口可以只是一个包装器,用于许多不需要大量花哨自定义的模块分发。

首先,这是一个简单接口的示例,用于包含单个“纯 Python”模块 (mymod.py) 的模块分发。

    from distutils.core import setup
    setup (name = "mymod",
           version = "1.2",
           author = "Greg Ward <gward@cnri.reston.va.us>",
           description = "A very simple, one-module distribution")
请注意,我们没有明确列出 mymod.py:Distutils 假设这是一个以其唯一模块 (mymod) 命名的单模块分发。

那些喜欢定义子类的人可能更喜欢以不同的方式表达

    from distutils.core import Distribution, setup

    class MyDistribution (Distribution):
        name = "mymod"
        version = "1.2",
        author = "Greg Ward <gward@cnri.reston.va.us>",
        description = "A very simple, one-module distribution")

    setup (distclass = MyDistribution)
对于小型分发来说,这有点矫枉过正:我们仅为了提供属性值而定义一个新类,而 distutils.core.setup 的主要目的正是让我们这样做。尽管如此,面向对象纯粹主义者会喜欢这一点——而且无疑会有客户端必须覆盖行为而不仅仅是数据的情况,届时面向对象接口将是必要的。

对于更复杂的模块分发,需要自定义大量属性,将其分解开来可能更容易阅读/维护。考虑一个包含两个纯 Python 模块 (mymodmy_othermod) 和一个 C 扩展 (myext) 的分发;C 扩展必须与两个辅助 C 文件和一个 C 库链接。哦,这个分发需要 Python 1.5 和任何版本的 re 模块(忽略一个通常意味着另一个的事实)

    from distutils.core import Distribution, setup

    class MyDistribution (Distribution):
        name = "mydist",
        version = "1.3.4",
        author = "Greg Ward <gward@cnri.reston.va.us>"
        description = """\
    This is an example module distribution.  It provides no useful code,
    but is an interesting example of the Distutils in action."""

        # Dependencies
        requires = { 'python': '1.5',  # I like class-based exceptions
                     're': '*',        # and I love Perl-style regexps! ;-)
                   }
        # Actual files that need to be processed and installed in some form
        py_modules = ['mymod.py', 'my_othermod.py'],
        ext_modules = {'myext.c': 
                        {'other_c': ['extra1.c', 'extra2.c'],
                         'c_libraries': ['mylib']}
                      }

    setup (distclass = MyDistribution)

有几点需要注意

  • 我不害怕使用深度嵌套的数据结构;如果您正在编写和分发 Python 模块,这不应该是一个问题!
  • 每个属性都有一个特定的类型(字符串、列表、字典等)
  • 具有复杂类型(尤其是字典)的属性将具有众所周知且有文档说明的内部结构,例如。
        """ext_modules is a hash mapping names of C source files (each
        containing a Python extension module) to a nested hash of
        information about how to build that module.  The allowed keys to
        this nested hash are: 
          - other_c: other C files that must be compiled and linked with 
                     the main C file to create the module
          - c_libraries: C libraries that must be included in the link
          ...
       """
    

毫无疑问,ext_modules 嵌套哈希将有更多选项,毫无疑问,其他 Distribution 属性将具有复杂且有文档说明的结构。

最后,所有 Distribution 属性的列表必须是众所周知且有文档说明的!这些似乎分为几个大类。这是一个最初的列表尝试

  • 分发元数据
    • 名称
    • 版本
    • 作者
    • 描述
  • 依赖项
    • requires
  • 要处理和安装的文件
    • py_modules
    • ext_modules
    • doc_files
  • 构建目录(默认都在 ./blib 下)
    • build_lib - 放置独立于平台的库文件的位置
    • build_platlib - 放置依赖于平台的库文件的位置
    • build_exe - 放置可执行程序(即脚本)的位置
    • build_html - 放置已处理文档(HTML)的位置
  • 安装目录(默认在 sysconfig.LIBDEST 下)
    • install_lib
    • install_platlib
    • install_exe
    • install_html
……嗯,这是一个开始。

Distutils 的观点再探

总而言之,让我们回顾一下用户运行 setup.py 时发生的事情。无论 setup.py 是以简单形式(调用函数)还是通用形式(定义子类)编写,都没有太大关系,所以我不会将事情分成两个流。

  • setup.py 导入 distutils.core
  • distutils.core 启动代码解析命令行参数:处理它知道的全局选项,并保留其余部分供客户端 (setup.py) 处理;找出每个命令的命令和选项,将它们都保存起来以便稍后处理
  • setup.py 调用 distutils.core.setup(可能带有一个 distclass 参数,指定 Distribution 的子类,可能带有一堆其他命名参数,指定 Distribution 实例的各种属性)
  • distutils.core.setup 实例化 Distribution(或客户端提供的子类),并使用其参数(除了 distclass)来覆盖此实例的属性
  • distutils.core.setup 加载命令模块(例如 distutils.build
  • distutils.core.setup 确定命令类(通常仅以命令命名,例如 distutils.build.Build,但也可能是客户端作为 Distribution 实例的属性之一提供的类)并实例化它
  • 命令类构造函数接受 Distribution 实例和 setup.py 命令行上特定于此命令的任何命令行参数作为参数
  • 命令类构造函数解析其选项以设置/覆盖某些实例属性
  • distutils.core.setup 调用命令对象上的 run 方法
  • 该方法执行命令应该执行的任何操作:构建模块、处理文档、安装文件等。
  • distutils.core.setup 确定下一个命令类(如果给定了多个命令),并像以前一样进行