组织大型 Python 程序

PythonPythonBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

如果你要编写一个较大的程序,你并不真想将其组织成顶层的大量独立文件集合。本节介绍包的概念。

模块

任何 Python 源文件都是一个模块。

## foo.py
def grok(a):
 ...
def spam(b):
 ...

import 语句会加载并执行一个模块。

## program.py
import foo

a = foo.grok(2)
b = foo.spam('Hello')
...

包与模块

对于更大的代码集合,将模块组织成包是很常见的做法。

## 从这样
pcost.py
report.py
fileparse.py

## 到这样
porty/
    __init__.py
    pcost.py
    report.py
    fileparse.py

你选择一个名称并创建一个顶级目录。 上面示例中的 porty(显然,选择这个名称是最重要的第一步)。

在该目录中添加一个 __init__.py 文件。它可以是空的。

将你的源文件放入该目录中。

使用包

包用作导入的命名空间。

这意味着现在有了多级导入。

import porty.report
port = porty.report.read_portfolio('portfolio.csv')

导入语句还有其他变体。

from porty import report
port = report.read_portfolio('portfolio.csv')

from porty.report import read_portfolio
port = read_portfolio('portfolio.csv')

两个问题

这种方法存在两个主要问题。

  • 同一包内文件之间的导入会出错。
  • 放在包内的主脚本会出错。

所以,基本上所有东西都会出错。不过,除此之外,它还是能运行的。

问题:导入

同一包内文件之间的导入现在必须在导入中包含包名。记住这个结构。

porty/
    __init__.py
    pcost.py
    report.py
    fileparse.py

修改后的导入示例。

from porty import fileparse

def read_portfolio(filename):
    return fileparse.parse_csv(...)

所有导入都是绝对的,而非相对的。

import fileparse    ## 出错。找不到 fileparse

...

相对导入

你可以使用 . 来指代当前包,而不是直接使用包名。

from. import fileparse

def read_portfolio(filename):
    return fileparse.parse_csv(...)

语法:

from. import modname

这样便于重命名包。

问题:主脚本

将包的子模块作为主脚本运行会出错。

$ python porty/pcost.py ## 出错
...

原因:你在单个文件上运行Python,而Python无法正确识别包结构的其余部分(sys.path 有误)。

所有导入都会出错。要解决这个问题,你需要以不同的方式运行程序,使用 -m 选项。

$ python -m porty.pcost ## 可行
...

__init__.py 文件

这些文件的主要目的是将模块组合在一起。

示例:整合函数

## porty/__init__.py
from.pcost import portfolio_cost
from.report import portfolio_report

这使得在导入时,名称出现在顶级

from porty import portfolio_cost
portfolio_cost('portfolio.csv')

而不是使用多级导入。

from porty import pcost
pcost.portfolio_cost('portfolio.csv')

脚本的另一种解决方案

如前所述,现在你需要使用 -m package.module 来运行包内的脚本。

$ python3 -m porty.pcost portfolio.csv

还有另一种选择:编写一个新的顶级脚本。

#!/usr/bin/env python3
## pcost.py
import porty.pcost
import sys
porty.pcost.main(sys.argv)

此脚本位于包外部。例如,查看目录结构:

pcost.py       ## 顶级脚本
porty/         ## 包目录
    __init__.py
    pcost.py
  ...

应用程序结构

代码组织和文件结构是应用程序可维护性的关键。

对于Python来说,没有“一刀切”的方法。然而,一种适用于许多问题的结构大致如下。

porty-app/
  README.txt
  script.py         ## 脚本
  porty/
    ## 库代码
    __init__.py
    pcost.py
    report.py
    fileparse.py

顶级目录 porty-app 是其他所有内容的容器 —— 文档、顶级脚本、示例等等。

同样,顶级脚本(如果有的话)需要存在于代码包之外,即上一级目录。

#!/usr/bin/env python3
## porty-app/script.py
import sys
import porty

porty.report.main(sys.argv)

此时,你有一个包含几个程序的目录:

pcost.py          ## 计算投资组合成本
report.py         ## 生成报告
ticker.py         ## 生成实时股票报价

还有各种具有其他功能的支持模块:

stock.py          ## 股票类
portfolio.py      ## 投资组合类
fileparse.py      ## CSV解析
tableformat.py    ## 格式化表格
follow.py         ## 跟踪日志文件
typedproperty.py  ## 带类型的类属性

在本练习中,我们将清理代码并将其放入一个通用包中。

练习9.1:创建一个简单的包

创建一个名为 porty/ 的目录,并将上述所有Python文件放入其中。此外,创建一个空的 __init__.py 文件并将其放入该目录中。你应该有一个如下所示的文件目录:

porty/
    __init__.py
    fileparse.py
    follow.py
    pcost.py
    portfolio.py
    report.py
    stock.py
    tableformat.py
    ticker.py
    typedproperty.py

删除你目录中存在的 __pycache__ 文件。这包含之前预编译的Python模块。我们希望重新开始。

尝试导入一些包模块:

>>> import porty.report
>>> import porty.pcost
>>> import porty.ticker

如果这些导入失败,请进入相应的文件并修复模块导入,以包含相对包的导入。例如,像 import fileparse 这样的语句可能会更改为以下内容:

## report.py
from. import fileparse

...

如果你有像 from fileparse import parse_csv 这样的语句,将代码更改为以下内容:

## report.py
from.fileparse import parse_csv

...

✨ 查看解决方案并练习

练习9.2:创建一个应用程序目录

对于一个应用程序来说,仅仅把所有代码放入一个“包”中通常是不够的。有时候还会有支持文件、文档、脚本以及其他内容。这些文件需要存在于你在上面创建的 porty/ 目录之外。

创建一个名为 porty-app 的新目录。将你在练习9.1中创建的 porty 目录移动到该目录中。将 portfolio.csvprices.csv 测试文件复制到这个目录中。另外创建一个 README.txt 文件,写入一些关于你自己的信息。现在你的代码应该如下组织:

porty-app/
    portfolio.csv
    prices.csv
    README.txt
    porty/
        __init__.py
        fileparse.py
        follow.py
        pcost.py
        portfolio.py
        report.py
        stock.py
        tableformat.py
        ticker.py
        typedproperty.py

要运行你的代码,你需要确保你在顶级目录 porty-app/ 中工作。例如,在终端中:

$ cd porty-app
$ python3
>>> import porty.report
>>>

尝试将你之前的一些脚本作为主程序运行:

$ cd porty-app
$ python3 -m porty.report portfolio.csv prices.csv txt
      Name     Shares      Price     Change
---------- ---------- ---------- ----------
        AA        100       9.22     -22.98
       IBM         50     106.28      15.18
       CAT        150      35.46     -47.98
      MSFT        200      20.89     -30.34
        GE         95      13.48     -26.89
      MSFT         50      20.89     -44.21
       IBM        100     106.28      35.84

$

练习9.3:顶级脚本

使用 python -m 命令通常有点奇怪。你可能想要编写一个顶级脚本来处理包的一些特殊情况。创建一个生成上述报告的脚本 print-report.py

#!/usr/bin/env python3
## print-report.py
import sys
from porty.report import main
main(sys.argv)

将此脚本放在顶级目录 porty-app/ 中。确保你可以在该位置运行它:

$ cd porty-app
$ python3 print-report.py portfolio.csv prices.csv txt
      Name     Shares      Price     Change
---------- ---------- ---------- ----------
        AA        100       9.22     -22.98
       IBM         50     106.28      15.18
       CAT        150      35.46     -47.98
      MSFT        200      20.89     -30.34
        GE         95      13.48     -26.89
      MSFT         50      20.89     -44.21
       IBM        100     106.28      35.84

$

你最终的代码现在应该如下结构:

porty-app/
    portfolio.csv
    prices.csv
    print-report.py
    README.txt
    porty/
        __init__.py
        fileparse.py
        follow.py
        pcost.py
        portfolio.py
        report.py
        stock.py
        tableformat.py
        ticker.py
        typedproperty.py
✨ 查看解决方案并练习

总结

恭喜你!你已经完成了“包”实验。你可以在LabEx中练习更多实验来提升你的技能。