概述
包 定义
为了组织好模块,会将多个模块分为包。Python 处理包也是相当方便的。简单来说,包就是文件夹,但该文件夹下必须存在 __init__.py 文件。
常见的包结构如下:
最简单的情况下,只需要一个空的 __init__.py 文件即可。当然它也可以执行包的初始化代码,或者定义稍后介绍的 __all__ 变量。当然包底下也能包含包,这和文件夹一样,还是比较好理解的。
导入包
包的导入仍使用 import 、 from ... import 语句,使用 “圆点模块名” 的结构化模块命名空间。 下面来看一个包的例子来了解下具体的运作。(官方文档中的例子)
假设你现在想要设计一个模块集(一个“包”)来统一处理声音文件和声音数据。存在几种不同的声音格式(通常由它们的扩展名来标识,例如: .wav, .aiff, .au )于是,为了在不同类型的文件格式之间转换,你需要维护一个不断增长的包集合。可能你还想要对声音数据做很多不同的操作(例如混音,添加回声,应用平衡 功能,创建一个人造效果)所以你要加入一个无限流模块来执行这些操作。你的包可能会是这个样子(通过分级的文件体系来进行分组):
用户可以每次只导入包里的特定模块,例如: import sound.efforts.echo 这样就导入了 sound.effects.echo 子模块。它必须通过完整的名称来引用:
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
导入包时有一个可以选择的方式: from sound.effects import echo 这样就加载了 echo 子模块,并且使得它在没有包前缀的情况下也可以使用,所以它可以如下方式调用:
echo.echofilter(input, output, delay=0.7, atten=4)
还有另一种变体用于直接导入函数或变量: from sound.effects.echo import echofilter 这样就又一次加载了 echo 字模块,但这样就可以直接调用它的 echofilter() 函数:
echo.echofilter(input, output, delay=0.7, atten=4)
需要注意的是 from package import item 方式导入包时,这个子项(item)既可以是子包也可以是其他命名,如函数、类、变量等。若无,会引发ImportError异常。
而用类似 import item.subitem.subsubitem 这样的语法时,这些子项必须是包,最后的子项可以是包或模块,但不能是类、函数、变量等。
从 * 导入包
import * 这样的语句理论上是希望文件系统找出包中所有的子模块,然后导入它们。这可能会花长时间,并出现边界效应等。Python 解决方案是提供一个明确的包索引。
这个索引由 __init__.py 定义 __all__ 变量,该变量为一列表,如上例 sound/effects 下的 __init__.py 中,可定义 __all__ = ["echo","surround","reverse"]
这意味着, from sound.effects import * 会从对应的包中导入以上三个子模块; 尽管提供 import * 的方法,仍不建议在生产代码中使用这种写法。
包内引用
如果是子包内的引用,可以按相对位置引入子模块 以 echo 模块为例,可以引用如下:
1 from . import reverse # 同级目录 导入 reverse 2 from .. import frormats # 上级目录 导入 frormats 3 from ..filters import equalizer # 上级目录的filters模块下 导入 equalizer
多重目录包搜索
包支持一个更为特殊的特性, __path__ 在包的 __init__.py 文件代码执行前,该变量初始化一个目录名列表。作用于子包和模块的搜索功能。
该功能可以用于扩展包中的模块集,不过不常用。
https://www.cnblogs.com/kex1n/p/5977051.html
Python类、模块、包的区别
包(package)
为了组织好模块,将多个模块分为一个包。包是python模块文件所在的目录,且该目录下必须存在__init__.py
文件。常见的包结构如下:
package_a
├── __init__.py
├── module_a1.py
└── module_a2.py
package_b
├── __init__.py
├── module_b1.py
└── module_b2.py
main.py
- 如果
main.py
想要引用packagea
中的模块module
a1
,可以使用:
from package_a import module_a1
import package_a.module_a1
- 如果
packagea
中的module
a1
需要引用packageb
,那么默认情况下,python是找不到package
b
。我们可以使用sys.path.append('../')
,可以在packagea
中的__init__.py
添加这句话,然后该包下得所有module都添加*import __init
_
即可。
Python包和类的基本用法
http://blog.csdn.net/liukang325/article/details/46724365
构建健壮 Python 包的 5 个简单规则
创建一个软件包(package)似乎已经足够简单了,也就是在文件目录下搜集一些模块,再加上一个__init__.py
文件,对吧?我们很容易看出来,随着时间的推移,通过对软件包的越来越多的修改,一个设计很差的软件包可能会出现循环依赖问题,或是可能变得不可移植和不可靠。
1. __init__.py
仅为导入服务
对于一个简单的软件包,你可能会忍不住把工具方法,工厂方法和异常处理都丢进__init__.py
,千万别这样!
一个结构良好的__init__.py
文件,仅为一个非常重要的目的来服务:从子模块导入。你的__init__.py
应该看起来像这个样子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# ORDER MATTERS HERE -- SOME MODULES ARE DEPENDANT ON OTHERS
# 导入顺序要考虑——一些模块会依赖另外的一些
from
exceptions
import
FSQError
,
FSQEnvError
,
FSQEncodeError
,
FSQTimeFmtError
,
FSQMalformedEntryError
,
FSQCoerceError
,
FSQEnqueueError
,
FSQConfigError
,
FSQPathError
,
FSQInstallError
,
FSQCannotLockError
,
FSQWorkItemError
,
FSQTTLExpiredError
,
FSQMaxTriesError
,
FSQScanError
,
FSQDownError
,
FSQDoneError
,
FSQFailError
,
FSQTriggerPullError
,
FSQHostsError
,
FSQReenqueueError
,
FSQPushError
# constants relies on: exceptions, internal
import
constants
# const relies on: constants, exceptions, internal
from
const
import
const
,
set_const
# has tests
# path relies on: exceptions, constants, internal
import
path
# has tests
# lists relies on: path
from
lists
import
hosts
,
queues
#...
|
2.使用__init__.py
来限制导入顺序
- 把方法和类置于软件包的作用域中,这样用户就不需要深入软件包的内部结构,使你的软包变得易用。
- 作为调和导入顺序的唯一地方。
使用得当的话,__init__.py
可以为你提供重新组织内部软件包结构的灵活性,而不需要担心由内部导入子模块或是每个模块导入顺序所带来的副作用。因为你是以一个特定的顺序导入子模块,你的__init__.py
对于他程序员来讲应该简单易懂,并且能够明显的表示该软件包所能提供的全部功能。
文档字符串,以及在软件包层面对__all__
属性的赋值应当是__init__.py中唯一与导入无关的代码:
1
2
3
4
5
6
7
8
9
|
__all__
=
[
'FSQError'
,
'FSQEnvError'
,
'FSQEncodeError'
,
'FSQTimeFmtError'
,
'FSQMalformedEntryError'
,
'FSQCoerceError'
,
'FSQEnqueueError'
,
'FSQConfigError'
,
'FSQCannotLock'
,
'FSQWorkItemError'
,
'FSQTTLExpiredError'
,
'FSQMaxTriesError'
,
'FSQScanError'
,
'FSQDownError'
,
'FSQDoneError'
,
'FSQFailError'
,
'FSQInstallError'
,
'FSQTriggerPullError'
,
'FSQCannotLockError'
,
'FSQPathError'
,
'path'
,
'constants'
,
'const'
,
'set_const'
,
'down'
,
'up'
,
# ...
]
|
3.使用一个模块来定义所有的异常
你也许已经注意到了,__init__.py
中的第一个导入语句从exceptions.py
子模块中导入了全部的异常。从这里出发,你将看到,在大多数的软件包中,异常被定义在引起它们的代码附近。尽管这样可以为一个模块提供高度的完整性,一个足够复杂的软件包会通过如下两种方式,使得这一模式出现问题。
- 通常一个模块/程序需要从一个子模块导入一个函数, 利用它导入代码并抛出异常。为了捕获异常并保持一定的粒度,你需要导入你需要的模块,以及定义了异常的模块(或者更糟,你要导入一系列的异常)。这一系列衍生出来的导入需求,是在你的软件包中编织一张错综复杂的导入之网的始作俑者。你使用这种方式的次数越多,你的软件包内部就变的越相互依赖,也更加容易出错。
- 随着异常数量的不断增长,找到一个软件包可能引发的全部异常变的越来越难。把所有的异常定义在一个单独的模块中,提供了一个方便的地方,在这里,程序员可以审查并确定你的软件包所能引发全部潜在错误状态。
你应该为你的软件包的异常定义一个基类:
1
2
3
|
class
APackageException
(
Exception
)
:
'''root for APackage Exceptions, only used to except any APackage error, never raised'''
pass
|
然后确保你的软件包在任何错误状态下,只会引发这个基类异常的子类异常,这样如果你需要的话,你就可以阻止全部的异常:
1
2
3
4
|
try
:
'''bunch of code from your package'''
except
APackageException
:
'''blanked condition to handle all errors from your package'''
|
对于一般的错误状态,这里有一些重要的异常处理已经被包括在标准库中了(例如,TypeError, ValueError等)
灵活地定义异常处理并保持足够的粒度:
1
2
3
4
5
6
7
8
9
10
|
# from fsq
class
FSQEnvError
(
FSQError
)
:
'''An error if something cannot be loaded from env, or env has an invalid
value'''
pass
class
FSQEncodeError
(
FSQError
)
:
'''An error occured while encoding or decoding an argument'''
pass
# ... and 20 or so more
|
在你的异常处理中保持更大的粒度,有利于让程序员们在一个try/except中包含越来越大的,互相不干涉的代码段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
# this
try
:
item
=
fsq
.
senqueue
(
'queue'
,
'str'
,
'arg'
,
'arg'
)
scanner
=
fsq
.
scan
(
'queue'
)
except
FSQScanError
:
'''do something'''
except
FSQEnqueueError
:
'''do something else'''
# not this
try
:
item
=
fsq
.
senqueue
(
'queue'
,
'str'
,
'arg'
,
'arg'
)
except
FSQEnqueueError
:
'''do something else'''
try
:
scanner
=
fsq
.
scan
(
'queue'
)
except
FSQScanError
:
'''do something'''
# and definitely not
try
:
item
=
fsq
.
senqueue
(
'queue'
,
'str'
,
'arg'
,
'arg'
)
try
:
scanner
=
fsq
.
scan
(
'queue'
)
except
FSQScanError
:
'''do something'''
except
FSQEnqueueError
:
'''do something else'''
|
在异常定义时保持高度的粒度,会减少错综复杂的错误处理,并且允许你把正常执行指令和错误处理指令分别开来,使你的代码更加易懂和更易维护。
4. 在软件包内部只进行相对导入
在子模块中你时常见到的一个简单错误,就是使用软件包的名字来导入软件包。
1
2
|
# within a sub-module
from
a_package
import
APackageError
|
这样做会导致两个不好的结果:
- 子模块只有当软件包被安装在 PYTHONPATH 内才能正确运行。
- 子模块只有当这个软件包的名字是 a_package 时才能正确运行。
尽管第一条看上去并不是什么大问题,但是考虑一下,如果你在 PYTHONPATH 下的两个目录中,有两个同名的软件包。你的子模块可能最终导入了另一个软件包,你将无意间使得某个或某些对此毫无戒备的程序员(或是你自己)debug 到深夜。
1
2
3
4
5
6
7
|
# within a sub-module
from
.
import
FSQEnqueueError
,
FSQCoerceError
,
FSQError
,
FSQReenqueueError
,
constants
as
_c
,
path
as
fsq_path
,
construct
,
hosts
as
fsq_hosts
,
FSQWorkItem
from
.
internal
import
rationalize_file
,
wrap_io_os_err
,
fmt_time
,
coerce_unicode
,
uid_gid
# you can also use ../... etc. in sub-packages.
|
5. 让模块保持较小的规模
你的模块应当比较小。记住,那个使用你软件包的程序员会在软件包作用域进行导入,同时你会使用你的 __init__.py
文件来作为一个组织工具,来暴露一个完整的接口。
好的做法是一个模块只定义一个类,伴随一些帮助方法和工厂方法来协助建立这个模块。
1
2
3
4
5
6
|
class
APackageClass
(
object
)
:
'''One class'''
def
apackage_builder
(
how_many
)
:
for
i
in
range
(
how_many
)
:
yield
APackageClass
(
)
|
如果你的模块暴露了一些方法,把一些相互依赖的方法分为一组放进一个模块,并且把不相互依赖的方法移动到单独的模块中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
####### EXPOSED METHODS #######
def
enqueue
(
trg_queue
,
item_f
,
*
args
,
*
*
kwargs
)
:
'''Enqueue the contents of a file, or file-like object, file-descriptor or
the contents of a file at an address (e.g. '/my/file') queue with
arbitrary arguments, enqueue is to venqueue what printf is to vprintf
'''
return
venqueue
(
trg_queue
,
item_f
,
args
,
*
*
kwargs
)
def
senqueue
(
trg_queue
,
item_s
,
*
args
,
*
*
kwargs
)
:
'''Enqueue a string, or string-like object to queue with arbitrary
arguments, senqueue is to enqueue what sprintf is to printf, senqueue
is to vsenqueue what sprintf is to vsprintf.
'''
return
vsenqueue
(
trg_queue
,
item_s
,
args
,
*
*
kwargs
)
def
venqueue
(
trg_queue
,
item_f
,
args
,
user
=
None
,
group
=
None
,
mode
=
None
)
:
'''Enqueue the contents of a file, or file-like object, file-descriptor or
the contents of a file at an address (e.g. '/my/file') queue with
an argument list, venqueue is to enqueue what vprintf is to printf
if entropy is passed in, failure on duplicates is raised to the caller,
if entropy is not passed in, venqueue will increment entropy until it
can create the queue item.
'''
# setup defaults
trg_fd
=
name
=
None
# ...
|
上面的例子是 fsq/enqueue.py,它暴露了一系列的方法来为同一个功能提供不同的接口(就像 simplejson 中的l oad/loads)。尽管这个例子足够直观,让你的模块保持较小规模需要一些判断,但是一个好的原则是:
当你有疑问的时候,就去创建一个新的子模块吧。
最后
以上就是傻傻红牛为你收集整理的Python 包Python类、模块、包的区别Python包和类的基本用法构建健壮 Python 包的 5 个简单规则的全部内容,希望文章能够帮你解决Python 包Python类、模块、包的区别Python包和类的基本用法构建健壮 Python 包的 5 个简单规则所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复