python import 原理

本文测试环境为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
      6
      >>> import test_script

      >>> test_script.func()
      Hi, I am function
      >>> test_script.TestClass().func()
      Hi, I am class
      1
      2
      3
      4
      5
      6
      >>> from test_script import func, TestClass

      >>> func()
      Hi, I am function
      >>> TestClass().func()
      Hi, I am class

      注:若发生导包错误,如下:

      1
      2
      3
      4
      >>> import 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
      9
      >>> import sys
      >>> sys.path.append('/opt/test/python_exm')

      # 重新导入,无错误产生
      >>> 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
      10
      test_package/
      ├── __init__.py
      ├── pack1
      │   ├── __init__.py
      │   ├── mod1.py
      │   └── mod2.py
      └── pack2
      ├── __init__.py
      ├── mod1.py
      └── mod2.py
  • package 导入

    1
    2
    3
    import 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
    8
    1. 模块的命名空间不仅包括模块的常量和变量,还包括模块中定义的函数和类。此外还包括,任何被导入模块中的东西。
    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
    4
    L: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() # 2
    • global 关键字用来在函数或其他局部作用域中声明全局变量。

      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 搜索路径

  1. 在当前目录下搜索该模块
  2. python安装目录,UNIX下,默认路径一般为/usr/local/lib/python/
  3. 在 Python 安装路径的 lib 库中搜索
  4. python3.x 中.pth 文件内容

注:import 搜索路径是按照 sys.path中的路径列表来搜索的。

1
2
3
4
5
>>> import sys
>>> sys.path
['', '/usr/local/python2/lib/python27.zip', '/usr/local/python2/lib/python2.7', '/usr/local/python2/lib/python2.7/plat-linux2', '/usr/local/python2/lib/python2.7/lib-tk', '/usr/local/python2/lib/python2.7/lib-old', '/usr/local/python2/lib/python2.7/lib-dynload', '/usr/local/python2/lib/python2.7/site-packages', '/usr/local/python2/lib/python2.7/site-packages/setuptools-41.0.1-py2.7.egg', '/usr/local/python2/lib/python2.7/site-packages/pip-19.1.1-py2.7.egg']

# sys.path 是一个list, 第一项为一个空字符串,代表当前目录,可通过 os.getcwd() 查看当前所在目录。

Python import 导入步骤

​python 所有加载的模块信息都存放在 sys.modules结构中,当 import 一个模块时,会按如下步骤来进行:

  1. 如果是 import A,检查 sys.modules中是否已经有 A,如果有则不加载,如果没有则为 A 创建 module 对象,并加载 A 到 sys.modules
  2. 如果是 from A import B,先为 A 创建 module 对象,再解析A,从中寻找B并填充到 A 的 __dict__

Python import 导入方式

Python 相对导入与绝对导入是相对于包内导入而言的。所谓包内导入是指包内的模块导入包内部的模块。

  • 绝对导入

    1
    2
    3
    4
    5
    import A.B 

    from A import B

    注:module A的路径应在 sys.path 列表中,否则会引发 ModuleNotFoundError 异常
  • 相对导入

    1
    2
    3
    4
    5
    6
    from . 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
    38
    test_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
      4
      >>> import 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
        5
        try:
        import test_package
        excep ModuleNotFoundError:
        import sys
        sys.path.append('path') # path 为test_package所在的路径
      • test_package包所在的路径添加到PYTHONPATH环境变量中

        1
        2
        3
        4
        5
        cd ~;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

https://blog.csdn.net/gaifuxi9518/article/details/81038818

-------------本文结束感谢您的阅读-------------