• 模块的导入使用


    一、什么是模块

    模块就是一系列功能的集合体,一个模块就是一个包含了Python定义和声明的文件,文件名就是模块名字加上.py的后缀。

    模块有三种来源:

      1、内置的模块

      2、第三方的模块

      3、自定义模块

    模块的四种通用类别:

      1、使用Python编写的代码(.py文件)

      2、已被编译为共享库或DLL的C或C++扩展

      3、把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件夹称之为包)

      4、使用C编写并链接到Python解释器的内置模块

    二、为何要用模块

      1、使用内置的或者第三方模块的好处是:拿来就可以使用,可以极大提升开发效率

      2、使用自定义模块的好处是:可以减少代码冗余(抽取我们自己程序中要公用的一些功能定义成模块,然后程序的各部分组件都去模块中调用共享的功能)

    三、模块的使用

    前提:一定要区分开谁是执行文件,谁是被导入模块

    1、import导入模块

    在Pycharm中右键新建一个文件,文件名是spam.py,模块名是spam,再新建一个文件叫run.py,在run.py中

    import spam

    首次导入一个模块,会发生以下事情:

      1)会产生一个模块的名称空间

      2)执行文件spam.py,将执行过程中产生的名字都放到模块的名称空间中

      3)在当前执行文件的名称空间中拿到一个模块名,该名字指向模块的名称空间

    之后的导入,都是直接引用第一次导入的成果,不会重新执行文件

    在spam.py文件中添加代码

    money = 1000
    
    def read1():
        print('spam模块:',money)
    
    def read2():
        print('spam模块')
        read1()
    
    def change():
        global money
        money = 0

    在执行文件中访问模块名称空间中名字的语法:模块名.名字,即在run.py中

    import spam
    
    print(spam.money)   # 1000
    print(spam.read1)   # <function read1 at 0x000001CE16209B70>
    print(spam.read2)   # <function read2 at 0x000001CE16209A60>
    print(spam.change)  # <function change at 0x000001CE16209C80>
    spam.read1()    # spam模块: 1000
    
    def read1():
        print('run.py --> read1')
    
    read1()         # run.py --> read1
    
    spam.read2()    # spam模块
                    # spam模块: 1000
    money = 9999
    spam.change()
    print(spam.money)   # 0
    print(money)        # 9999

    总结 import 导入模块:在使用时必须加上模块名作为前缀

    优点:指名道姓的向某一个名称空间拿取名字,不会与当前名称空间中的名字冲突

    缺点:但凡应用模块中的名字都需要加前缀,不够简洁

    还可以一行导入多个模块,但不推荐使用

    import spam, os, time

    可以为模块起别名(注意:模块名应全为小写)

    import spam as sm
    
    print(sm.money)
    print(sm.read1)

    2、from...import...导入模块

    首次导入模块发生的三件事:

      1)创建一个模块的名称空间

      2)执行文件spam.py,将执行过程中产生的名字都放到模块的名称空间中

      3)在当前执行文件中直接拿到一个名字,该名字就是执行模块中相对应的名字

    删除上面的两个文件,重新建一个spam.py,添加代码

    money = 1000
    
    def read1():
        print('spam模块:',money)
    
    def read2():
        print('spam模块')
        read1()
    
    def change():
        global money
        money = 0

    再新建一个run.py,在run.py中

    from spam import money
    
    print(money)  # 1000
    from spam import money
    
    money = 200  
    print(money)  # 与当前名称空间中的名字冲突, 结果为200

    总结from...import...导入模块

    优点:使用时,无需再加前缀,更简洁

    缺点::容易与当前名称空间中的名字冲突

    现在将run.py里的代码改成

    from spam import money, read1
    
    money = 200
    
    # 这里执行read1里面的money还是1000,因为在定义阶段就已经固定死了,与调用位置无关
    read1()         # spam模块: 1000
    
    print(money)    # 200

    还可以导入全部模块,但不推荐使用

    # 星号代表从被导入模块中拿到所有名字
    from spam import *

    有一个 __all__ 的功能,将指定的模块名以字符串的形式存放,如果再使用星号导入模块,这时导入的就是 __all__里面的内容,而不是导入全部的模块,例如:

    在spam.py中定义 __all__

    __all__ = ['money', 'read1']
    
    money = 1000
    
    def read1():
        print('spam模块:',money)
    
    def read2():
        print('spam模块')
        read1()
    
    def change():
        global money
        money = 0

    在run.py中用星号导入

    from spam import *
    
    read1()     # spam模块: 1000
    read2()     # 报错, 因为 __all__没有包含read2模块

    可以为模块起别名

    from spam import read1 as r1
    
    r1()

     四、Python文件的两种执行方式

    1、直接运行

    2、作为模块导入

    # 在m1.py中
    def f1():
        print('f1')
    
    print(__name__)    # 执行后是 __main__
    
    
    # 在run.py中
    import m1    # 执行后是 m1

    即:当做脚本运行,__name__ 等于'__main__',当做模块导入,__name__等于模块名

    所以这个条件是:if __name__ == '__main__': 用来控制.py文件在不同的应用场景下执行不同的逻辑

    # 在m1.py中
    def f1():
        print('f1')
    
    if __name__ == '__main__':
        f1()
    
    
    # 在run.py中
    import m1

    五、模块的搜索路径

    1、模块搜索路径的优先级

      1)内存中已经加载过的

      2)内置模块

      3)sys.path(第一个值是当前执行文件所在的文件夹)

    模块搜索路径优先从内存中已加载过的开始查找,例如我新建一个m1.py和一个run.py,在run.py中导入模块m1

    # m1.py
    def f1():
        print('from f1')
    
    # run.py
    import time
    import m1
    time.sleep(10)    # 程序睡眠10秒的过程中删除m1.py
    import m1
    m1.f1()        # 这里还可以执行

     删除m1.py文件后再导入执行不会报错,因为在删除的过程中程序没有结束,再次导入是优先从内存中查找,找到了直接运行,但如果再次运行就会报错,因为内存已经回收,找不到m1模块了

    注意:不应该将自己的模块名命名成与内置模块或第三方模块的名字相同

    2、添加sys.path

    新建m1.py和run.py,将m1.py放在dir1文件夹下,让dir1文件夹和run.py在同一级目录

    # m1.py
    def f1():
        print('from f1')
    f1()
    # run.py import m1 # 这时候是找不到的,但是如果添加sys.path,将run.py改成如下

    # run.py import sys sys.path.append('E://Test//dir1') # 将m1所在的文件夹路径添加到sys.path import m1

    3、from...import...

    还是上面的情况,可以在run.py里面通过from...import...导入

     

    from dir1 import m1

    这是在sys.path中找到的,但是我没有添加sys.path,为什么也能找到呢?

    因为sys.path的第一个值是当前执行文件所在的文件夹,也就是Test文件夹(我的run.py和dir1的上一层是Test文件夹),所以说是以当前执行文件的sys.path为准,可以找到dir1,进而找到m1

    基于上面的目录,我在dir1下新建一个文件夹叫dir2,在dir2中新建一个文件叫m2.py,现在的路径关系是:Test下面有一个run.py和dir1,dir1下有一个m1.py和dir2,dir2下有一个m2.py

    m2中添加代码

    def f2():
        print('from f2')
    f2()

    可以通过from...import...导入

    from dir1.dir2 import m2

     第一种特殊情况:模块的绝对导入

    删除上面的所有文件,新建run.py和dir1文件夹,在dir1下新建m1.py和m2.py

    # m1.py
    def f1():
        print('from f1')
    
    # m2.py
    def f2():
        print('from f2')

    run想访问m1,因为不在同一级目录,所以要通过上面讲到的两种方法(添加sys.path或from...import导入)来访问(我用后者)

    # run.py
    from dir1 import m1

    现在我执行run.py,可以通过run访问到m1

    现在m1想访问m2,只需要在m1中导入m2即可

    # m1.py
    import m2
    
    def f1():
        print('from f1')
    
        m2.f2()

    但是现在,我再执行run.py,能否通过run访问到m1再访问到m2呢?

    不行!因为sys.path是以当前执行文件为准的,那么sys.path的第一个值就是 E://Test//,在m1里面导入m2的时候,去sys.path中无法找到m2,所以会报错

    强调:所有被导入的模块参照的环境变量sys.path都是以执行文件为准的

    那么怎么解决呢

    # m1.py
    from dir1 import m2
    
    def f1():
        print('from f1')
    
        m2.f2()
    
    
    # m2.py
    def f2():
        print('from f2')
    
    # run.py
    from dir1 import m1
    
    m1.f1()

    注意:不要理解成sys.path是以执行文件所在的文件夹为准,因为sys.path有很多个值,而执行文件所在的文件夹仅仅只是sys.path中的一个值,这个执行文件所在的文件夹找不到后面还有很多值可以找

    上面这种情况是以执行文件的sys.path作为参考点开始导入,称之为模块的绝对导入

    优点:在执行文件与被导入的模块中都可以使用

    缺点:所有导入都是以sys.path为参考点,导入麻烦

    第二种特殊情况:模块的相对导入

    参照当前所在文件的文件夹为起始开始查找,称之为模块的相对导入

    符号:.(一个点)代表当前所在文件的文件夹,..(两个点)代表上一级文件夹,...(三个点)代表上一级的上一级文件夹

    优点:导入更加简单

    缺点:只能在被导入的模块中使用,不能在执行文件中用

    现在在Test下新建一个dir0,dir0下新建一个dir1和run.py,dir1下新建m1.py和m2.py

    # run.py
    from dir0.dir1 import m1    # 用的还是绝对导入
    
    m1.f1()
    
    
    # m1.py
    from . import m2    # 用的是相对导入
    
    def f1():
        print('from f1')
    
        m2.f2()
    
    
    # m2.py
    def f2():
        print('from f2')

     六、软件开发的目录规范

    以 “ATM+购物车” 举例

    ATM + 购物车
        bin: 整个程序的执行文件,入口(调用核心逻辑的一些功能)
            - start.py
        conf: 配置文件(程序组件共用的变量)
            - settings.py
        lib: 库(自定义的模块, 程序组件共用的功能)
            - common.py
        core: 核心逻辑(与业务相关的逻辑, 购物车的登录注册转账取款等)
            - src.py
        log: 日志(程序运行产生的关键信息记录)
            - transaction.log
        db: 数据(数据库相关的文件)
        Readme: 读我(软件的介绍, 说明书)

    调用业务相关的逻辑应该通过start.py去调取src模块,所以要添加环境变量,然后在start.py中添加代码

    # run.py
    
    import sys
    sys.path.append(r'E:PythonATMcore')
    
    import src
    src.run()

    虽然添加core能够让start.py调取到src模块,但可不能这么做。因为以后别人作为软件的使用者,下载了这个软件然后使用,但这里添加的环境变量是开发者自己机器上的文件路径,别人使用时和我肯定不是一样的文件路径,所以无法使用。这时需要让使用者无论在哪个路径下都能使用这个软件,所以要添加一个通用的环境变量,具体做法是添加整个项目的根文件夹。但是这里可不能又写我硬盘上的文件路径,而是让程序自己获取整个项目的根文件夹。

    # run.py
    
    print(__file__)
    
    # 运行
    E:/Python/ATM/bin/start.py

    __file__可以获取当前执行文件的绝对路径,不同的机器可以获取不同的绝对路径。现在需要获取执行文件所在文件夹的父级文件夹,所以可以基于__file__来获取

    os模块有个功能可以获取当前执行文件所在的文件夹的路径

    # run.py
    
    import os
    print(os.path.dirname(__file__))
    
    # 运行
    E:/Python/ATM/bin

    那么基于上面获取父级文件夹就是再使用一个os.path.dirname

    # run.py
    
    import os
    print(os.path.dirname(os.path.dirname(__file__)))
    
    # 运行
    E:/Python/ATM

    这时便获取到了执行文件所在文件夹的父级文件夹,然后添加到环境变量,以后无论这个软件在哪台机器上运行,都可以得到精准的目录

    # run.py
    
    import os
    import sys
    
    BASE_DIR = os.path.dirname(os.path.dirname(__file__))
    sys.path.append(BASE_DIR)
    
    from core import src
    
    src.run()

    有一个标准的写法,是将if __name__ == '__main__': 添加进去

    # run.py
    
    import os
    import sys
    
    BASE_DIR = os.path.dirname(os.path.dirname(__file__))
    sys.path.append(BASE_DIR)
    
    from core import src
    
    if __name__ == '__main__':
        src.run()
  • 相关阅读:
    Codeforces 166E. Tetrahedron
    Codeforce 687A. NP-Hard Problem
    Codeforces 570C. Replacement
    Codeforces 554B. Ohana Cleans Up
    Codeforces 482A. Diverse Permutation
    Codeforces 431C. k-Tree
    Codeforces 750B. Spider Man
    Codeforces 463A. Caisa and Sugar
    Codeforces 701B. Cells Not Under Attack
    Codeforces 445A. DZY Loves Chessboard
  • 原文地址:https://www.cnblogs.com/qiuxirufeng/p/9773001.html
Copyright © 2020-2023  润新知