目录| 上一节 (8.3 调试) | [下一节 (9.2 第三方包)]()
9.1 包
如果编写一个较大的程序,咱们并不真的想在顶层将其组织为一个个独立文件的大型汇合。本节对包(package)进行介绍。
模块
任何一个 Python 源文件称为一个模块(module)。
# foo.pydef grok(a): ...def spam(b): ...
一条 import
语句加载并执行 一个模块。
# program.pyimport fooa = 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 fileparsedef read_portfolio(filename): return fileparse.parse_csv(...)
所有的导入都是相对的,而不是绝对的。
# report.pyimport fileparse # BREAKS. fileparse not found...
绝对导入
除了应用包名间接导入,还能够应用应用 .
援用以后的包。
# report.pyfrom . import fileparsedef 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 portyporty.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.csv
和 Data/prices.csv
到 porty-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.84shell %
练习 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.84shell %
最初,代码的组织构造应该上面这样:
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