VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • python基础教程之自定义模块,模块的运行方式,

自定义模块:

  • 什么是模块:本质就是.py文件,是封装语句的最小单位。模块是一系列常用功能的集合体,一个py文件就是一个模块。
  • 自定义模块:实际上就是定义 . py ,其中可以包含:变量定义,可执行语句,if结构,for循环,函数定义等等,他们统称模块的成员
  • 为什么要使用模块:
    • 1、从文件级别组织程序,更方便管理
      随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用
    • 2、拿来主义,提升开发效率,避免重复造轮子
  • 常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀。其实import加载的模块分为四个通用类别: 
    • 1 使用python编写的代码(.py文件)
    • 2 已被编译为共享库或DLL的C或C++扩展
    • 3 包好一组模块的包
    • 4 使用C编写并链接到python解释器的内置模块

模块的运行方式:

  • 脚本方式:直接用解释器执行。或者PyCharm中右键运行。脚本,一个文件就是整个程序,用来被执行

    #自定义模块:(a1.py)--->以脚本方式直接运行
        # 可执行语句
        age = 10
        print(age)
    
        for x in range(3):
             print(x)
    
        # 函数定义
        def f1():
            print('hello world')
        f1()
    
        print(__name__)
    
        #结果:
        10
        0
        1
        2
        hello world
        __main__  #注意同下面的区别
  • 模块方式:被其他的模块导入。为导入它的模块提供资源(变量,函数定义,类定义等)。模块,文件中存放着一堆功能,用来被导入使用

    #1:
    #自定义模块:(a1.py)--->被其他模块导入
        # 可执行语句
        age = 10
        print(age)
    
        for x in range(3):
             print(x)
    
        # 函数定义
        def f1():
            print('hello world')
        f1()
    
        print(__name__)
    
     #测试自定义模块的导入: (test.py 该py文件 与 a1.py 在同一个目录下)
      import a1  #import不会导致a1中的不可执行语句(函数定义,类定义等)立刻执行,会导致可执行语句立即执行。仅仅通过该句不能判断a1中没有不可执行语句。
    
        #运行结果:   #【问题】自定义模块被其他模块导入时,其中的可执行语句会立即执行
        10
        0
        1
        2
        hello world
        a1  #注意同上面的区别,是被导入模块的名字
    
    #2:
      #自定义模块:(a1.py)--->被其他模块导入
        age = 10
    
        # 函数定义
        def f1():
            print('hello world')
    
      #测试自定义模块的导入: (test.py 该py文件 与 a1.py 在同一个目录下)
      import a1
    
        print(a1.age) #使用自定义模块的成员  模块名.变量名
    
        a1.f1()       #使用自定义模块的成员
    
        print(a1.__name__)  #使用自定义模块的成员
    
        #结果:
        10
        hello world
      a1
    
    

__name__属性的使用:

在脚本方式运行时,__name__是固定的字符串:__main__

在以模块方式被导入时,__name__就是本模块的名字。

在自定义模块中对__name__进行判断,决定是否执行可执行语句:开发阶段,就执行,被导入使用阶段就不执行。

  • 自定义模块中,通常不会直接包含可以执行的打印语句,循环语句等,而是包含一些变量,函数,类的定义.这些统
    称为模块的成员。
    
    自定义模块中,把对模块成员的测试代码放到一个单独的函数中(通常起名为main).根据__name__ 属性的值决
    定是否执行这个测试函数。 __name__用来控制.py文件在不同的应用场景下执行不同的逻辑(或者是在模块文件中测试代码)
 #自定义模块:(a1.py)--->以脚本方式直接运行【开发阶段】
    
    age = 10

    # 函数定义
    def f1():
        print('hello world')

    # 测试函数(定义一个函数,包含测试语句),在开发阶段,对本模块中的功能进行测试。
    def main():
        print(age)
        for x in range(3):
            print(x)
        f1()
        print(__name__)
    #python中提供一种可以判断自定义模块是属于开发阶段还是使用阶段。决定是否执行可执行语句:开发阶段,就执行,使用阶段就不执行 
    if __name__ == '__main__':
        main()#可执行语句会执行
    #结果;
    10
    0
    1
    2
    hello world
    __main__
    
    
  #测试自定义模块的导入: (test.py 该py文件 与 a1.py 在同一个目录下,同一个路径下)
    import a1
    
    #test.py以脚本方式运行,什么都不显示,可执行语句没有执行。
    

系统导入模块的路径

​ 导入一个模块时:(import xxx 去哪些路径寻找模块xxx?)模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块。

  • 内存中:如果之前成功导入过某个模块,会拿过来直接使用已经存在的模块。(如果两次导入同一个模块,如果第一次导入成功,第二次导入时就不会再导入,重复导入会直接引用内存中已经加载好的结果)模块可以包含可执行的语句函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行。import语句是可以在程序中的任意位置使用的,且针对同一个模块被import多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载到内存中的模块对象增加了一次引用,不会重新执行模块内的语句。
  • 内置路径中:安装路径下:Lib (如果内存中没有,就去内置路径下寻找。如果没有,解释器则会查找同名的内建模块)
  • PYTHONPATH: import 时寻找模块的路径。
  • sys.path:是一个路径的列表。(如果在内存中和内置路径下都没找到某个模块,就回去sys.path这个列表里去找。如果还没有找到,就从sys.path给出的目录列表中依次寻找spam.py文件。)【列表可修改sys.path.append() sys.path.insert() 】

如果上面都找不到,就报错。

有三种解决方案:

  1. 把我们自定义的模块放到系统默认的路径中,例如:D:\mysoft\Python36\lib
  2. 在环境变量中定义PYTHONPATH,在其中保存被导入模块的路径.
  3. 手动修改sys.path表示的列表.将自定义的模块的路径添加到其中.
    第一种方案并不好,因为会污染系统自带的模块库.

第二种方案也不好,因为系统环境变量的改变会影响到其他的项目.

通常的做法是采用第三种方案.即不会污染系统内置路径也不会影响其他项目.只在当前项目中有效.

问题:两个py文件在同一个路径下,可以进行模块导入。系统自带的模块,也可以导入。但是,如果两个py文件不在同一路径下,直接引用模块就会报错。ModuleNotFoundError: No module named 'a1'
解决:导入自己写的模块时,通常会修改sys.path,在列表中添加一个路径,把要导入模块a1.py的路径添加到sys.path中。

可以通过动态修改sys.path列表的方式将自定义模块添加到sys.path中

#测试自定义模块的导入(test.py)
    查看sys.path内容,系统查找模块的路径。
    import sys
    print(sys.path)#是一个列表,第一个元素是当前脚本所在的路径:'D:\\python22\\day15',第二个元素是当前项目的路径 'D:\\python22',如果脚本和项目之间有多级路径,路径不会在该列表中
    
    #1:(有缺陷)
        添加a1.py所在的绝对路径到sys.path中
        import sys
        sys.path.append(r'D:\python_22\day15\aa') #这个是绝对路径,如果项目路径修改了,比如不在D盘了,就不能成功导入了
        import a1
        print(a1.age)#10  说明导入成功
    #2:找到a1.py相对于当前脚本文件test.py的相对路径。a1.py和test.py在同一项目下,相对路径不会变。
        使用相对位置找到aa文件夹,首先要知道当前文件的路径
        print(__file__) #D:/python22/day15/test.py  当前文件的绝对路径
         # 使用os模块获取一个路径的父路径,
          os.path.dirname():获取某个路径的父路径。通常用于获取当前模块的相对路径
      
        import os   #用于找到要添加的路径
        print(os.path.dirname(__file__))#D:/python22/day15    当前模块的相对路径,用来获得模块所在的路径
        print(os.path.dirname(__file__)+ '/aa')#D:/python22/day15/aa  因为test.py和aa文件夹在同一文件夹day15下,a1.py在aa文件夹下,所以test.py的相对路径 + aa 就得到了a1.py的相对路径
       #
       import os   
       import sys  #用于添加寻找到的路径
       sys.path.append(os.path.dirname(__file__) + '/aa')
       print(sys.path)# 列表的最后一个元素是:'D:/python22/day15/aa',说明a1的相对路径添加到了sys.path
       import a1
       print(a1.age)#10  说明导入成功
     #总结:   
        import sys
        import os
        sys.path.append(os.path.dirname(__file__) + '/aa')

导入模块的多种方式:

  • import xxx:导入一个模块的所有成员

  • import aaa,bbb:一次性导入多个模块的成员。不推荐这种写法,建议分开写。

  • from xxx import a:从某个模块中导入指定的成员。

  • from xxx import a,b,c:从某个模块中导入多个成员。

  • from xxx import *:从模块中导入所有成员。大部分情况下我们的python程序不应该使用这种导入方式,因为你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差。

  • import 语句的模块顺序:推荐所有的模块在 Python 模块的开头部分导入。 而且最好按照这样的顺序:Python 标准库模块,Python 第三方模块,应用程序自定义模块,然后使用一个空行分割这三类模块的导入语句。

    可以使用__all__来控制 *(用来发布新版本)。在自定义模块中新增一行__all__=['age1','age2'] ,这样在另外一个文件中用from xxx import *就只能导入列表中规定的两个名字。
    
    #自定义模块:(a1.py)--->被其他模块导入
        age = 10
    
        # 函数定义
        def f1():
            print('hello world')
    
    #测试自定义模块的导入: (test.py 该py文件 与 文件夹aa 在同一个目录下,a1.py 在 aa 目录下)
        # 把自定义模块的路径添加到sys.path中
        import os
        import sys
        sys.path.append(os.path.dirname(__file__) + '/aa')
    
        # 使用import xxx(模块名) 导入
         age = 1000
         import a1
         print(a1.age)#10
    
        # 使用from xxx(模块名) import age(成员名) 的方式导入
         age = 1000
         from a1 import age
       print(age)#10

import xxx 和 from xxx import * 的区别

第一种方式在使用其中成员时,必须使用模块名作为前缀。不容易产生命名冲突。

第二种方式在使用其中成员时,不用使用模块名作为前缀,直接使用成员名即可,使用方便。但是容易产生命名冲突(与当前执行文件中的名字冲突)。在后面定义的成员生效(执行文件若有与模块同名的变量或者函数名,会有覆盖效果,把前面的覆盖了。)不是良好的编程风格, 因为它"污染"当前名称空间, 而且很可能覆盖当前名称空间中现有的名字; 但如果某个模块有很多要经常访问的变量或者模块的名字很长, 这也不失为一个方便的好办法。

怎么解决名称冲突的问题

  • 改用import xxx这种方式导入。---> 必须使用模块名作为前缀。不容易产生命名冲突
  • 自己避免使用同名
  • 使用别名解决冲突

使用别名:alias

  • 给成员起别名,避免名称冲突
    • from my_module import age as a
  • 给模块起别名,目的简化书写可以将过长的模块命名改成短的,便于操作,有利于代码的拓展,优化。
    • import my_module as m
    
    #自定义模块:(a1.py)--->被其他模块导入
        age = 10

        # 函数定义
        def f1():
            print('hello world')
    
    
    #测试自定义模块的导入: (test.py 该py文件 与 文件夹aa 在同一个目录下,a1.py 在 aa 目录下)
        # 把自定义模块的路径添加到sys.path中
        import os
        import sys
        sys.path.append(os.path.dirname(__file__) + '/aa')

        # 成员使用别名避免命名冲突
        from a1 import age as a
        age = 1000
        print(age)#1000
        print(a)#10

        # 给模块起别名
        import a1 as m
        print(m.age)#10

from xxx import * 控制成员被导入

默认情况下,所有的成员都会被导入

__all__是一个列表,用于表示本模块可以被外界使用的成员。元素是成员名的字符串。

注意

__all__只是对 from xxx import * 这种导入方式生效。其余的方式都不生效。

#自定义模块  (a1.py)--->被其他模块导入

    # 使用__all__控制被导入的成员
    __all__ = [
        'age1',
        'age2',
    ]

    age1 = 10
    age2 = 20
    age3 = 30
    
#测试自定义模块的导入: (test.py 该py文件 与 文件夹aa 在同一个目录下,a1.py 在 aa 目录下)
    import os
    import sys
    sys.path.append(os.path.dirname(__file__) + '/aa')

    # 验证__all__控制的成员
    from a1 import *
    print(age1)#10
    print(age2)#20
    print(age3)#NameError: name 'age3' is not defined


    # 使用如下方式,可以绕过__all__的限制
        import a1 as m     #__all__只是对 from xxx import *这种导入方式生效
        print(m.age1)#10
        print(m.age2)#20
        print(m.age3)#30

        import a1          #__all__只是对 from xxx import *这种导入方式生效
        print(a1.age1)#10
        print(a1.age2)#20
        print(a1.age3)#30

        from a1 import *
        print(age1)#10
        print(age2)#20
        from a1 import age3    #没写到__all__中的变量,可以单独引用
        print(age3)#30
    
    
    

相对导入

Python 允许通过在模块或包名称前置句点实现相对导入。

​ 因为 import 语句总是绝对导入的, 所以相对导入只应用于 from-import 语句,而不适用于import语句。

​ 语法的第一部分是一个句点, 指示一个相对的导入操作。 之后的其他附加句点代表当前 from起始查找位置后的一个级别。

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

​ 针对某个项目中的不同模块之间进行导入,称为相对导入。外界使用这个包中的某个模块时,并不知道这个模块内部是否导入了其他模块。

只有一种格式:

​ from 相对路径 import xxx(模块名 或 * ) 例如: from ..z.zz import * (其中zz是模块名)

注:此处的相对路径是用.或者..的方式作为起始

  • 符号: .代表当前文件所在的文件夹,..代表上一级文件夹,...代表上一级的上一级文件夹
    • .表示的是当前文件的父路径。
    • ..表示的是父路径的父路径。
    • ..z表示的是父路径的父路径下的文件夹z
  • 相对导入: 用.或者..的方式作为起始(只能在一个包中使用,不能用于不同目录内。只能在导入包中的模块时才能使用。)
    • 相对导入只能用于包内部模块之间的相互导入,导入者与被导入者都必须存在于一个包内。只能在导入包中的模块时才能使用,相对导入方式通常是用在一个路径(包)的多个子模块互相导入时使用。
    • 试图在顶级包之外使用相对导入是错误的,言外之意,必须在顶级包内使用相对导入,每增加一个.代表跳到上一级文件夹,而上一级不应该超出顶级包。
#1 相对导入同项目下的模块:
      # from . import abc             # 导入同路径下的abc模块
      # from ..z import zz            # 容易向外界暴露zz模块,不使用此方式
      
     from ..z.zz import *  (使用此方式导入)  # ..z.zz是一个相对路径
#2 不使用相对导入的方式,导入本项目中的模块,通过当前文件的路径找到z的路径
        import os
        import sys
        sys.path.append(os.path.dirname(os.path.dirname(__file__)) + '/z')
        from zz import * (使用此方式导入)
        #import zz  容易向外界暴露zz模块,不使用此方式

#1: 使用相对导入的方式,导入本项目中的模块,可以简化代码
    【模块1 yy.py 和模块2 zz.py 在同一个项目下(把xx看作一个项目,一个包),模块1和模块2之间的导入称为相对导入,模块1作为对外引用的入口,模块1yy.py 和模块3 test.py 之间不属于相对导入】
    
    模块1#项目中,子模块  (zz.py) 作为被相对导入的模块,为本项目下的模块提供资源   D:/python22/day15/xx/z/zz.py
        age = 10
        def f():
            print('hello')
    
    模块2:在模块2中导入了模块1,模块3不直接导入模块1,模块3导入模块2之后,就可以使用模块1和模块2
    #在项目中,此模块作为对外引用的入口 (yy.py),外界要想引用xx这个项目,要从yy.py 引用。 D:/python22/day15/xx/y/yy.py
  
        # 相对导入同项目下的模块
        #from ..z import zz             # 容易向外界暴露zz模块,不使用此方式  从yy.py 的父路径y的父路径xx下边找到z,再导入z下的zz模块
        from ..z.zz import *        #【使用此方式相对导入同项目下的模块】

        # 定义自己的成员
        age2 = 888
        def f2():
            print('f2')
            
        注意:模块2使用相对导入引用模块1后,直接以脚本方式运行模块2会报错,ValueError: attempted relative import beyond top-level package
            但是,通过添加路径的方式,然后模块2被模块3导入后再运行,在模块3不会报错,能执行模块2中的内容。
            如果模块3使用相对导入from ..xx.y import yy 导入模块2,然后以脚本方式运行,同样会报错ValueError: attempted relative import beyond top-level package
    
    模块3#测试模块 ( test.py) D:/python22/day15/test.py
        import os
        import sys
        # 把项目xx所在的父路径加到sys.path中  D:/python22/day15
        sys.path.append(os.path.dirname(__file__))

        from xx.y import yy   #此处不是相对导入
        print(yy.age2)#888
        yy.f2()#f2
        
        #模块2中使用 from ..z import zz  导入zz模块时才可以,否则会报错。但是容易暴露zz模块(不想让外界知道zz模块的存在),所以模块1和模块2相对导入不使用 from ..z import zz 。
        #使用模块1(zz.py)下的成员
        print(yy.zz.age)#10
        yy.zz.f()#hello

        #模块2中使用 from ..z.zz import *  导入zz模块时才可以,否则会报错。使用此方式相对导入同项目下的模块(在模块yy下导入模块zz),外界test.py在使用时就看不到模块zz
        print(yy.age)#10  #外界看不到zz模块,相当于直接在yy下定义了age和f() ,实际上age 和 f()是zz下的成员
        yy.f()#hello

        
   
#2:不使用相对导入的方式,导入本项目中的模块
    模块1#项目中,子模块  (zz.py)  D:/python22/day15/xx/z/zz.py
        age = 10
        def f():
            print('hello')

    模块2:在模块2中导入了模块1,模块3不直接导入模块1,模块3导入模块2之后,就可以使用模块1和模块2
    #此模块作为对外引用的入口 (yy.py) D:/python22/day15/xx/y/yy.py
 
       # 不使用相对导入的方式,导入本项目中的模块。通过当前文件的路径找到z的路径。
         import os
         import sys
         sys.path.append(os.path.dirname(os.path.dirname(__file__)) + '/z')    #将zz的相对路径添加到sys.pah中: yy.py 的父路径为 y ,y 的父路径为 xx, xx 下的 z,即为 zz 的相对路径
         from zz import *   #在模块 yy 中导入了 模块zz

        # 定义自己的成员
        age2 = 888
        def f2():
            print('f2')
     
    模块3#测试模块 (test.py) D:/python22/day15/test.py
        import os
        import sys
        # 把项目xx所在的父路径加到sys.path中  D:/python22/day15
        sys.path.append(os.path.dirname(__file__))

        from xx.y import yy  #此处不是相对导入,因为先在sys.path中添加了路径。
        print(yy.age2)#888
        yy.f2()#f2
        
        #模块2中未使用相对导入 
        print(yy.age)#10
        yy.f()#hello
        
        #模块2中不使用相对导入,并且使用 import zz 导入zz模块时  或者模块2使用相对导入from ..z import zz  导入zz模块时 才可以,否则会报错。【容易向外界暴露zz模块】
        print(yy.zz.age)  #报错  AttributeError: module 'xx.y.yy' has no attribute 'zz'
        yy.zz.f() #报错   
        
        
        #自测试--模块3:
            from xx.y.yy import *   #导入xx文件夹下的y文件夹下的yy.py模块的所有成员
        
            #注:模块2没有使用相对导入,通过当前文件的路径找到z的路径。且使用 from zz import * 导入zz模块的情况
            print(age2)  #888     #age2,f2,age,f前面不能再加yy
            f2()         #f2
            
            print(age) #10
            f()        #hello

       

        
    
   在模块1(zz.py)和模块2(yy.py)不变的情况下,模块3(test.py)的路径改变,放到aa文件夹下 #D:/python22/day15/aa/test.py
   test.py是用于直接运行的脚本,yy是对外提供导入的模块(yy模块中引用了zz模块)
        
            #要想成功导入模块2(使用模块1和模块2),需要把xx文件夹的相对路径添加到sys.path
            import os
            import sys
            # 把项目xx所在的父路径加到sys.path中
            sys.path.append(os.path.dirname(os.path.dirname(__file__))) #__file__ 是test.py的绝对路径,os.path.dirname(__file__)是其父路径,os.path.dirname(os.path.dirname(__file__))是其父路径的父路径,同时也是xx的父路径。
            print(os.path.dirname(os.path.dirname(__file__)))#D:/python22/day15

            #测试:from day15.xx.y import yy  使用此句添加模块yy,就不用了上面的3行语句了。
            
            from xx.y import yy
            print(yy.age2)#888
            yy.f2()#f2
        
            #注:在模块2中使用 from ..z.zz import * (相对导入)
            print(yy.age)#10
            yy.f()#hello
 


相关教程