模块、包、库
import和from ... import ...及as
模块搜索顺序sys.path、模块预加载sys.module、模块的重复导入
模块的属性__file__ 和 __name__ 及if __name__ == '__main__':
相对导入和绝对导入及__init__.py
模块中的访问控制,from ... import *
和__all__
模块、包、库
编程语言中的库,库、包、模块可以理解为同一概念,都是代码的一种组织方式
- 模块module:一个
.py
文件就是一个模块 - 包package:在模块的基础上,将模块组织在一起的(一个个
.py
文件),和包名同名的目录及其相关文件。包目录下必须包含__init__.py
文件,包目录下可包含子目录,若子目录中也包含__init__.py
文件,则可成为子包 - 库:具有相关功能模块的集合。这也是python特色之一,python具有强大的标准库(built-in)如os,sys等、第三方库(如requests等)及自定义库
查看属性dir()
可以使用dir()来查看模块的属性,对于不同的作用域还可使用locals()和globals()
详情可参看魔法函数查看属性dir()和__dict__
导入语句
import和from...import...
import语句
- import 只能是module,不能是类或函数
- import 找到指定的模块,加载和初始化它,生成模块对象。并在import所在的作用域的局部命名空间中,增加名称和其创建的对象关联
- 导入顶级模块,如
import os
,其名称os
会加到本地名词空间中,并绑定到其模块对象 - 导入非顶级模块,如
import os.path
,只将其顶级模块名称os
加入到本地名词空间中。导入的模块必须使用完全限定名称来访问os.path
import functools # import后只能是模块
# import functools.wraps # No module named 'functools.wraps'; 'functools' is not a package
print(dir()) # [..., 'functools']
print(functools) # <module 'functools' from ...>
print(functools.wraps) # <function wraps at 0x00000000010FB400>
import os.path # 虽然导入了os.path,但需要加载os(父模块)和os.path(子模块)
print(os) # <module 'os' from ...>
print(os.path) # <module 'posixpath' from....>
print(dir()) # [...., 'os']只有顶级模块加入到了本地名词空间中
print(os.path.exists('abc')) # 导入的模块必须使用完全限定名称来访问os.path
def test_import():
import os.path # 局部
print(dir()) # ['os']
test_import()
print(dir()) # 无os
print(globals().keys()) # 无os
from 语句
- from ... import ... : from module import [ module | class | function | 其他如全局变量等]
- 找到from子句中指定的模块,加载并初始化它(注意不是导入)
- import子句后的名称:先查from子句导入的模块是否具有该名称的属性->否则尝试导入该名称的子模块-> 否则抛出ImportError异常
- import子句后的名称保存到本地名词空间中
from pathlib import Path # 导入类Path
from os.path import exists # 到如exists函数
print(Path, id(Path))
print(dir()) # ['Path',...,'exitsts']
print(Path) # <class 'pathlib.Path'>
print(exists) # <function exists at 0x10b482f70>
理解import和from的区别
from json import encoder
print(encoder)
print(dir()) # [..., encoder, ...]
d = {'a': 1}
# print(json.dumps(d)) # NameError: name 'json' is not defined
import json.encoder
print(json.encoder)
print(dir()) # [..., json, ...]
d = {'a': 1}
print(json.dumps(d))
from json import encode : 当前名词空间没有json,但是json模块已经加载过了,没有json的引用,无法使用dumps函数
import json.encoder:也加载json模块,但是当前名词空间有json,因此可以调用json.dumps
as子句
- import...as:as后的名称直接绑定到导入的模块对象,并将该名称加入到本地名词空间中
- from ... import ... as:as子句后的名称保存到本地名词
import pathlib as pl # 导入模块使用别名
print(dir())
print(pl)
print(pl.Path, id(pl.Path))
from pathlib import Path as pp # 导入类Path
print(pp, id(pp))
print(dir())
# 可以看出导入的名词pp和pl.Path是同一个对象
自定义模块
# t1.py文件,可认为是一个模块
print('this is t1 module')
x = 100
class A:
def showmodule(self):
print(1, self.__class__.__module__)
print(2, self.__dict__)
print(3, self.__class__.__dict__)
print(4, self.__class__.__name__)
a = A()
a.showmodule()
# t2.py文件
import t1
print(t1.a)
print(t1.x)
print(t1.A)
print(t1.__dict__['A'])
print(t1.__dict__.get('A'))
print(getattr(t1, 'A'))
print(dir(t1))
print(dir())
------执行t2后输出----------
this is t1 module
1 t1
2 {}
3 {'__module__': 't1', 'showmodule': <function A.showmodule at 0x10e1a43a0>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
4 A
<t1.A object at 0x10e192640>
100
<class 't1.A'>
<class 't1.A'>
<class 't1.A'>
<class 't1.A'>
['A', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'x']
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 't1']
模块有严格的边界,无法突破
- 模块命名规则:通常模块名为全小写,下划线来分割;不要使用系统模块名来避免冲突
模块搜索顺序
- sys.path:python模块的路径搜索顺序
import sys
# print(*sys.path, sep='\n')
for p in sys.path:
print(p)
- 可以看出模块的搜索顺序是:
- 程序主目录,程序运行的主程序脚本所在的目录
- PYTHON PATH目录,环境变量PYTHON PATH设置的目录也是搜索模块的路径
- 标准库目录,Python自带的库模块所在目录
当加载一个模块的时候,需要从这些搜索路径中从前到后依次查找,并不搜索这些目录的子目录
搜索到模块就加载,搜索不到就抛异常
路径也可以为字典、zip文件、egg文件
- sys.path返回的是list,因此可以被修改即搜索模块顺序可以被修改,但不建议这么做
- sys.path首先搜索路径是程序运行的主程序目录,因此,一般自定义模块不建议定义与常用模块同名,否则会造成冲突,常用模块的功能被拦截
模块的重复导入
- 所有已加载的模块都会记录在sys.modules中
- sys.modules是存储已经加载过的所有模块的字典
- 模块并不会重复导入
# t1.py文件
print('this is t1 module')
class A:
def showmodule(self):
print(1, self.__class__.__module__)
print(2, self.__dict__)
print(3, self.__class__.__dict__)
print(4, self.__class__.__name__)
a = A()
a.showmodule()
# t2.py文件
print('this is t2 module')
import t1
print('~~~~~')
import t1
print('~~~~~')
import t1
print('~~~~~')
从上例执行结果来看不会出现重复导入的现象
# sys.modules存储已经加载过的所有模块的字典
import sys
print(*sys.modules.keys(), sep='\n')
import os
print(os.path.exists('111'))
从sys.modules打印的结果可以看到os、os.path都已经预加载了,因此尽管只import os,也可以使用os.path
总结:模块搜索顺序,先查看sys.modules中是否已经预加载,然后再按sys.path顺序查找,找不到则抛出异常
模块的属性
# 在模块中如t1.py
print(dir())
print(globals())
--------------
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x102a573a0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'xxx/t3.py', '__cached__': None}
这里主要关注
__name__
和__file__
__name__
和__file__
__name__
:模块名。显示当前模块执行过程中的名称,如果当前程序在这个模块中执行,__name__
就是__main__
,否则就是这个模块的名称
# t1.py文件
print('this is t1 module')
# t2.py文件
import t1
print(1, globals())
print(2, __name__)
print(3, t1.__name__)
----------执行t2输出结果--------
this is t1 module
1 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x1038a63a0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/tina/PycharmProjects/pypractice/t3.py', '__cached__': None, 't1': <module 't1' from '/Users/tina/PycharmProjects/pypractice/t1.py'>}
2 __main__
3 t1
__file__
: 文件(模块)当前位置(路径)
import os
from pathlib import Path
print(__file__)
p = Path(__file__)
print(p)
t1_path = ''
if os.path.exists('t1.py'):
t1_path = p.with_name('t1.py')
print(t1_path)
if __name__ == '__main__':
的用途
- 本模块的功能测试
对于非主模块,测试本模块内的函数、类 - 避免主模块变更的副作用
若没有封装,主模块使用时是没有问题的。但当使用新的主模块并导入老的主模块时,老的主模块不封装的话,一些测试代码等不必要的代码就会一并执行了
包package
- 特殊的模块,一个带有
__init__.py
文件的目录即包 - 包也是模块,但模块不一定是包,包是特殊的模块,是一种组织方式,它包含
__path__
属性
# 如下目录关系
'''
├── m
│ ├── __init__.py
│ ├── m1.py
│ └── m2
│ ├── __init__.py
│ ├── m21
│ │ └── __init__.py
│ └── m22.py
└── t1.py
'''
# t1.py文件
import m.m2.m22
print(dir(m))
print(m.__path__)
print(dir(m.m2.m22))
__init__.py
- 包目录中
__init__.py
是在包第一次导入的时候就会执行 - 内容可以为空,也可以是用于该包初始化工作的代码,最好不要删除(低版本python不可删除
__init__.py
文件)
子模块
- 导入子模块一定会加载父模块,但是导入父模块一定不会导入子模块
- 包能够更好的组织模块,尤其是大的模块,其代码行数很多,可以把它拆分成很多子模块,便于使用某些功能就加载相应的子模块
- 包目录之间只能使用
.
点号作为间隔符,表示模块及其子模块的层级关系
绝对导入和相对导入
绝对导入
- 在import语句或者from导入模块,模块名称最前面不是以
.
点开头的
相对导入
- 只在包内使用,包内部的文件之间互相导入可以使用相对导入,可以通过提前把函数、常量、类导入到
__init__.py
中再在其他文件中导入,可以简化代码 - 只能用在from语句中
- 相对导入,像是目录操作。使用
.
点号,表示当前目录内;..
表示上一级目录 - 一个模块中使用相对导入,就不可以作为主模块运行了(ImportError: attempted relative import with no known parent package)
模块中的访问控制
# t1.py文件
print(__name__)
a = 100
_b = 200
__c = 300
__A__ = 400
# t2.py文件
import t1
print(t1.a, t1._b, t1.__c, t1.__A__)
# from t1 import a, _b, __c, __A__ # 都能正常导入
- 普通变量、保护变量、私有变量、特殊变量,都没有被隐藏。也就是说模块内没有私有的变量,在模块中定义不做特殊处理
from ... import *
# t1.py文件
print(__name__)
a = 100
_b = 200
__c = 300
__A__ = 400
# t2.py文件
from t1 import *
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(locals()['a'])
a = 1000
print(locals()['a'])
结果是只导入了A,下划线开头的都没有导入
__all__
print(__name__)
__all__ = ['a', '_b', '__c'] # must be str
a = 100
_b = 200
__c = 300
__A__ = 400
# t2.py文件
from t1 import *
import sys
print(sorted(sys.modules.keys()))
print(dir())
可以看到使
用from ... import *
导入__all__
列表中的名称
- 如果模块没有
__all__
,from ... import *
只导入非下划线开头的该模块的变量。如果是包,子模块也不会导入,除非在__all__
中设置,或__init__.py
中导入它们 - 如果模块有
__all__
,from ... import *
只导入__all__
列表中指定的名称,哪怕这个名词是下划线开头的,或者是子模块 from ... import *
方式导入,使用简单,但是其副作用是导入大量不需要使用的变量,甚至有可能造成名称的冲突。而__all__
可以控制被导入模块在这种导入方式下能够提供的变量名称,因此,编写模块时,应该尽量加入__all__
参考
- magedu