彻底明白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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值