美文网首页
Python基础38-包/模块(导入及其底层逻辑)

Python基础38-包/模块(导入及其底层逻辑)

作者: Jacob_LJ | 来源:发表于2018-06-14 00:01 被阅读32次

    Python基础-包/模块

    1 导入模块后具体做了什么?
    2 从哪个位置找到需要导入的模块
    3 导入模块的常见场景?

    1 导入模块后具体做了什么?

    1.1 第一次导入

    1. 在自己当下的命名空间中,执行所有代码
    2. 创建一个模块对象,并将模块内所有顶级变量以属性的形式绑定在模块对象上
    3. 在 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 第二层级

    1. 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 库中搜索
    1. 追加路径的方式(修改 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 绝对

    1. 参照sys.path路径进行检索
    2. 指明包名或模块名import afrom a import b
    3. 上述两点是基于 Python3.x 之后

    3.5.1.2 相对

    1. 使用.来指代相对路径
      .:根据模块名(__name__)称所获取的当前目录
      ..:根据模块名(__name__)称所获取的上层目录
    2. 如:from . import afrom .. import a

    3.5.2 补充

    1. Python3.x 版本之前,直接使用 import a。会优先从本地当前目录查找,并非是 sys.path
    2. 解决方案: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
    
    • 可以通过上述例子,了解点点的作用
    • 注意:包内不能获取包外的文件

    相关文章

      网友评论

          本文标题:Python基础38-包/模块(导入及其底层逻辑)

          本文链接:https://www.haomeiwen.com/subject/ufukeftx.html