1 导入模块后具体做了什么?
2 从哪个位置找到需要导入的模块
3 导入模块的常见场景?
1 导入模块后具体做了什么?
1.1 第一次导入
- 在自己当下的命名空间中,执行所有代码
- 创建一个模块对象,并将模块内所有顶级变量以属性的形式绑定在模块对象上
- 在 import 的位置,引入 import 后面的变量名称到当前命名空间
1.1.1 在自己当下的命名空间中,执行所有代码
Test2.py
name = "Test2"
age = 2
print("执行了Test2所有代码")
Test1.py
import Test2
# 让后 run 该分代码
>>>>打印结果
执行了Test2所有代码
- 也就是说 Test1.py 中导入了 Test2模块后,在运行 Test1模块代码时,会先让 Test2中的代码在 Test2 的自己的命名空间中执行 Test2的所有代码
- 其实,从使用角度来理解的话,当然是 Test2 内的代码先跑完,Test1 中才能使用 Test2 中的变量等资源啊。
1.1.2 创建一个模块对象,并将模块内所有顶级变量以属性的形式绑定在模块对象上
(具有内存地址及类型)
Test2.py
name = "Test2"
age = 2
print("执行了Test2所有代码")
Test1.py
import Test2
# 查看Test2
print(Test2)
# 查看内存地址
print(id(Test2))
# 查看类型
print(type(Test2))
# 通过打印 Test2 对象的属性,验证在 Test1 中需要通过 模块名.属性形式访问的原因
print(Test2.__dict__)
>>>>打印结果
执行了Test2所有代码
<module 'Test2' from '/Users/xxx/Desktop/PythonProject/PackageAndModule/Test2.py'>
4560942648
<class 'module'>
# __dict__ 字典打印结果没有天上自己试验
1.1.3 在 import 的位置,引入 import 后面的变量名称到当前命名空间
- 在 Test1 中通过 Test2变量名来使用 Test2 模块的资源
Test2.py
name = "Test2"
age = 2
print("执行了Test2所有代码")
Test1.py
import Test2
print(Test2.name)
print(Test2.age)
>>>>打印结果
执行了Test2所有代码
Test2
2
- 将 Test2 这个指向 Test2 模块的对象变量名引入 当前 命名空间中(注意命名空间即作用域的区别)
Test1.py
def testFunc():
import Test2 # 导入到了函数的命名空间中
print(Test2.name)
testFunc()
print(Test2.name) # 全局命名空间不能使用 Test2
>>>>打印结果
Traceback (most recent call last):
执行了Test2所有代码
File "/Users/xxx/Desktop/PythonProject/PackageAndModule/Test1.py", line 14, in <module>
Test2
print(Test2.name)
NameError: name 'Test2' is not defined
1.2 第二次导入
1.2.1直接执行1.1.3这个步骤
Test2.py
name = "Test2"
age = 2
print("执行了Test2所有代码")
Test1.py
import Test2
print(Test2.name)
print(Test2.age)
# 再次导入, 打印中并没有出现两次“执行了Test2所有代码”
import Test2
>>>>打印结果
执行了Test2所有代码
Test2
2
1.3 注意
from ... import ... 导入方式与 import ... 导入方式底层执行步骤区别
- 两种导入方式均都会大致的执行上述的3个步骤
- 多次导入模块,该模块并不会执行多次
- 两种导入方式并不存在哪一种更节省内存之说,区别在于把哪一部分内容拿到当前位置来用,因为一旦导入都将模块内的对象创建好。
Test2.py
name = "Test2"
age = 2
print("执行了Test2所有代码")
Test1.py
from Test2 import name
print(name)
>>>>打印结果
执行了Test2所有代码
Test2
2 模块检索顺序(从哪个位置找到需要导入的模块)
2.1 第一次导入
- 按照模块检索路径顺序查找
1 内建模块
2 sys.path
2.1 当前目录环境变量PYTHONPATH
2.2 Python 安装路径
2.3 安装路径下的.pth 路径
2.4 lib 库 site-packages
2.5 lib 库 site-packages下的 pth 文件配置
3 其他
2.2.1 第一层级
- 内置模块<built - in>
2.2.2 第二层级
- sys.path
>>> import sys >>> print(sys.path) ['', '/usr/local/Cellar/python3/3.6.4_1/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/usr/local/Cellar/python3/3.6.4_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6', '/usr/local/Cellar/python3/3.6.4_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', '/usr/local/lib/python3.6/site-packages'] >>>
- 构成 (如下索引并非指优先顺序)
1.1 当前目录
1.2 环境变量 PYTHONPATH 中指定的路径列表
1.3 特定路径下的 .path 文件中的文件路径列表
1.4 在 Python 安装路径的 lib 库中搜索
- 追加路径的方式(修改 sys.path 方式)
-
直接修改 sys.path,只作于本次的执行代码模块
Test1.py
import sys
sys.path.append("/Users/xxx/Desktop/myTest")
print(sys.path)
import fkm
# print(fkm.__dict__)
print(fkm.age)
>>>>打印结果
打印了abc 所有代码
12
注意该路径是追加到sys.path最后的,当 工程目录中有同名模块时,会优先加载工程内的同名模块
修改环境变量 PYTHONPATH(自行百度)
实际,修改PYTHONPATH环境变量也是修改 sys.path 列表内容,将环境变量PYTHONPATH代表的路径添加到 sys.path中,只不过是默认就给添加了,不同于通过代码修改。
注意,系统修改 PYTHONPATH 和 pycharm 添加环境变量的区别-
添加.pth 文件
添加 .pth 文件位置可通过 site 模块查看import site print(site.getsitepackages()) >>>>打印结果 ['/usr/local/Cellar/python3/3.6.4_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages', '/Library/Python/3.6/site-packages']
只需在以上目录下创建.pth 文件,然后在文件中添加对应模块路径即可
2.2 第二次导入
1 从已经加载过的模块中去找
- 查看已加载模块
import sys
sys.modules
3 导入模块的常见场景?
3.1 局部导入
def testFunc():
import Test2 # 导入到了函数的命名空间中
print(Test2.name)
testFunc()
使用的时候才进行导入,此时 Test2 是一个局部变量,只存在于函数作用域中;减少内存的开销;
3.2 覆盖导入
场景一:
自定义模块和非内置的标准模块重名,根据前者的存储位置,有可能前者会覆盖后者
建议:自定义模块命名不要与后者重名场景二:
自定义模块与内置模块重名,内置肯定覆盖自定义
建议:如果确实需要使用重名的自定义模块,可以使用 from ... import ...方式指明绝对路径进行导入使用
from package1 import sys
print(sys.name)
3.3 循环导入
- 模块 A 中导入模块 B, 模块 B 中又导入模块 A;
Test2.py
t2_1 = "t2_1"
t2_2 = "t2_2"
import Test1
print(Test1.t1_1)
print(Test1.t1_2)
Test1.py
t1_1 = "t1_1"
t1_2 = "t1_2"
import Test2
print(Test2.t2_1)
print(Test2.t2_2)
>>>>打印结果
t2_1
t2_2
t1_1
t1_2
t2_1
t2_2
3.4 可选导入
- 两个功能相近的包,根据需求优先选择其中一个导入
- 想优先使用 A;当没有 A 的情况下,使用 B 作备份
try:
import Testxxx as t
except ModuleNotFoundError:
import Test1 as t
print(t.name)
3.5 包内导入
明确一下概念
绝对导入和相对导入,这两个概念是相对于包内导入而言的
包内导入:即是包内的模块导入包内的模块,即package1包内的 Test3 内导入 Test3_1
包外导入:即是包外的模块导入包内的模块,即 Test1 内导入package1包内的 Test3
查看当前文件参照的执行路径
import sys
sys.path
- 查看当前模块名称
如果文件是直接运行的话,__name__的值是 __main__
如果是以模块的形式进行加载文件的话,该名称的值则是由加载路径决定,格式为:包名.子包名.模块名 (此时,第一层包名称作为顶级名称)
print(__name__)
Test1.py
import package1.Test3
Test3
- 查找文件顺序都是先 内置<built-in> 再 sys.path
- 当点击运行(使用解析器去执行)某一 py 文件时,此时程序会确定当前py 文件所在的目录,并且将该目录添加到 sys.path 中,此时的 sys.path内容基本确定
3.5.1 概念
3.5.1.1 绝对
- 参照sys.path路径进行检索
- 指明包名或模块名
import a
、from a import b
- 上述两点是基于 Python3.x 之后
3.5.1.2 相对
- 使用
.
来指代相对路径
.
:根据模块名(__name__)称所获取的当前目录
..
:根据模块名(__name__)称所获取的上层目录 - 如:
from . import a
、from .. import a
3.5.2 补充
- Python3.x 版本之前,直接使用 import a。会优先从本地当前目录查找,并非是 sys.path
- 解决方案:
from __future__ import absolute_import
,使用该代码语句改变 import 行为
3.5.3 结论
- 包内导入,使用相对导入
- 包外导入,使用绝对导入
例子1:Test1 导入 Test3,Test3 内又导入Test3_1;即包外导入+包内导入
Test1.py
import package1.Test3
或
from package1 import Test3
Test3.py
from . import Test3_1
不建议写成 from package1 import Test3_1 ,因为当包名变化时,使用包名的地方都要改动
例子2:多层级导入
Test1.py
print("Test1")
print(__name__)
from package1 import Test3
Test2.py
print("Test2")
print(__name__)
Test3.py
print("Test3")
print(__name__)
from .package_sub1 import Test4
Test3_1.py
print("Test3_1")
print(__name__)
Test4.py
print("Test4")
print(__name__)
from .package_subsub1 import Test5
Test4_1.py
print("Test4_1")
print(__name__)
Test5.py
print("Test5")
print(__name__)
from .. import Test4_1
from ... import Test3_1
# from .... import Test2 #包内不能获取包外的文件
运行 Test1.py
>>>>打印结果
Test1
__main__
Test3
package1.Test3
Test4
package1.package_sub1.Test4
Test5
package1.package_sub1.package_subsub1.Test5
Test4_1
package1.package_sub1.Test4_1
Test3_1
package1.Test3_1
- 可以通过上述例子,了解点点的作用
- 注意:包内不能获取包外的文件