046.Python包和模块_导入相关

无奋斗不青春

我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈
入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈
虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈
PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)文章合集 👈👈
Oracle数据库教程:👉👉 Oracle数据库文章合集 👈👈
优 质 资 源 下 载 :👉👉 资源下载合集 👈👈

分隔线

包和模块的导入

理论基础

  • 导入模块就可以使用这个模块里面的内容

常规导入import

  • 作用
    • 导入一个模块中所有资源到当前位置
  • 导入方式
  • 方式一:导入单个模块
    import 模块名
    import 包名.模块名
    import 包名.子包名.模块名
    
    # 如果是某个包里面的模块,可以通过点语法来定位
    # 导入多层级包里面的模块,会分别执行每一级包里面的__init__.py文件
    
  • 方式二:导入多个模块
    # 方式1
    import 模块名1
    import 模块名2
    
    # 方式2
    import 模块名1, 模块名2
    import 模块名1, 包名.子包名.模块名2
    
  • 方式三:给导入的包和模块起别名
    import 模块名 as 别名
    import 包名.子包名.模块名 as 别名
    
    
    # 后续使用可以直接通过 别名.方法名() / 别名.属性 使用
    
  • 起别名的好处
    • 1、简化资源访问前缀
    • 2、增加程序的扩展性
  • 好处1示例
    # 不起别名
    import 包名.子包名.模块名
    print(包名.子包名.模块名.属性1)      # 不起别名,使用时,前缀太长太复杂
    print(包名.子包名.模块名.属性2)
    print(包名.子包名.模块名.属性3)
    
    # 起别名
    import 包名.子包名.模块名 as m
    
    print(m.属性1)                      # 起别名后,使用时,简单
    print(m.属性2)
    print(m.属性3)
    
    
  • 好处2示例
    # 根据文件后缀调用不同模块,对文件进行打开、读取、关闭操作
    # 不起别名
    if file_extension == 'txt':
        import txt_parse
        txt_parse.open()
        txt_parse.read()
        txt_parse.close()
    elif file_extension == 'doc':
        import doc_parse
        doc_parse.open()
        doc_parse.read()
        doc_parse.close()
    
    # 这里不同的文件类型,对文件的打开、读取、关闭操作都是一样的,仅仅是使用的模块不同
    # 起别名
    if file_extension == 'txt':
        import txt_parse as p
    elif file_extension == 'doc':
        import doc_parse as p
        
    p.open()
    p.read()
    p.close()
        
    # 整个代码就可以将文件的打开、读取、关闭操作进行提取整合,减少代码冗余
    
  • 注意
    • 通过import导入包和模块,在使用包和模块内部资源时,需要指明资源的包和模块名称
    • 如:包名.子包名.模块.属性包名.子包名.模块.方法()
  • 补充
    • 如果通过import 包名的方式导入一个包,默认不会导入任何模块
  • 解决方案
    • 1、在导入包的时候会自动执行包里面的__init__.py文件,所以可以在__init__.py文件中再次导入需要的模块
      • 这里导入模块的时候需要写明模块的绝对路径,是因为模块检索路径的问题,后续会讲

    • 2、应该以from....import....的形式导入

from导入

  • 作用

    • 导入一个模块或包里面某一部分资源
  • 语法

    from A import B[ as 别名]
    
    # 从A导入B资源到当前位置
    
    • 理解
      • 导入顺序:从大的地方找小的东西
        • 按照大小资源排序:包 > 模块 > 资源
      • 注意面向关系
        • 包里面只能看到模块,看不到模块资源
        • 模块里面只能看到模块资源
  • 最终组合

    • 1、从包中导入模块
    • 2、从模块中导入模块资源
  • 语法

    • 语法1:从包中导入模块
      # 导入单个模块
      fromimport 模块
      
      # 导入多个模块
      fromimport 模块1, 模块2
      
      # 起别名
      fromimport 模块 as 别名
      fromimport 模块1 as 别名1, 模块2 as 别名2
      
      # 包有多层级
      from.子包 import 模块
      
      
      # 使用模块资源
      模块.资源
      
      
    • 语法2:从模块中导入模块资源
      # 导入单个资源
      from 模块 import 资源
      
      # 导入多个资源
      from 模块 import 资源1, 资源2
      
      # 起别名
      from 模块 import 资源 as 别名
      from 模块 import 资源1 as 别名1, 资源2 as 别名2
      
      # 模块有多层级
      from.模块 import 资源
      
      
      # 使用资源
      资源
      
  • 示例

  • 先创建几个简单的模块

    # host_module1.py
    h_m1_num1 = 100
    h_m1_num2 = 199
    
    # host_module2.py
    h_m1_num1 = 200
    h_m1_num2 = 299
    
    # sub_module1.py
    s_m1_num1 = 1100
    s_m1_num2 = 1199
    
    # sub_module2.py
    s_m2_num1 = 2100
    s_m2_num2 = 2199
    
    • 在这里插入图片描述
  • 通过test.py分别导入这些模块

    • 示例1:从包中导入模块
      # 导入单个模块
      from host_package import host_module1
      print(host_module1.h_m1_num1)       # 100
      print(host_module1.h_m1_num2)       # 199
      
      # 导入多个模块
      from host_package import host_module1, host_module2
      print(host_module1.h_m1_num1)       # 100
      print(host_module1.h_m1_num2)       # 199
      print(host_module2.h_m2_num1)       # 200
      print(host_module2.h_m2_num2)       # 299
      
      # 起别名1
      from host_package import host_module1 as hm1
      print(hm1.h_m1_num1)                # 100
      print(hm1.h_m1_num2)                # 199
      
      # 起别名2
      from host_package import host_module1 as hm1, host_module2 as hm2
      print(hm1.h_m1_num1)                # 100
      print(hm1.h_m1_num2)                # 199
      print(hm2.h_m2_num1)                # 200
      print(hm2.h_m2_num2)                # 299
      
      
      # 包有多层级
      from host_package.sub_package import sub_module1
      print(sub_module1.s_m1_num1)        # 1100
      print(sub_module1.s_m1_num2)        # 1199
      
      from host_package.sub_package import sub_module1, sub_module2
      print(sub_module1.s_m1_num1)        # 1100
      print(sub_module1.s_m1_num2)        # 1199
      print(sub_module2.s_m2_num1)        # 2100
      print(sub_module2.s_m2_num2)        # 2199
      
      from host_package.sub_package import sub_module1 as sm1, sub_module2 as sm2
      print(sm1.s_m1_num1)        # 1100
      print(sm1.s_m1_num2)        # 1199
      print(sm2.s_m2_num1)        # 2100
      print(sm2.s_m2_num2)        # 2199
      
      # 错误演示
      from host_package import sub_package.sub_module1
      print(sub_package.sub_module1.s_m1_num1)
      
      # ========== 输出结果 ==========
      # File "E:\StudyCode\Python\15-包和模块\test.py", line 44
      # from host_package import sub_package.sub_module1
      #                                         ^
      # SyntaxError: invalid syntax
      
    • 示例2:从模块中导入资源
      • 调整test.py文件位置,与host_module1.py模块在同一级目录
      # 导入单个资源
      from host_module1 import h_m1_num1
      print(h_m1_num1)        # 100
      # print(h_m1_num2)        # 报错:NameError: name 'h_m1_num2' is not defined
      
      # 导入多个资源
      from host_module1 import h_m1_num1, h_m1_num2
      print(h_m1_num1)        # 100
      print(h_m1_num2)        # 199
      
      # 起别名1
      from host_module1 import h_m1_num1 as hm1
      print(hm1)              # 100
      
      # 起别名2
      from host_module1 import h_m1_num1 as hm1, h_m1_num2 as hm2
      print(hm1)              # 100
      print(hm2)              # 199
      
      # 模块有多层级
      from sub_package.sub_module1 import s_m1_num1
      print(s_m1_num1)        # 1100
      
      from sub_package.sub_module1 import s_m1_num1, s_m1_num2
      print(s_m1_num1)        # 1100
      print(s_m1_num2)        # 1199
      
      # 错误演示
      from sub_package import sub_module1.s_m1_num1
      print(sub_module1.s_m1_num1)
      
      # ========== 输出结果 ==========
      # File "E:\StudyCode\Python\15-包和模块\host_package\test.py", line 85
      # from sub_package import sub_module1.s_m1_num1
      #                                    ^
      # SyntaxError: invalid syntax
      
  • 注意

    • from A import B保证B部分路径最简,从哪里能看到谁才能导入谁
    • 错误示例
      • from 大包 import 小包.模块
        • 1、import后面没有做到路径最简化
        • 2、在 大包 层级并不能直接看到 模块
        • 修改:from 大包.小包 import 模块
      • from 包 import 模块.资源
        • 1、import后面没有做到路径最简化
        • 2、在 包 层级并不能直接看到 模块内资源
        • 修改:from 包.模块 import 资源
  • 导入特例

    • 批量导入模块中的资源
      from 模块 import *
      
    • 这里 import * 并不是导入模块中的所有资源,而是导入模块中__all__变量指定的资源
      # other.py
      num1 = 100
      num2 = 200
      num3 = 300
      num4 = 400
      
      __all__ = ['num1', 'num2', 'num4']
      
      
      # test.py文件
      from other import *
      # 这里 import * 仅仅只是导入了__all__变量指定的num1、num2和num4
      print(num1)
      print(num2)
      print(num4)
      
    • 批量导入包中的资源
      fromimport *
      
      • 这里 import * 并不是导入包中所有的模块资源,而是导入包中__init__.py文件中__all__变量指定的模块
    • import *这种方式慎用
      • 因为通过这种方式导入,容易出现资源名称与模块资源名称重名问题
    • 注意:
      • 如果模块和包中没有没有写__all__变量指明资源,那么就会导入包里面所有模块和模块中所有公有资源

导入模块底层原理

导入模块后具体做了什么事?
  • 第一次导入时

    • 1、在所导入模块的命名空间中,执行所有代码
    • 2、创建一个模块对象,并将模块内所有顶级变量以属性的形式绑定在模块对象上
    • 3、将import后面的变量名称引入到import语句所在位置的当前命名空间
  • 示例讲解

    • other.py文件
      num1 = 100
      num2 = 200
      
      
      def run():
          num3 = 300
          print(num1)
      
    • test.py文件
      import other
      
    • 在这里插入图片描述
  • 第二次导入时

    • 直接执行上述第三步
  • 结论

    • importfrom两种导入方式都会大致执行以上的步骤
    • 多次导入模块时,并不会多次执行该模块,只有第一次导入时才执行
    • 两种导入不存在哪一种更省内存,区别在于把哪一部分内容拿到当前位置来用
    import other
    
    # other-模块 ————begin
    # other-模块 ————end
    
    
    from other import num1
    
    # other-模块 ————begin
    # other-模块 ————end
    
    • 两种导入方式,均会执行整个other.py文件
    • 区别在于:
      • import 会将other对象的所有资源都拿到当前命名空间中来用
      • from 仅仅是将other对象的num1资源拿到当前命名空间中来用
模块检索路径
  • 模块检索路径顺序(从哪个位置找到需要导入的模块?)
  • 第一次导入时先到内置模块中搜索,如果没有,再按照sys.path中记录的路径按顺序中去搜索
  • 第二次导入时候,直接到已加载的模块中搜索sys.modules
  • 第一次导入时
    • 第一级:内置模块
    • 第二级:sys.path中的路径
      • sys.path路径构成
        1、当前目录
        2、环境变量PYTHONPATH中指定的路径列表
        3、Python安装路径
        4、Python安装路径下的.pth文件中的文件路径列表
        5、Python安装路径的lib库
        6、Python安装路径的lib库中.pth文件中的文件路径列表
        
      • sys.path追加路径的方式
        • 1、直接修改sys.path(仅作用于本次)
          import sys
          sys.path.append('指定路径')             # 将指定路径添加到sys.path列表的最后
          sys.path.insert(index, '指定路径')      # 将指定路径添加到列表的指定索引位置
          
          import 指定模块
          
          # 模块的查找顺序最终根据sys.path列表中路径的位置而定
          
        • 2、修改环境变量PYTHONPATH(仅在shell中有效,PyCharm需要另外一种设置方式)
          # 添加环境变量 PYTHONPATH(仅能在cmd中通过python环境执行)
          # 此电脑 —— 属性 —— 高级系统设置 —— 环境变量
          # 添加到 用户变量, 仅当前用户可用
          # 添加到 系统变量, 所有用户均可用
          
          # 配置PyCharm
          # PyCharm —— File —— Settings —— Project:xxxx —— Python Interpreter —— 右侧设置按钮 —— Show All... —— Show paths for the selected interpreter —— “+” 把路径添加进去
          
        • 3、添加.pth文件
          # 查看.pth文件存放目录
          
          import site
          print(site.getsitepackages())
          
          # ['E:\\StudyCode\\Python\\.venv', 'E:\\StudyCode\\Python\\.venv\\lib\\site-packages']
          
          # 创建.pth文件:新建文本文档——将.txt后缀改成.pth
          # 将路径添加到.pth文件中
          # 再将.pth文件添加到上面查询到的.pth文件存放目录
          
          
  • 第二次导入时
    • 从已经加载过的模块中去查找
      # 查看已加载模块
      import sys
      print(sys.modules)
      
导入模块的常见场景
局部导入
  • 在某个局部范围(命名空间)内导入模块,在其他范围无法使用
  • defclass、模块会产生单独的命名空间,iffor语句不会
  • 如果想要全局范围都能使用,则再文件顶部导入相关模块
  • 使用场景
    • 模块仅仅在某个函数内部或者某个类内部需要使用,其他地方统统用不到时
    • 我们就可以使用局部导入,在函数或者类内部导入,只有执行函数或者使用类的时候才会加载模块
覆盖导入
  • 重名的模块导入时,会根据检索路径的优先级发生覆盖情况
  • 自定义模块和非内置的标准模块重名时,根据牵着存储位置,有可能牵着会覆盖后者
    • 注意:自定义模块命名不要与非内置的标准模块重名
  • 自定义模块和内置模块重名时,内置模块肯定会覆盖自定义模块
    • 如果非要使用自定义模块,使用from...import...指明绝对路径进行导入
循环导入
  • 加入有两个模块A和B,模块A内导入了模块B,模块B内又导入了模块A,这样就造成了循环导入

  • 场景

    • 在这里插入图片描述

    • test.py 文件

      t1 = 'T1'
      t2 = 'T2'
      
      import other
      print(other.o1)
      print(other.o2)
      
    • other.py 文件

      o1 = 'O1'
      o2 = 'O2'
      
      import test
      print(test.t1)
      print(test.t2)
      
    • 输出结果

      O1
      O2
      T1
      T2
      O1
      O2
      
  • 图解
    *

    循环导入

    执行test.py文件
    1:增加属性t1、t2
    2:导入模块other
        2-1:先到sys.modules中查找,未找到
        2-2:再根据sys.path路径进行查找
        2-3:找到other模块,将other添加到sys.modules中
        2-4:执行other模块,给other增加属性o1、o2
        2-5:导入test模块
            2-5-1:先到sys.modules中查找,未找到
            2-5-2:再根据sys.path路径进行查找
            2-5-3:找到test模块,将other添加到sys.modules中
            2-5-4:执行test模块,给other增加属性t1、t2
            2-5-5:导入other模块
                2-5-5-1:先到sys.modules中查找,找到了
                2-5-5-2:第二次导入other模块,不再执行文件
            2-5-6:导入other模块完成,继续往下执行
            2-5-7:输出o1、o2,打印O1、O2
        2-6:导入test模块完成,继续往下执行
        2-7:输出t1、t2,打印T1、T2
    3:导入other模块完成,继续往下执行
    4:输出o1、o2,打印O1、O2            
    
可选导入
  • 两个功能相近的包,根据需求优先选择其中一个导入
  • 场景
    • 有两个包 A和B,都可以实现相同的功能
    • 想优先使用A,而且需要做到:在没有A的情况下,使用B做备选
  • 实现
    try:
        import other2 as o
    except ModuleNotFoundError:
        import other as o
        
    print(o.name)
    
包内导入
  • python相对导入与绝对导入,这两个概念是相对于包内导入而言额

  • 包内导入即是包内的模块导入包内部的模块

  • 绝对导入

    • 导入时参照sys.path路径进行检索
    • 例如:
      import a
      from b import a
      
    • 注意:以上结论是基于Python3.x版本之后的
  • 相对导入

    • 使用.来替代相对路径
      .       # 根据模块名称获取的当前目录
      ..      # 根据模块名称获取的上级目录
      
    • 例如
      from . import a
      from .. import a
      
    • 注意
      • ...并不是简单的指代当前文件的当前目录和上级目录
  • 通过示例演示

    • 文件结构

      • 在这里插入图片描述
    • sub_module2.py 文件

      s2_m1 = 'sub2-1'
      s2_m2 = 'sub2-2'
      
    • sub_module1.py 文件

      import sub_module2
      
      print(sub_module2.s2_m1)
      print(sub_module2.s2_m2)
      
    • 执行sub_module1.py 文件时,能正常导入sub_module2模块,并执行

      # ========== 输出结果 ==========
      sub2-1
      sub2-2
      
    • 当我们在上一层目录的module1.py文件中导入sub_module1模块时,执行居然会报错…

      import sub_package.sub_module1
      # from sub_package import sub_module1
      
      # 这两种导入方式是一样的效果,都是绝对导入
      
      
      # ========== 输出结果 ==========
      Traceback (most recent call last):
        File "E:\StudyCode\Python\15-包和模块\包内导入\module1.py", line 1, in <module>
          import sub_package.sub_module1
        File "E:\StudyCode\Python\15-包和模块\包内导入\sub_package\sub_module1.py", line 2, in <module>
          import sub_module2
      ModuleNotFoundError: No module named 'sub_module2'        
      
      
    • 这里报错:没有名称是sub_module2的模块,为什么直接执行sub_module1的时候能正常导入sub_module2模块,现在执行module1.py文件导入sub_module1模块,再在sub_module1模块内导入sub_module2模块就会报错呢?

  • 报错原因

    • 1、import afrom b import a两种导入语法都是绝对导入,模块检索路径由sys.path觉得

    • 2、当执行module1.py文件时,就确定了sys.path里面的路径了,后面基本不会再改变

      > 执行 module1.py 文件时会将文件当前目录添加到 sys.path 路径中
      > 导入 sub_module1 模块时,会执行内部的全部代码,内部需要再导入 sub_module2 模块
      > 此时,需要到 sys.path 路径中去搜索 sub_module2 模块
      > 但是,在执行 module1 文件时,就确定了 sys.path 路径,仅仅是将 包内导入 目录添加到了路径中
      > 并没有将 sub_package 目录添加到路径中,所以通过 import sub_module2 导入模块会因为搜索不到模块而报错
      
    • 修改文件代码,跟踪sys.path数据

      • sub_module1.py 文件

        import sys
        print('sub_module1:', sys.path)
        
        
        # import sub_module2
        #
        # print(sub_module2.s2_m1)
        # print(sub_module2.s2_m2)
        
        • 在这里插入图片描述
      • module1.py 文件

        import sub_package.sub_module1
        # from sub_package import sub_module1
        
        import sys
        print('')
        print('module1:', sys.path)
        
        • 在这里插入图片描述
    • 通过跟踪sys.path的情况

  • 解决办法:使用相对导入

    • 只需要修改sub_module1.py文件代码
      from . import sub_module2
      
      print(sub_module2.s2_m1)
      print(sub_module2.s2_m2)
      
  • 关于...的说明

    • 这里的...并不是简单的获取当前文件的目录以及当前文件的上一级目录
    • 而是根据模块名称来获取的,接下来我们来看一下模块名称的变化。我们先对文件代码进行调整
    • sub_module1.py文件代码
      # from . import sub_module2
      
      # print(sub_module2.s2_m1)
      # print(sub_module2.s2_m2)
      
      print('sub_module1:', __name__)    
      
    • 直接执行该文件,输出结果如下
      # ========== 输出结果 ==========
      sub_module1: __main__
      
    • 这里我们可以看到,直接执行,输出的文件名称是__main__
    • 修改module1.py文件代码
      import sub_package.sub_module1
      # from sub_package import sub_module1
      
      print('module1:', __name__)    
      
    • 执行module1.py文件,输出结果如下
      # ========== 输出结果 ==========
      sub_module1: sub_package.sub_module1
      module1: __main__
      
    • 额…大家发现了没有。当我们执行module1.py文件时,通过导入sub_module1模块的方式执行模块内部的代码时
    • sub_module1模块的名称变成了sub_package.sub_module1
  • 关于模块名称的问题

    • 当前模块是主程序,即直接由Python解释器运行当前模块时,当前模块的名称__name__则是main
    • 当前模块通过import语句导入执行时,当前模块的名称__name__则是由加载路径决定:包名.子包名.模块名(这个名称最前面部分则被称为顶级名称)
  • 当我们执行module1.py文件时,通过导入sub_module1模块的方式执行模块内部的代码时sub_module1模块的名称变成了sub_package.sub_module1

  • 我们来解析一下sub_module1模块中的导入语句

    from . import sub_module2
    
    
    # 此时 sub_module1 模块的名称变成了 sub_package.sub_module1 
    # 上面的 from 导入语句中的 . 则表示的是这个模块名称中上一个 . 前面的路径 sub_package
    # 相当于是 from sub_package import sub_module2
    # module1.py 到 sub_package 目录中导入 sub_module2模块,这个当然没问题
    
  • 问题点

  • 如果直接修改成上述代码,那么执行module1.py文件不会报错了,但是直接执行sub_module1.py文件会报错

  • sub_module1.py文件代码

    from . import sub_module2
    
    print(sub_module2.s2_m1)
    print(sub_module2.s2_m2)
    
  • 直接执行执行sub_module1.py文件会报错

    Traceback (most recent call last):
      File "E:\StudyCode\Python\15-包和模块\包内导入\sub_package\sub_module1.py", line 2, in <module>
        from . import sub_module2
    ImportError: attempted relative import with no known parent package
    # 导入错误:尝试相对导入,没有已知的顶级包
    
  • 报错原因

    • 直接执行sub_module1.py文件,就是直接由Python解释器运行当前模块时,此时当前模块的名称__name__则是main
    • from . import xxx中的.是获取模块名称中.前面的路径,但是此时模块名称是main并没有.,所以会报错
  • 如果即想直接执行模块,又想其他模块导入都不报错,那么可以将模块代码修改成如下:

    # 根据模块名称的情况,分别使用不同的导入方式
    if __name__ == '__main__':
        import sub_module2
    else:
        from . import sub_module2
    
    print(sub_module2.s2_m1)
    print(sub_module2.s2_m2)
    

关于Python2.x的说明

  • 在Python2.x中,import a其实就是相对导入,但是这里面又没有加...的语法,这种称之为隐式的相对导入

  • 但是为什么后面又把这种写法改变成了绝对导入呢?

    • 因为,如果import a这种写法是相对导入,那么查找模块就是优先到当前路径下查找
    • 那么,如果自定义模块与内置模块重名了,那就再也没有办法访问到内置模块了
    • 所以,在Python3.x版本中,import a就升级成了绝对导入,查找模块优先在内置模块中查找
    • 如果要查找与内置模块重名的自定义的模块,我们就可以使用显示的相对导入from . import a语法进行导入
  • 如果要让Python2.x和Python3.x在相对和绝对导入兼容,可以使用一下语句

    from __future__ import absolute_import
    
    # 禁用Python2.x版本中的隐式相对导入
    # 使import a语法在Python2.x版本中也是绝对导入
    
  • 31
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失心疯_2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值