Python模块(Python Module)
一般来说,在编程语言中。包、库、模块都是指同一个概念,是一种代码的组织方式,将一种功能或若干种封装起来,供自己或他人使用。
Python中只有一种组织方式,称为模块。
module
- Python中的模块module,指的是Python源代码文件
- python中的一个类。模块导入后,会实例化一个module对象方便使用
package
- 包package是一种特殊的module
- 为了便利模块的组织,使用文件夹(目录)加
__init__.py
文件来作为包,包也是一个模块,文件夹中__init__.py
的内容则视为是包的内容。在包中的其他源代码文件则为包的子模块。
import
在python中,可以使用import语句来导入模块,导入后会生成一个模块对象绑定到本地的名词空间中,对象拥有模块中所有的对象。可以通过点运算符来使用。
python中只要一个带有__init__.py
的文件夹或者一个.py
文件都视为是一个模块。都可以通过import语句导入。import有以下几种用法。
测试模块的文件结构
m
+--m1
+--__init__.py
+--m2
+--__init__.py
+--__init__.py
+--test.py
#不带后缀的为包,并且都带有各自的__init__.py文件
m - __init__.py
x = 100 在m模块中有一个属性x=100
导入方式
- import module
print(dir())
import m
print(dir())
print(type(m))
print(m.x)
#['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
#['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'm']
# <class 'module'>
#100
可以看到导入后本地名词空间中增加了一个新对象,与模块名相同。并且可以使用点运算符访问到模块中的成员。
- import module as name
print(dir())
import m as alias
print(dir())
print(type(alias))
print(alias.x)
# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'alias']
# <class 'module'>
#100
增加到名词空间中的对象名称为as后的名称。使用效果与直接导入相同,原名称m并不在其中。
- import package.module
import m.test
print(dir())
print(m.test)
# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'm']
# <module 'm.test' from 'D:\\PythonRoot\\module_test\\m\\test.py'>
注意:import只能导入模块,即无法通过import module.attribute来导入模块中的属性。
这里可以发现即使使用了import m.test
,绑定的名词依旧是m。那么这样与直接导入m有什么差异呢?
import m
print(dir())
print(m.test)
# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'm']
#!AttributeError: module 'm' has no attribute 'test'
发现只导入顶级模块的话,直接使用顶级模块下的其他模块就会引发AttributeError
属性异常。不存在test属性,即test模块没有被导入。
import m.test
print(m.test)
print(m.m1)
#<module 'm.test' from 'D:\\PythonRoot\\module_test\\m\\test.py'>
#!AttributeError: module 'm' has no attribute 'm1'
使用import m.test
发现,只有test模块被加载了,与test同级的m1没有被加载
import m.m1.m2
print(m)
print(m.m1)
print(m.m1.m2)
# <module 'm' from 'D:\\PythonRoot\\module_test\\m\\__init__.py'>
# <module 'm.m1' from 'D:\\PythonRoot\\module_test\\m\\m1\\__init__.py'>
# <module 'm.m1.m2' from 'D:\\PythonRoot\\module_test\\m\\m1\\m2\\__init__.py'>
导入子模块时,父模块也会被导入
- import package.package.module as name
import m.m1.m2 as alias
print(alias)
#<module 'm.m1.m2' from 'D:\\PythonRoot\\module_test\\m\\m1\\m2\\__init__.py'>
可以通过as name的方式,直接将模块绑定到名词上,方便使用。
总结
- 通过import导入模块后会生成一个模块对象,可以通过模块对象访问到其中的对象。
- import只能导入模块
- 加载子模块,则一定会加载其父模块
- import分两步,加载和将名词导入。在加载时,只会加载显式导入的模块,例如
import m.test
只会加载m模块和test模块,虽然名词空间中只有m,但是可以使用m.test
访问到test模块。import m
只会加载m模块,使用m.test
时则会导致异常。 - 在名词导入时,默认只会将顶级模块名导入,使用as则会把模块绑定到指定的名词并导入。
from import
在Python中,为了方便使用还提供了另一种导入方式,即from import
用法
- from package import module
from m import test
print(dir())
#['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'test']
通过这种方式能直接把test模块对象的名称直接导入到当前的名词空间中,相当于import m.test as test
,这里的package可以是非顶级的包。即可以用import m.m1 import m2
的方式来导入m2模块。
- from module import attribute
from m import x
print(dir())
print(x)
# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'x']
# 100
可以使用这种方式直接导入模块中的属性
- from import 能够导入模块或者属性,那么如果在一个模块下有一个子模块和其属性同名呢?
在m的__init__.py
文件中加入test = 'attribute m.test'
from m import test
print(test)
#attribute m.test
可以发现导入对象并不是一个模块,即from import会优先导入非模块的属性
总结
- from import能够导入模块或者属性,并且会将导入的模块或者属性直接加入到本地名词空间中
- from后必须是一个模块,语句无法从其他对象导入属性
- 如果有同名的属性和模块,会优先导入非模块属性
模块对象属性
在Python3之中,万物皆对象,module也是一个对象,可以通过其 __dict__
来查看其属性。
import m
print(m.__dict__.keys())
#dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'x', 'test'])
发现除了x和test自己添加的属性外,默认还带有许多属性。
变量名 | 类型 | 作用 |
---|---|---|
__name__ | str | 模块名 |
__doc__ | str | 文档字符串 |
__package__ | str | 所在的包名,如果是包,则是自身名称 |
__spec__ | _frozen_importlib.ModuleSpec类实例 | 模块使用的标准 |
__path__ | list | 包的文件夹所在的路径。只有包有该属性。 |
__file__ | str | 文件的路径。如果是包,则是__init__.py 文件的 路径。 |
__cached__ | str | 记录模块编译后生成.pyc 文件的位置 |
查找顺序和加载方式
Python中使用一个list来存储搜索模块时的路径,优先级从低到高依次降低。从索引0开始查找,只要找到了就停止,找不到返回importError异常。
sys.path
sys.path
中存放了查找模块时所用到的路径。是一个有序的可写的list,可以使用list操作往其中增加或删减路径。
注意:sys.path
必须存放绝对路径
sys.module
在m模块中加入打印语句print(__name__)
import m
import m
#m
可以发现打印语句只执行了一次,说明在Python中存在一个导入机制,不会导入重复的模块。
Python将导入的模块放到了sys.module
,是一个dict类型。解释器在运行时,发现要加载的模块已经加载过了,就只使用一个名称来绑定这个模块,不再重新导入。我们可以也可以使用这个字典直接来访问已经加载过的模块。
主模块
- 程序中的第一个入口,其他模块皆为该模块直接或间接加载
- 运行的第一个
.py
文件 __name__
属性名为'__main__'
if __name__ == '__main__':
pass
利用这一特性,可以用上面语法来写模块的测试语句。只有当此模块为主模块时才会执行其中语句。
绝对导入和相对导入
绝对导入
文章至此,使用的都是绝对导入,使用一个模块名,从sys.path
中搜索路径导入的方式都是绝对导入。
相对导入
- 相对导入只能在
from import
中使用 - 只能在一个包内使用
- 使用相对导入后的模块无法作为主模块来使用
用法
与绝对路径相似,但在from后使用相对路径
符号 | 含义 |
---|---|
. | 当前目录 |
.. | 上级目录 |
... | 上上级目录 |
比如可以在m2中使用from ... import test
来访问m下的test模块,也可以使用from ..package import module
来导入同级目录包内的模块,from .module import attribute
导入同级目录下模块属性。
需要注意的是,如果是包,是以__init__.py
文件位置为准,并非文件夹。
from import *
-
from import
可以使用from module import *
的方式来简易得导入多个属性 -
默认只会导入非下划线开头的变量名,不会导入模块,如果导入的模块中显式地导入了其他模块,且导入模块使用的变量名非下划线开头,该模块对象也会被导入。
-
__all__
属性- 模块默认没有定义
__all__
属性,当没有该属性时,from import *
只会导入非下划线开头以及非模块属性。 __all__
是一个可迭代对象,常用tuple和list- 如果定义了
__all__
,from import *
只会导入其中的变量,并且不管是否是模块(这里的模块指的是未被加载的子模块)或是下划线开头。
- 模块默认没有定义