模块与import
Python中import机制可以导入我们需要使用的库,避免编写重复代码。此篇我们来看看什么事模块,又该如何导入模块。
1. 模块 与 import
【模块Module】在.py文件中定义的函数、全局变量都是可以提供给外界使用的工具,因此一个.py文件可以称为一个模块, 模块名为源码文件名。Python标准库/第三方库都由一个个模块组成的,我们也可以编写属于自己的个人工具库,该工具库由若干个人模块组成。 相关概念:
- 模块:一个以.py为后缀的文件就是一个模块
- 常规包:__init__.py所在目录就是一个常规包
- 命名空间包:命名空间包是一种虚拟的概念,它由多个子包构成,这些子包可以在任意位置,可以为 zip 中的文件或网络上的文件,这些子包在概念上是一个整体,这个整体就是一个命名空间包.「常规包」与「命名空间包」的概念在 PEP420 被提出,在 Python3.3 及之后的 Python 版本中实现,此前只有「常规包」这一种包。
【导入import】在导入模块的过程中,首先会搜索 sys.modules中内容,sys.modules 是模块的缓存,其中包括了所有此前导入的模块。 import 一个新的模块时,实际上完了两个操作:
- 搜索操作:在sys.path定义的路径中搜索指定名称的模块
- 绑定操作:将搜索到的结果绑定到当前作用域对应的名称上 (即创建 module对象,并初始化),[如果未找到,则抛出 ModuleNotFoundError]
【import 模块的两种方式】
# 模块导入的两种方式
import os # 方式1:import后面只能接模块或包 (常规包或命名空间包)
from os.path import join # 方式2:from后只能接模块或包,而此时import后可以接任何变量 (模块、包或模块中具体的方法)
# 方式1 import A时,会先检查 sys.moduels 中是否存在模块A,如果存在,则不会再去搜索加载;如果没有,则搜索模块并进行绑定操作,随后便将其加载到 sys.modules 中
# 方式2 from A import B时,此时依旧会先检查 A 并进行相同的操作,随后获得 A 的 module对象后,从中解析并寻找 B 然后再填充到 A 的__dict__结构中,从而实现不用标明来自于 A,直接通过 B 的名称就可直接使用的 B 的效果。
Python2.6 之前使用 relative import (相对导入) 作为默认 import 机制,
Python2.6 之后使用 absolute import (绝对导入) 作为默认 import 机制;
# 1.相对导入格式为:其中.表示当前模块,.. 表示上层模块 (与相对路径概念类似)
from . import B
from ..A import B
# 2. 绝对导入格式为
import A.B.C
form A.B import C
# 相对导入/绝对导入 这两个概念与相对路径/绝对路径的概念有些相似
# 在开发第三方模块时,通常使用相对导入的形式,这可以有效避免硬编码带来的问题,比如在开发过程中,修改了某个文件的名称,
# 此时,使用绝对导入形式的import就需要修改相应的名称。
# 这其实也不是绝对的,当包的结构发生改变时,相对导入就会出现问题,当相对模块名称的改变而言,目录结构的改变出现概率会小一些。
Tips && Todo:
- import 是最常用的导入机制,但并不是唯一的方式,importlib 模块以及内置的__import__() 方法都可以实现模块的导入。
- Python中定义了多种搜索策略去搜索相应的模块,而这些策略可以通过 importlib 等提供的各类 hook 进行修改实现一些有趣的效果。在 Python3.3 之后,所有的模块导入机制都会通过「sys.meta_path」暴露,不会在有任何隐式导入机制
- .pyc是编译过的二进制文件,Python作为一个解释性的语言,解释一行执行一行。import模块时,在执行的时,解释器将模块文件编译成.pyc文件,方便快速使用。因为库文件一般很少变动,如果对修改了模块内容,解释器会重新编译。.pyc文件是解释器加快运行速度,而对库文件进行统一编译而生成的一个二进制文件
参考博文:详解 Python import 机制 (一):import 中的基本概念
2. sys.path
sys.path: A list of strings that specifies the search path for modules. Initialized from the environment variable PYTHONPATH, plus an installation-dependent default. [From python document]
sys.path为import模块时的查询路径列表,为一个[List]。使用环境变量PYTHONPATH 、默认安装依赖 初始化。sys.path列表默认包含的路径有:
- current working directory/the script’s directory/an empty string
- 环境变量 PYTHONPATH 中指定的路径列表
- Python安装路径的lib目录所在路径
sys.path[0]: sys.path列表的第一项,与程序启动方式有关。在import模块时,sys.path[0]它将引导Python首先搜索当前目录中的模块。
【python 启动方式】
python script.py command line: prepend the script’s directory. If it’s a symbolic link, resolve symbolic links.
python -m module command line: prepend the current working directory.
python -c code and python (REPL) command lines: prepend an empty string, which means the current working directory.
我们可以将需要的路径(个人工具库/第三方库)添加到sys.path中,修改sys.path的3种方式如下:
2.1 sys.path.append() 和 sys.path.insert()
动态修改 sys.path:因为 sys.path为list,所以list操作append()和insert()可很轻松实现搜索路径修改,这种方式只会对当前项目临时生效。
(base) chenyingying@cyy-2 import_test % python
>>> import sys
>>> sys.path # (...手动修改省略显示)
['', '/home/libtestpath/path1', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7', ...]
>>> sys.path.append("cyy_test1")
>>> sys.path.insert(0, "cyy_test0")
>>> sys.path
['cyy_test0', '', '/home/libtestpath/path1', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7', ..., 'cyy_test1']
2.2 PYTHONPATH
修改 PYTHONPATH 环境变量,这种方式会永久生效,而且所有的 Python 项目都会受到影响,因为 Python 程序启动时会自动去读取该环境变量的值。
# 默认情况下PYTHONPATH是空的, sys.path是一个列表,包括有所有查找包的目录 (...手动修改省略显示)
(base) chenyingying@cyy-2 import_test % echo $PYTHONPATH
(base) chenyingying@cyy-2 import_test % python
>>> import sys
>>> sys.path
['', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7', ....]
# 修改环境变量PYTHONPATH(以:分隔各个路径), sys.path中新增了PYTHONPATH的内容
(base) chenyingying@cyy-2 import_test % export PYTHONPATH=/home/libtestpath/path1
(base) chenyingying@cyy-2 import_test % echo $PYTHONPATH
/home/libtestpath/path1
(base) chenyingying@cyy-2 import_test % python
>>> import sys
>>> sys.path
['', '/home/libtestpath/path1', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7', ...]
Linux export 命令用于设置或显示环境变量。在 shell 中执行程序时,shell 会提供一组环境变量。export 可新增,修改或删除环境变量,供后续执行的程序使用。export 的效力仅限于该次登陆操作。
学习如何在Linux中使用export命令
TODO:如何删除环境变量
2.3 增加.pth后缀的文件
增加.pth 后缀的文件。在 sys.path 已有的某一个目录下添加.pth 后缀的配置文件,该文件的内容就是要添加的搜索路径,Python 在遍历已有目录的过程中,如果遇到.pth 文件,就会将其中的路径添加到 sys.path 中。
例如:在 /usr/lib/python2.6/site-packages 下面新建一个.pth 文件(以pth作为后缀名);将模块的路径写进去,一行一个路径:
vim pythonmodule.pth
/home/liu/shell/config
/home/liu/shell/base
参考博文:
Buildout自动化构建打包工具–闲侃 sys.path 与 buildout 实践
启动方式差异给sys.path带来的差异–Python sys.path详解
增加.pth文件–python之sys.path介绍
3.__ init__.py
__ init__.py文件的一个作用
- 是标识一个文件夹是一个python常规包
- 定义模糊导入时要导入的内容,当我们使用from package import * 导入语句时候,即在使用模糊导入,这时包的编写者需要再在__init__.py文件中定义 all 的内容来限制模糊导入的内容。这样可以避免将一些只在包内使用的方法或变量暴露给用户。
如果目录中包含了 __ init__.py 时,当用 import导入该目录时,会执行 __ init__.py 里面的代码,init.py简化导入过程:假设我们有一个 models文件夹,文件夹里有一个 User.py文件,这个文件里保存着我们的 User表的类。
# __init__.py中未填写内容时的导入方式
from app.models.User import User
# __init__.py中填写如下内容时的导入方式
# from .User import User
from app.models import User
参考博文:-- python包中__init__.py文件的作用
todo :模糊导入__ init__.py __ all__ 填写Python Tips: init.py的作用
4.导入用户模块demo
(base) chenyingying@cyy-2 import_test % tree
.
├── branch
│ ├── m3.py
│ ├── m4.py
│ └── sub_branch
│ ├── __init__.py
│ └── m5.py
├── m1.py
├── m2.py
└── m2.pyc
# __init__.py
from branch.sub_branch.m5 import print_myself
# m5.py
#coding=utf-8
def print_myself():
print("I am m5")
# m4.py
#coding=utf-8
import m3
def print_myself():
print("I am m4")
# m3.py
#coding=utf-8
def print_myself():
print("I am m3")
# m2.py
# coding = utf-8
def print_myself():
print("I am m2")
# m1.py
#coding=utf-8
"""
@brief : test import module
"""
import sys # 导入标准库
import m2 # 导入同路径下m2.py文件,使用其中定义的函数/类, 模块名为文件名
from branch import m3 # python2这么写是有问题的,python3没问题
if __name__ == "__main__":
for _ in sys.path:
print("sys.path", _)
m2.print_myself()
m3.print_myself()
--------------------------
(base) chenyingying@cyy-2 import_test % python3 m1.py
sys.path /pathxxx/import_test
sys.path /home/libtestpath/path1
sys.path /opt/homebrew/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python39.zip
sys.path /opt/homebrew/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9
sys.path /opt/homebrew/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload
sys.path /opt/homebrew/lib/python3.9/site-packages
I am m2
I am m3
--------------------------
(base) chenyingying@cyy-2 import_test % python2 m1.py
Traceback (most recent call last):
File "m1.py", line 7, in <module>
from branch import m3 # python2这么写是有问题的,python3没问题
ImportError: No module named branch
# m1.py
#coding=utf-8
import m2 # 导入同路径下m2.py文件,使用其中定义的函数/类, 模块名为文件名
m2.print_myself()
from branch import m3 # python2这么写是有问题的,python3没问题
m3.print_myself()
from branch.sub_branch.m5 import print_myself
print_myself()
import branch.sub_branch
branch.sub_branch.m5.print_myself()
branch.sub_branch.print_myself() # 需要配合__init__.py 文件使用,可以省略中间m5 文件名
----------------------
(base) chenyingying@cyy-2 import_test % python3 m1.py
I am m2
I am m3
I am m5
I am m5
I am m5
demo演示来源-实验结论和该博文给出的有出入–Python中import的用法
demo 中__ init__.py 文件演示参考–Python Tips: init.py的作用
–Python中import的用法