本文测试环境为python3。这里先简单介绍下与 import 导入相关的概念。
前期铺垫
python module (python 模块)
module 定义
- 是一个以
.py、.pyo、.pyc、.pyd、.so、.dll
等结尾的文件; - 文件内包含了Python定义的变量、函数、类和及其它可执行语句。
- 是一个以
module 来源
- Python内置的模块(标准库)
- 第三方模块
- 自定义模块
module 使用
当做脚本直接运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# test_script.py
#!/usr/bin/python3
def func():
print("Hi, I am function")
class TestClass(object):
def func(self):
print("Hi, I am class")
if __name__ == "__main__":
func()
TestClass().func()
[root@hadoop-centos-01 python_exm]# python test_script.py
Hi, I am function
Hi, I am class导入其它模块
以上述test.py模块为例
1
2
3
4
5
6import test_script
test_script.func()
Hi, I am function
test_script.TestClass().func()
Hi, I am class1
2
3
4
5
6from test_script import func, TestClass
func()
Hi, I am function
TestClass().func()
Hi, I am class注:若发生导包错误,如下:
1
2
3
4import test_script
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'test_script'需添加当前脚本的路径到
sys.path
中,如下:1
2
3
4
5
6
7
8
9import sys
'/opt/test/python_exm') sys.path.append(
# 重新导入,无错误产生
import test_script
dir(test_script)
['TestClass', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'func']
注:sys.path是python的搜索模块的路径集,其为一个list
python package (python 包)
package 定义
是一个包含了
__init__.py
文件的文件夹;__init __.py
文件用于组织包(package),方便管理各个模块之间的引用、控制着包的导入行为;__init __.py
文件可以为空,但必须存在;__init __.py
文件在包被导入时会自动运行所有的包都是模块,但并非所有模块都是包。 或者换句话说,包只是一种特殊的模块。任何具有
__path__
属性的模块都会被当作是包。示例:
1
2
3
4
5
6
7
8
9
10test_package/
├── __init__.py
├── pack1
│ ├── __init__.py
│ ├── mod1.py
│ └── mod2.py
└── pack2
├── __init__.py
├── mod1.py
└── mod2.py
package 导入
1
2
3import test_package
注:通过此种方式导入包,其实际是运行了 __init__.py 文件, 故将需要导入的模块导入到 __init__.py 文件即可1
from test_package import pack1, pack2
命名空间
定义
- 名称到对象的映射。
- 命名空间是一个字典的实现,键为变量名,值是变量对应的值。
- 各个命名空间是独立没有关系的,一个命名空间中不能有重名,但是不同的命名空间可以重名而没有任何影响。
分类
- 局部命名空间(Local Namespace),函数运行时创建,记录了函数中定义的所有变量,包括函数的入参、内部定义的局部变量。
- 全局命名空间(Global Namespace),每个模块加载时创建,记录了模块中定义的变量,包括模块中定义的函数、类、其他导入的模块、模块级的变量与常量。
- 内建命名空间(Built-in Namespace ),任何模块均可以访问,放着内置的函数和异常。
生命周期
- Local Namespace 在函数被调用时创建,结束时销毁。
- Global Namespace 在模块被加载时创建,通常一直保留直到python解释器退出。
- Built-inNamespace 在python解释器启动时创建,一直保留直到解释器退出。
创建顺序
- 内建命名空间 -> 全局命名空间 -> 局部命名空间
销毁顺序
- 局部命名空间 -> 全局命名空间 -> 内建命名空间
变量查找顺序
- 局部命名空间 -> 全局命名空间 -> 内建命名空间
- 若在这些命名空间找不到相关变量,Python会放弃查找并且引发一个NameError异常
访问
局部命名空间可以通过 locals() 访问
locals()返回一个键/值的dict,键为变量名字(str形式),值为该变量的实际值
全局命名空间可以用 globals() 访问
注:
1
2
3
4
5
6
7
81. 模块的命名空间不仅包括模块的常量和变量,还包括模块中定义的函数和类。此外还包括,任何被导入模块中的东西。
2. 内置命名同样放置在一个模块中,被称作builtins
3. from module import function 和import module不同
使用import module,模块被导入,但是它仍保持自己的命名空间,所以需要模块名来访问函数或者属性(例如module.function);
使用from module import function ,实际上是直接从另一个模块中导入相关属性或函数,因此可以直接访问而不需要知道它们的来源。使用globals函数可以实现。
4. locals是只读的(不能改变),globals不是(可以改变)
locals没有返回局部命名空间,它返回的是一个拷贝。所以对它进行改变,对局部命名空间中的变量值没有影响
globals返回全部实际命名空间,而非拷贝。所以globals返回的dict任何改动都会影响到全局变量
作用域
定义
访问变量时所查找的区域,python中作用域规则可以简单的归纳为LEGB原则:
1
2
3
4L:local,局部作用域,即函数中定义的变量;
E:enclosing,外部嵌套函数作用域,即包含此函数的上级函数的局部作用域,但不是全局的;
G:global,全局作用域,就是模块级别定义的变量;
B:built-in,内建作用域,系统固定模块里面的变量,比如:int,bytearray等查找顺序
局部作用域 ->外部嵌套函数作用域 -> 全局作用域 -> 内建作用域,即 L -> E -> G -> B
global 关键字
global 关键字用来在函数或其他局部作用域中使用全局变量。如果不修改全局变量,根据变量查找规则,也可以不使用global关键字。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#!/usr/bin/env python3
gloabl_var = 1
def func1():
print(gloal_var) # 若不修改全局变量,可直接使用
def func2():
global gloabl_var
gloabl_var += 1
print(gloabl_var)
if __name__ == "__main__":
func1() # 1
func2() # 2global 关键字用来在函数或其他局部作用域中声明全局变量。
1
2
3
4
5
6
7
8
9
10
11#!/usr/bin/env python3
def func():
global gloabl_var # 声明的全局变量可在全局命名空间内使用
gloabl_var = 1
print(gloabl_var)
print(gloabl_var) # 1
if __name__ == "__main__":
func()
nonlocal关键字
nonlocal 关键字用来在函数或其他作用域中使用外层(非全局)变量。
1
2
3
4
5
6
7
8
9
10
11
12#!/usr/bin/env python3
def func():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
if __name__ == "__main__":
func()() # 1
注:Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if ~ elif ~ else、try ~ except、for ~ else 、while ~ else等)是不会引入新的作用域的,故在这些语句内定义的变量,外部也可以访问。
import 导入
Python import 搜索路径
- 在当前目录下搜索该模块
- python安装目录,UNIX下,默认路径一般为/usr/local/lib/python/
- 在 Python 安装路径的 lib 库中搜索
- python3.x 中.pth 文件内容
注:import 搜索路径是按照 sys.path
中的路径列表来搜索的。
1 | import sys |
Python import 导入步骤
python 所有加载的模块信息都存放在 sys.modules
结构中,当 import 一个模块时,会按如下步骤来进行:
- 如果是
import A
,检查sys.modules
中是否已经有 A,如果有则不加载,如果没有则为 A 创建 module 对象,并加载 A 到sys.modules
中 - 如果是
from A import B
,先为 A 创建 module 对象,再解析A,从中寻找B并填充到 A 的__dict__
中
Python import 导入方式
Python 相对导入与绝对导入是相对于包内导入而言的。所谓包内导入是指包内的模块导入包内部的模块。
绝对导入
1
2
3
4
5import A.B
或
from A import B
注:module A的路径应在 sys.path 列表中,否则会引发 ModuleNotFoundError 异常相对导入
1
2
3
4
5
6from . import B
或
from ..A import B
注:.代表当前模块,..代表上层模块,...代表上上层模块,依次类推。
相对导入时不能超过包的顶层,即不能导入包以外的模块或包注:
Python2.x 缺省为相对路径导入,Python3.x 缺省为绝对路径导入。
相对导入可以避免硬编码带来的维护问题,例如我们改了某一顶层包的名,那么其子包所有的导入就都不能用了。
绝对导入可以避免导入子包覆盖掉标准库模块(由于名字相同,发生冲突)。如果在 Python2.x 中要默认使用绝对导入,可以在文件开头加入如下语句:
1
from__future__ import absolute_import
Python 导包异常处理
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38test_package/
├── __init__.py
├── pack1
│ ├── __init__.py
│ ├── mod1.py
│ └── mod2.py
└── pack2
├── __init__.py
├── mod1.py
└── mod2.py
# 在根目录下的 __init__.py 文件写入下列语句
import test_package.pack1
import test_package.pack2
在使用 import test_package 导入包时,可自行导入 pack1 和 pack2 两个包,
import test_package
dir(test_package)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'pack1', 'pack2', 'test_package']
# 在pack1 __init__.py 文件写入下列语句
import test_package.pack1.mod1
import test_package.pack1.mod2
在使用 from test_package import pack1 导入包时,可自行导入 pack1 包下的 mod1 和 mod2 模块
from test_package import pack1
dir(pack1)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'mod1', 'mod2', 'test_package']
# 在pack2 __init__.py 文件写入下列语句
from test_package.pack1.mod1 import pack1_func_1
from test_package.pack1.mod2 import pack1_func_2
在使用 from test_package import pack2 导入包时,可自行导入 pack2 包下的 mod1 和 mod2 模块中的pack1_func_1 和 pack1_func_2 函数
from test_package import pack1
dir(pack1)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'mod1', 'mod2', 'pack1_func_1', 'pack1_func_2']错误处理
ModuleNotFoundError: No module named ‘test_package’
1
2
3
4import test_package
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'test_package'ModuleNotFoundError 异常是由于找不到 test_package 包的路径造成的,解决方法:
在
sys.path
中添加test_package
包所在的路径1
2
3
4
5try:
import test_package
excep ModuleNotFoundError:
import sys
sys.path.append('path') # path 为test_package所在的路径将
test_package
包所在的路径添加到PYTHONPATH环境变量中1
2
3
4
5cd ~;vim .bashrc
export PYTHONPATH='path' # path为test_package所在的路径
# 保存退出,并重新激活 source .bashrc
ValueError: attempted relative import beyond top-level package
1
2
3
4
5
6# 在pack2下的mod1.py通过 from ..pack1 import mod1 的方式导入pack1下的mod1, 并执行pack2下的mod1.py
[root@hadoop-centos-01 pack2]# python3 mod1.py
Traceback (most recent call last):
File "mod1.py", line 3, in <module>
from ..pack1 import mod1
ValueError: attempted relative import beyond top-level package处理方法参考: https://blog.csdn.net/sky453589103/article/details/78863050
Reference
命名空间:
https://www.cnblogs.com/windlaughing/archive/2013/05/26/3100362.html
https://www.cnblogs.com/zhangxinhe/p/6963462.html
相对导入和绝对导入:
https://www.jb51.net/article/102252.htm
https://blog.csdn.net/weixin_38256474/article/details/81228492