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

9.1 包

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

模块

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

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

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

# program.py
import foo a = foo.grok(2)
b = foo.spam('Hello')
...

包 vs 模块

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

# From this
pcost.py
report.py
fileparse.py # To this
porty/
__init__.py
pcost.py
report.py
fileparse.py

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

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

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

使用包

包用作导入的命名空间。

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

import porty.report
port = porty.report.read_portfolio('port.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

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

# report.py
from porty import fileparse def read_portfolio(filename):
return fileparse.parse_csv(...)

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

# report.py
import fileparse # BREAKS. fileparse not found ...

相对导入

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

# report.py
from . 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__.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 运行包内的脚本。

bash % python3 -m porty.pcost portfolio.csv

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

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

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

pcost.py       # top-level-script
porty/ # 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.py
import sys
import porty porty.report.main(sys.argv)

练习

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

pcost.py          # computes portfolio cost
report.py # Makes a report
ticker.py # Produce a real-time stock ticker

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

stock.py          # Stock class
portfolio.py # Portfolio class
fileparse.py # CSV parsing
tableformat.py # Formatted tables
follow.py # Follow a log file
typedproperty.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.py
from . import fileparse
...

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

# report.py
from .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-app
shell % python3
>>> import porty.report
>>>

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

shell % cd porty-app
shell % 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.py
import sys
from porty.report import main
main(sys.argv)

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

shell % cd porty-app
shell % 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

翻译:《实用的Python编程》09_01_Packages的更多相关文章

  1. 翻译:《实用的Python编程》InstructorNotes

    实用的 Python 编程--讲师说明 作者:戴维·比兹利(David Beazley) 概述 对于如何使用我的课程"实用的 Python 编程"进行教学的问题,本文档提供一些通用 ...

  2. 翻译:《实用的Python编程》README

    欢迎光临 大约 25 年前,当我第一次学习 Python 时,发现 Python 竟然可以被高效地应用到各种混乱的工作项目上,我立即被震惊了.15 年前,我自己也将这种乐趣教授给别人.教学的结果就是本 ...

  3. 翻译:《实用的Python编程》05_02_Classes_encapsulation

    目录 | 上一节 (5.1 再谈字典) | 下一节 (6 生成器) 5.2 类和封装 创建类时,通常会尝试将类的内部细节进行封装.本节介绍 Python 编程中有关封装的习惯用法(包括私有变量和私有属 ...

  4. 翻译:《实用的Python编程》04_02_Inheritance

    目录 | 上一节 (4.1 类) | 下一节 (4.3 特殊方法) 4.2 继承 继承(inheritance)是编写可扩展程序程序的常用手段.本节对继承的思想(idea)进行探讨. 简介 继承用于特 ...

  5. 翻译:《实用的Python编程》01_02_Hello_world

    目录 | 上一节 (1.1 Python) | 下一节 (1.3 数字) 1.2 第一个程序 本节讨论有关如何创建一个程序.运行解释器和调试的基础知识. 运行 Python Python 程序始终在解 ...

  6. 翻译:《实用的Python编程》03_03_Error_checking

    目录 | 上一节 (3.2 深入函数) | 下一节 (3.4 模块) 3.3 错误检查 虽然前面已经介绍了异常,但本节补充一些有关错误检查和异常处理的其它细节. 程序是如何运行失败的 Python 不 ...

  7. 翻译:《实用的Python编程》03_04_Modules

    目录 | 上一节 (3.3 错误检查) | 下一节 (3.5 主模块) 3.4 模块 本节介绍模块的概念以及如何使用跨多个文件的函数. 模块和导入 任何一个 Python 源文件都是一个模块. # f ...

  8. 翻译:《实用的Python编程》03_05_Main_module

    目录 | 上一节 (3.4 模块) | 下一节 (3.6 设计讨论) 3.5 主模块 本节介绍主程序(主模块)的概念 主函数 在许多编程语言中,存在一个主函数或者主方法的概念. // c / c++ ...

  9. 翻译:《实用的Python编程》04_01_Class

    目录 | 上一节 (3.6 设计讨论) | 下一节 (4.2 继承) 4.1 类 本节介绍 class 语句以及创建新对象的方式. 面向对象编程(OOP) 面向对象编程是一种将代码组织成对象集合的编程 ...

随机推荐

  1. uniapp 在h5和小程序上使用高德获取用户城市位置

    开发文档 https://lbs.amap.com/api 错误状态 https://lbs.amap.com/api/webservice/guide/tools/info/ 虽然用的高德但是你还需 ...

  2. django学习-20.python3中的特殊方法【__str__】的作用

    目录结构 1.前言 2.[__str__]特殊方法的具体使用 2.1.当使用print打印一个类被实例化后生成的对象的时候,若类里有定义了[__str__]特殊方法,是打印出这样的数据:[__str_ ...

  3. C/C++子函数参数传递,堆栈帧、堆栈参数详解

    本文转载自C/C++子函数参数传递,堆栈帧.堆栈参数详解 导语 因为参数传递和汇编语言有很大联系,之后会出现较多x86汇编代码. 该文会先讲一下x86的堆栈参数传递过程,然后再分析C/C++子函数是怎 ...

  4. HttpDns 原理是什么

    本文转载自HttpDns 原理是什么 什么是 DNS DNS(Domain Name System,域名系统),DNS 服务用于在网络请求时,将域名转为 IP 地址.能够使用户更方便的访问互联网,而不 ...

  5. Window下Scala开发环境搭建

    在Windows下搭建Scala开发环境,需要做以下几个步骤 1) 安装JDK 2) 安装Scala,并配置环境变量 3) Idea安装并创建Scala 类 1.安装JDK JDK安装,这里不再介绍, ...

  6. sqlserver日期时间格式转换

    Select CONVERT(varchar(100), GETDATE(), 0): 05 16 2006 10:57AM Select CONVERT(varchar(100), GETDATE( ...

  7. MySQL 导出 select 结果集

    reference: https://blog.csdn.net/huaishuming/article/details/74762652法一:SELECT * from jc_archives wh ...

  8. hive分区分桶

    目录 1.分区 1.1.静态分区 1.1.1.一个分区 1.1.2.多个分区 1.2.动态分区 2.分桶 1.分区 如果一个表中数据很多,我们查询时就很慢,耗费大量时间,如果要查询其中部分数据该怎么办 ...

  9. 手把手教你Spring Boot2.x整合kafka

    首先得自己搭建一个kafka,搭建教程请自行百度,本人是使用docker搭建了一个单机版的zookeeper+kafka作为演示,文末会有完整代码包提供给大家下载参考 废话不多说,教程开始 一.老规矩 ...

  10. Spring-06 AOP

    Spring-06 AOP AOP 1.简介 AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AO ...