写点什么

翻译:《实用的 Python 编程》09_01_Packages

用户头像
codists
关注
发布于: 2021 年 04 月 16 日
翻译:《实用的Python编程》09_01_Packages

目录| 上一节 (8.3 调试) | 下一节 (9.2 第三方包)

9.1 包

如果编写一个较大的程序,我们并不真的想在顶层将其组织为一个个独立文件的大型集合。本节对包(package)进行介绍。

模块

任何一个 Python 源文件称为一个模块(module)。


# foo.pydef grok(a):    ...def spam(b):    ...
复制代码


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


# program.pyimport foo
a = foo.grok(2)b = foo.spam('Hello')...
复制代码

包 vs 模块

对于较大的代码集合,通常将模块组织到包中。


# From thispcost.pyreport.pyfileparse.py
# To thisporty/ __init__.py pcost.py report.py fileparse.py
复制代码


首先,选择一个名字并用该名字创建顶级目录。如上述的 porty (显然,第一步最重要的是选择名字)。


接着,添加 __init__.py 文件到该目录中。__init__.py 文件可以是一个空文件。


最后,把源文件放到该目录中。

使用包

包用作导入的命名空间。


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


import porty.reportport = porty.report.read_portfolio('port.csv')
复制代码


导入语句还有其它变体:


from porty import reportport = report.read_portfolio('portfolio.csv')
from porty.report import read_portfolioport = read_portfolio('portfolio.csv')
复制代码

两个问题

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


  • 同一包内不同文件之间的导入无效。

  • 包中的主脚本无效。


因此,基本上一切导入都是无效的,但是,除此之外,程序还是可以工作的。

问题:导入

现在,在导入的时候,同一包内的不同文件之间的导入必须包含包名。请记住这个结构:


porty/    __init__.py    pcost.py    report.py    fileparse.py
复制代码


根据上述规则(同一包内的不同文件之间的导入必须包含包名)修改后的导入示例:


# report.pyfrom porty import fileparse
def read_portfolio(filename): return fileparse.parse_csv(...)
复制代码


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


# report.pyimport fileparse    # BREAKS. fileparse not found
...
复制代码

相对导入

除了使用包名直接导入,还可以使用使用 . 引用当前的包。


# report.pyfrom . import fileparse
def read_portfolio(filename): return fileparse.parse_csv(...)
复制代码


语法:


from . import modname
复制代码


使用上述语法使得重命名包变得容易。

问题:主脚本

将包内的子模块作为主脚本运行会导致程序中断:


bash $ python porty/pcost.py # BREAKS...
复制代码


原因:你正在运行单个脚本,而 Python 不知道包的其余部分(sys.path 是错误的)。


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


bash $ python -m porty.pcost # WORKS...
复制代码

__init__.py 文件

该文件的主要目的是将模块组织在一起。


例如:


# porty/__init__.pyfrom .pcost import portfolio_costfrom .report import portfolio_report
复制代码


这使得导入的时候名字出现在顶层。


from porty import portfolio_costportfolio_cost('portfolio.csv')
复制代码


而不是使用多级导入:


from porty import pcostpcost.portfolio_cost('portfolio.csv')
复制代码

脚本的另一种解决方案

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


bash % python3 -m porty.pcost portfolio.csv
复制代码


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


#!/usr/bin/env python3# pcost.pyimport porty.pcostimport sysporty.pcost.main(sys.argv)
复制代码


脚本位于包外面。目录结构如下:


pcost.py       # top-level-scriptporty/         # package directory    __init__.py    pcost.py    ...
复制代码

应用结构

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


对于 Python 而言,没有“放之四海而皆准”的方法,但是一个适用于多种问题的结构就是这样:


porty-app/  README.txt  script.py         # SCRIPT  porty/    # LIBRARY CODE    __init__.py    pcost.py    report.py    fileparse.py
复制代码


顶级 porty-app 目录是所有其他内容的容器——这些内容包括文档,顶级脚本,用例等。


同样,顶级脚本(如果有)需要放置在代码包之外(包的上一层)。


#!/usr/bin/env python3# porty-app/script.pyimport sysimport porty
porty.report.main(sys.argv)
复制代码

练习

此时,我们有了一个包含多个程序的目录:


pcost.py          # computes portfolio costreport.py         # Makes a reportticker.py         # Produce a real-time stock ticker
复制代码


同时,还有许多具有各种功能的支持模块:


stock.py          # Stock classportfolio.py      # Portfolio classfileparse.py      # CSV parsingtableformat.py    # Formatted tablesfollow.py         # Follow a log filetypedproperty.py  # Typed class properties
复制代码


在本次练习中,我们将整理这些代码并将它们放入一个通用包中。

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

请创建一个名为 porty 的目录并将上述所有的 Python 文件放入其中。另外,在 porty 目录中创建一个空的 __init__.py 文件。最后,文件目录看起来像这样:


porty/    __init__.py    fileparse.py    follow.py    pcost.py    portfolio.py    report.py    stock.py    tableformat.py    ticker.py    typedproperty.py
复制代码


请将 porty 目录中的 __pycache__ 目录移除。该目录包含了之前预编译的 Python 模块。我们想重新开始。


尝试导入包中的几个模块:


>>> import porty.report>>> import porty.pcost>>> import porty.ticker
复制代码


如果这些导入失败,请进入到合适的文件中解决模块导入问题,使其能够包括相对导入。例如,import fileparse 语句可以像下面这样进行修改:


# report.pyfrom . import fileparse...
复制代码


如果有类似于 from fileparse import parse_csv 这样的语句,请像下面这样修改代码:


# report.pyfrom .fileparse import parse_csv...
复制代码

练习 9.2:创建应用目录

对应用而言,将所有代码放到“包”中通常是不够的。有时,支持文件,文档,脚本等文件需要放到 porty/ 目录之外。


请创建一个名为 porty-app 的新目录。然后将我们在练习 9.1 中创建的 porty 目录移动到 porty-app 目录中。接着,复制测试文件 Data/portfolio.csvData/prices.csvporty-app 目录。另外,在 porty-app 目录下创建一个 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/ 下。例如,从终端运行:


shell % cd porty-appshell % python3>>> import porty.report>>>
复制代码


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


shell % cd porty-appshell % 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
shell %
复制代码

练习 9.3:顶级脚本

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


#!/usr/bin/env python3# print-report.pyimport sysfrom porty.report import mainmain(sys.argv)
复制代码


然后把脚本 print-report.py 放到顶级目录 porty-app/ 中。并确保可以在 porty-app/ 目录下运行它:


shell % cd porty-appshell % 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
shell %
复制代码


最后,代码的组织结构应该下面这样:


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
复制代码


目录| 上一节 (8.3 调试) | 下一节 (9.2 第三方包)


注:完整翻译见 https://github.com/codists/practical-python-zh

发布于: 2021 年 04 月 16 日阅读数: 10
用户头像

codists

关注

公众号:编程人 2021.01.14 加入

Life is short, You need Python

评论

发布
暂无评论
翻译:《实用的Python编程》09_01_Packages