彻底明白Python package和模块

python 是通过module组织代码的,每一个module就是一个python文件,但是modules是通过package来组织的。

如果我们自己写着玩,有的时候就是一两个Python文件在同级目录下,但是当我们开始尝试开发更为复杂的项目的时候,package这个概念的使用就有助于我们组织我们写的一个个modules。

module的概念相对简单,所以不会再多说,主要是说一下package。

Python package

package的定义很简单,在当面目录下有__init__.py文件的目录即为一个package。

但是这会分为两种情况,第一种情况是一个空的__init__.py文件,另外一个情况是写了代码的__init__.py文件。不管是空的还是有内容的,这个目录都会被认为是一个package,这是一个标识。

package的初始化工作

一个package 被导入,不管在什么时候__init__.py的代码都只会被执行一次

>>> import package
hello world
>>> import package
>>> import package

由于 package 被导入时 __init__.py 中的可执行代码会被执行,所以小心在 package 中放置你的代码,尽可能消除它们产生的副作用,比如把代码尽可能的进行封装成函数或类。

__init__.py内的导入顺序

当我尝试导入

from package import something

import语句会首先检查something是不是__init__.py的变量,然后检查是不是subpackage,再检查是不是module,最后抛出ImportError

所以检查顺序如下:

  1. __init__.py 文件内变量
  2. 是不是package内的subpackage
  3. 是不是package内的module

看个例子

我们有一个如下结构的package

a.py文件内有一个函数

def bar():
    print("Hello, function 'bar' from module 'a' calling")

b.py文件内有一个函数

def foo():
    print("Hello, function 'foo' from module 'b' calling")

然后我们添加一个空的__init__.py 文件在simple_package里面。

我们看下,当我们import simple_package的时候到底会发生什么事情(在simple_package内激活Python shell 或者simple_package的的路径被包含在pythonsys.path或者在PYTHONPATH的环境变量中)

>>> import simple_package
>>> 
>>> simple_package
<module 'simple_package' from '/home/bernd/Dropbox (Bodenseo)/websites/python-course.eu/examples/simple_package/__init__.py'>
>>> 
>>> simple_package.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> 
>>> simple_package.b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined

我们可以看到simple_package已经被成功导入,但是a.pyb.py并没有被导入

当然了,如果你希望使用import simple_package后自动加载a或者b 模块,这里有两种办法。

第一种就是在__init__.py内导入a或者b模块,然后保存再激活python的交互环境

#__init__.py
import a
import b

当你再次尝试import simple_package后,就可以使用simple_package.a.bar()来使用模块a中的bar()函数了。

第二办法就是手动导入,当你想使用模块a中的bar()函数时,需要手动导入

import simple_package.a as a

然后就是可以a.bar()来使用bar()函数了。

一个更复杂的例子

这是一个来自官方的例子

文件结构如下

sound
|-- effects
|   |-- echo.py
|   |-- __init__.py
|   |-- reverse.py
|   `-- surround.py
|-- filters
|   |-- equalizer.py
|   |-- __init__.py
|   |-- karaoke.py
|   `-- vocoder.py
|-- formats
|   |-- aiffread.py
|   |-- aiffwrite.py
|   |-- auread.py
|   |-- auwrite.py
|   |-- __init__.py
|   |-- wavread.py
|   `-- wavwrite.py
`-- __init__.py

你可以将这个package的例子下载下来。如果直接使用import sound来导入这个package,我们可以导入package sound,但是sound的子package(effects,filters,formats)并不会被自动导入。子package不会被自动导入的原因是因为在sound目录下的__init__.py文件并没有任何关于导入子package的代码。

我们来看下在sound目录下的__init__.py的代码

"""An empty sound package

This is the sound package, providing hardly anything!"""


print("sound package is getting imported!")

然后我们导入sound试下

>>> import sound
sound package is getting imported!
>>> sound
<module 'sound' from '/home/bernd/packages/sound/__init__.py'>
>>> sound.effects
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'sound' has no attribute 'effects'

如果你想使用子package的内容,但是在父package的__init__.py的文件内并没有导入,你需要手动导入

>>> import sound.effects
effects package is getting imported!
>>> sound.effects
<module 'sound.effects' from '/home/bernd/packages/sound/effects/__init__.py'>

如果你希望python帮你自动导入sound.effects你可以往sound目录下的__init__.py文件写入

"""An empty sound package

This is the sound package, providing hardly anything!"""

import sound.effects
print("sound package is getting imported!")

那么你下次运行的时候python就会自动帮你导入sound.effects

>>> import sound
effects package is getting imported!
sound package is getting imported!

当然了,除了使用绝对路径你可以使用相对路径来导入sound.effects

"""An empty sound package

This is the sound package, providing hardly anything!"""

from . import effects
print("sound package is getting imported!")

这跟linux的命令行比较像,.代表当前目录,..代表上级目录

所以你可以在sound.effects__init__.py文件内写入

from .. import formats

来导入sound.formats

当你使用sound的时候就会发现,sound.effectssound.formats都被导入了

>>> import sound
formats package is getting imported!
effects package is getting imported!
sound package is getting imported!

最后我想给你展示下,怎么从sound.effects导入sound.filters.karaoke模块,将一下代码加入到sound.effects__init__.py文件中

"""An empty effects package

This is the effects package, providing hardly anything!"""

from .. import formats
from ..filters import karaoke
print("effects package is getting imported!")

激活python的交互环境以后,尝试import sound

>>> import sound
formats package is getting imported!
filters package is getting imported!
Module karaoke.py has been loaded!
effects package is getting imported!
sound package is getting imported!

现在我们可以使用karaoke的函数了

>>> sound.filters.karaoke.func1()
Funktion func1 has been called!
>>> 

把你的整个package都导入进来

还是用前面的例子,这一次,我会额外的加入一个叫做foobar的模块在主目录,你可以在这里下载例子

我们尝试使用*来进行全部的导入

>>> from sound import *
sound package is getting imported!

我们可以看到仅仅是导入了sound这个package但是其他的内容并没有导入。

>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']

__all__

我们可以使用__all__这个魔法变量来手动导入模块和子package,当你定义了__all____init__.py文件以后,python会根据你在list内给出的元素进行逐个导入

__all__ = ["formats", "filters", "effects", "foobar"]

所以我们可以再次导入试试

>>> from sound import *
sound package is getting imported!
formats package is getting imported!
filters package is getting imported!
effects package is getting imported!
The module foobar is getting imported

看下dir()

>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'effects', 'filters', 'foobar', 'formats']
>>>

你会发现所有模块都已经被顺利导入。

那如果我们仅仅导入sound.effectspackage内所有内容呢,会发生什么,我们import的时候到底import的是什么。

我们看下结果

>>> from sound.effects import *
sound package is getting imported!
effects package is getting imported!
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
>>> 

你会发现他仅仅是导入了sound.effects这个package,跟你没有修改sound__init__.py之前是类似情况,仅仅是导入了这个package。

所以你也可以修改sound.effects__init__.py文件来导入effects内的所有模块

__all__ = ["echo", "surround", "reverse"]

看下结果

>>> from sound.effects import *
sound package is getting imported!
effects package is getting imported!
Module echo.py has been loaded!
Module surround.py has been loaded!
Module reverse.py has been loaded!
>>> 
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'echo', 'reverse', 'surround']
>>> 

总结

  1. from package import * 语句中,如果 __init__.py 中定义了 __all__ 魔法变量,那么在__all__内的所有元素都会被作为模块自动被导入(ImportError任然会出现,如果自动导入的模块不存在的话)。
  2. 如果 __init__.py 中没有 __all__ 变量,导出将按照以下规则执行:
    1. 此 package 被导入,并且执行 __init__.py 中可被执行的代码
    2. __init__.py 中定义的 variable 被导入
    3. __init__.py 中被显式导入的 module 被导入

reference

深入理解 Python package
Packages in Python

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容