zoukankan      html  css  js  c++  java
  • part9-1 Python 模块和包(模块导入语法,模块定义,__doc__属性,环境变量设置,模块加载路径,模块导入的本质,模块__all__变量)


    Python 语言被广泛用于各行各业,很大程度上利益于它的模块化系统。在 Python 标准安装时包含了一组自带模块,这些模块称为“标准库”。Python3 标准库参考 https://docs.python.org/3/library/index.html

    实际开工中可以根据需要为 Python 增加扩展库。Python 在各行各业有丰富的扩展库,这些扩展库组成了“生态圈”。

    一、 模块化编程

    一个完整的 Python 程序通常需要借助第三方类库,并且源代码也不可能放在一个源文件中。这些需要以模块方式来组织项目的源代码。

    1、 导入模块的语法
    使用 import 语句导入模块,主要有两种用法:
    (1)、import 模块名1[as 别名1], 模块名2[as 别名2], ...;导入整个模块
    (2)、from 模块名 import 成员名1[as 别名1], 成员名2[as 别名2], ...;导入模块中指定成员。

    两种导入模块的语法区别有三点:
    (1)、第一种语法导入整个模块内的所有成员(包括变量、函数、类等);
    (2)、第二种语法只导入模块内的指定成员(除非使用 from 模块名 import *,不建议用这种语法)。
    (3)、用第一种语法导入模块时,在后面的代码中要使用该模块的成员时,必须在成员名前添加模块名或模块别名前缀;使用第二种语法导入模块中的成员时,不使用任何前缀,直接使用成员名或成员别名即可。

    下面语法使用简单语法导入指定的整个模块,不使用别名:
    import sys              # 导入整个 sys 模块
    # 要使用 sys 模块名作为前缀来访问模块中的成员
    print(sys.argv[0])      # 输出是源程序的文件名
    sys 模块下的 argv 变量用于获取运行 Python 程序的命令行参数,其中 argv[0] 获取的是该 Python 程序的文件名。

    在导入整个模块时也可以为模块指定别名,此时可通过别名访问模块的成员:
    import sys as s     # 导入整个 sys 模块,并指定别名为 s
    # 使用 s 别名作为前缀访问模块中的成员
    print(s.argv[0])
    导入整个模块的语法可以一次导入多个模块,多个模块间用逗号隔开,示例如下:
    import sys, os      # 同时导入 sys、os 两个模块
    # 使用模块名作为前缀访问模块中对应的成员
    print(sys.argv[0])
    print(os.sep)
    os 模块的 sep 变量代表系统平台上的路径分隔符。当然,在导入多个模块时也可为模块指定别名,示例如下:
    import sys as s, os as o        # 为 sys 指定别名 s,为 os 指定别名 o
    # 使用模块别名作为前缀访问模块中的成员
    print(s.argv[0])
    print(o.sep)
    使用 from ... import 导入模块的指定成员简单语法是:
    from sys import argv        # 导入 sys 模块内的 argv 成员
    print(argv[0])              # 导入成员后,直接使用成员名调用
    当然,使用 from...import 语法导入成员时,也可为成员指定别名访问,无需使用前缀,示例如下:
    from sys import argv as v       # 为导入的成员使用别名
    print(v[0])                     # 直接使用成员的别名访问即可
    使用 from ... import 导入模块时可以导入同一个模块的多个成员,并且还可为多个成员指定别名,示例如下:
    from sys import argv as v, winver as wv     # 使用 as 关键字,为不同的成员指定别名
    print(v[0])
    print(wv)       # 记录 Python 的版本号
    其中 sys 模块内的 winver 成员记录的是 Python 的版本号。

    使用 from ... import 语法还可一次导入指定模块内的所有成员,示例如下:
    from sys import *   # 导入 sys 模块内的所有成
    # 导入所有成员后,直接使用成员名即可访问成员
    print(argv[0])
    print(winver)
    不推荐使用 “from 模块 import *” 的语法导入模块内的所有成员,是因为这样做存在潜在的风险。假设导入的两个模块都有相同的成员 foo() 函数时,那么在后面调用 foo() 函数时,就不知道是在调用哪个模块的 foo() 函数,所以这种导入模块的方法有风险。可以将导入模块的方式改为下面形式:
    import module1
    import module2 as m2
    # 访问模块成员时,加上模块名或别名为前缀,可以很清楚的知道在调用哪个成员
    module1.foo()
    m2.foo()
    
    # 也可使用 from ... import 语法导入,并指定别名的方式也是可行的
    from module2 import foo as foo1
    from module2 import foo as foo2
    # 直接通过别名访问成员,也可进行更好的区分
    foo1()
    foo2()

    2、 定义模块
    模块就是 Python 程序,任何 Python 程序都可作为模块导入。使用模块的好处在于:可将某些全局变量、类、函数等成员放在一个单独的文件(或模块)中,后面不管在哪个代码文件中要使用这些成员,只需要在当前的代码文件中导入包含需要的成员的文件即可,这样可以很好的复用。导入模块,使用模块,避免每个代码文件都需要定义这些重复的成员。

    模块文件的文件名就是对应的模块名,比如 sys.py 的模块名就是 sys 一样。

    3、 为模块编写说明文档
    在定义函数、类的时候应该为函数、类写说明文档,同样也应该为模块写说明文档。模块的说明文档应包含该模块的用途,以及包含的功能等。

    模块的说明文档定义在模块的开始处,将说明信息包含在一个三引号内。例如下面这样:
    """
    这是 michael 编写的第一个模块,包含的内容有:
    N:计数器
    foo():一个简单的函数
    User:代表用户的类
    """
    这样的一段在模块文件开始处的字符串内容就是模块的说明文档,可通过模块的 __doc__ 属性访问说明文档。例如要访问 sys 模块的
    说明,可使用 sys.__doc__ 语句即可。

    4、 为模块编写测试代码
    测试代码用于测试模块中的每一个程序单元是否都能正常运行。由于模块就是一个 Python 文件,所以可以在命令行下像执行脚本文件一样执行 Python 源代码文件,在命令行使用的是 python 命令来解释和执行模块文件,只要模块文件中包含可执行代码。

    直接在命令行下执行模块文件对模块进行测试时,对于不是可执行代码的,比如模块中的变量、函数、类这些成员得不到相应的测试,所以还需要为这些成员提供测试程序。在实际开发项目时,对每个函数、类都需要使用更多的测试用例进行测试,以达到各种覆盖效果。

    测试代码要达到的效果是:如果直接使用 python 命令运行该模块(相当于测试),程序应该执行该模块的测试函数;如果是在其他代码文件中导入该模块,则不应该执行该模块的测试函数。为达到这个目的,所有模块有内置的 __name__ 变量进行区分,如果直接使用python 命令运行一个模块,则 __name__ 变量的值为 __main__;如果该模块被导入其他文件中,__name__ 变量的值就是模块名。所以希望测试函数在使用 python 命令直接运行时才执行,可在调用测试函数时增加判断:只有当 __name__ 属性值为 __main__ 时才调用测试函数。可在模块中添加下面这种测试代码即可:
    if __name__ == '__main__':
        main()      # 模块的测试代码细节在 main() 函数中
    可将本模块的测试代码细节封装在一个函数或类中,在 “if __name__ == '__main__':” 语句后面调用相应的函数或类即可完成模块的测试。此时,在其他源代码文件中导入该模块时,测试代码也不会执行。

    二、加载模块

    编程就是用合适的语法告诉计算机,让它帮助完成某个工作,因此计算能完成的事情,是程序员预先告诉它的。

    直接使用 import 或 from ... import 语法导入模块时,Python 需要知道去哪里找到这个模块。为了让 Python 能找到自定义模块(或第三方模块),可用下面两种方式告诉它:
    (1)、使用环境变量。
    (2)、将模块放在默认的模块加载路径下。

    1、 使用环境变量
    Python 根据 PYTHONPATH 环境变量的值来确定到哪里去加载模块。该变量的值是多个路径的集合,会依次搜索 PYTHONPATH 环境变量所指定的多个路径,试图从中找到程序要加载的模块。

    (1)、 Windows 平台上设置环境变量
    右击 “计算机” 图标选择 “属性” 菜单,系统显示“控制面板所有控制面板项系统” 窗口,单击该窗口左边栏中的 “高级系统设置”,“出现系统属性” 对话框,单击该对话框中的“高级”选项中的“环境变量”按钮,此时可看到 “环境变量” 对话框,通过该对话框可添加或修改环境变量。

    在“环境变量”对话框中,上面是 “用户变量” 部分用于设置当前用户的环境变量,下面是“系统变量” 部分用于设置整个系统的环境变量。系统变量对所有用户有效,在搜索路径时,系统变量的路径排在用户变量路径之前。

    单击用户变量中的 “新建” 按钮后,在 “变量名” 文本框中输入 PYTHONPATH,“变量值” 文本框中输入 .;d:python_module,点击确定后就创建了一个环境变量名是 PYTHONPATH,路径是 .;d:python_module 的环境变量。变量值中的路径以英文分号为分隔符,第一条路径为一个点(.),表示的是当前路径,即当运行 Python 时,Python 总能从当前路径加载模块;第二条路径是 d:python_module,表示运行 Python 时,可以从 d:python_module 中加载模块。

    设置成功后,可将 Python 源代码文件放在与当前运行 Python 程序相同的路径中或放在 d:python_module 路径下,这些源代码文件就能被成功加载。

    (2)、 在 Linux 上设置环境变量
    在 Linux 命令行界面下,进入到当前用户的 home 目录下,可执行 ls -a 命令列出当前目录下的所有文件,包括隐藏文件。Linux 平台的环境变量是通过 .bash_profile 文件来设置的,用 vim 打开该文件并添加 PYTHONPATH 环境变量,添加下面一行:
    PYTHONPATH=.:/home/michael/python_module
    Linux 平台上的路径分隔符是冒号(:),上面这一行设置了两个路径:点(.)是当前路径;另一条是当前用户目录下的 python_module 目录下。设置完 PYTHONPATH 变量值后,还需要在 .bash_profile 文件的最后面添加导出 PYTHONPATH 变量的语句:
    export PYTHONPATH
    保存并退出 .bash_profile 文件,并执行命令:
    source .bash_profile
    让 .bash_profile 文件中设置的 PYTHONPATH 变量值生效。设置完环境变量后,只要将自定义的模块(Python 源代码文件)放在 /home/michael/python_module 目录下,该模块就可以被成功加载。

    当在代码中重复导入同一个模块时,Python 只会导入一次。因为这些变量、函数、类等程序单元都只需要定义一次即可,不必导入多次。

    2、 默认的模块加载路径
    在安装第三方模块时,应直接安装在 Python 内部,这样可被所有程序共享,此时需要借助于 Python 默认的模块加载路径。Python 默认的模块加载路径由 sys.path 变量代表,在交互式解释器下执行下面命令可查看 Python 默认的模块加载路径。
    import sys, pprint
    pprint.pprint(sys.path)         # 输出省略
    这里使用 pprint 模块下的 pprint() 函数代替普通的 print() 函数,可以对要打印的内容有更友好的显式。通常应将扩展模块添加在 libsite-packages 路径下,它专用于存放 Python 的扩展模块和包。

    示例,将下面的代码保存为 print_shape.py 文件,并将该文件放在 libsite-packages 目录下。
     1 """
     2 一个简单的测试模块,该模块包含以下内容
     3 my_list:列表变量
     4 print_triangle:打印由星号组成的三角形函数
     5 """
     6 my_list = ['python', 'java', 'javascript']
     7 def print_triangle(n):
     8     """打印由星号组成的三角形"""
     9     if n <= 0:
    10         raise ValueError("n 必须大于 0")
    11     for i in range(n):
    12         print(" " * (n - i - 1), end=" ")
    13         print("*" * (i * 2 + 1), end=" ")
    14         print()
    15 
    16 # 下面是测试代码
    17 def test_print_triangle():
    18     print_triangle(5)
    19     print_triangle(3)
    20     print_triangle(8)
    21 
    22 if __name__ == '__main__':
    23     test_print_triangle()
    将上面代码保存为 print.shape.py 文件,并将该文件放在 libsite-packages 目录下后,就相当于为 Python 扩展了一个 print_shape 模块,现在在任何 Python 程序中都可以使用该模块,在 Python 交互式解释器中可以进行测试,示例如下:
    import pring_shape as ps
    print(ps.__doc__)           # 输出模块的描述信息
    ps.print_triangle.__doc__   # 查看模块中 print_triangle 函数的描述信息
    ps.my_list[2]               # 测试模块中 my_list 变量
    ps.print_triangle(5)        # 测试模块中 print_triangle() 函数,该函数的输出如下:
         *
        ***
       *****
      *******
     *********
    通过上面的一些测试表明该模块完全可以正常运行。

    2、模块导入的本质
    在当前的代码文件中导入某一个模块时(比如前面的 print_shape 模块),执行当前的源代码文件,import 导入的模块文件也会自动执行,当导入的模块中含有可执行代码,此时模块中的可执行代码就获得执行的机会,所以在模块中尽量不写可执行的代码。此外,在当前的执行程序中还包含一个与模块同名的变量,变量的类型是 module(可通过 print(type(print_shape)) 查看)。

    因此,当使用 “import print_shape” 导入模块的本质就是:将 print_shape 中的全部代码加载到内存并执行,然后将整个模块内容赋值给与模块同名的变量,该变量的类型是 module ,在模块中定义的所有程序单元都相当于该 module 对象的成员。

    当使用 from ... import 语句只导入模块中部分成员(比如 from print_shape import my_list),该模块中的可执行代码也会在 import 时自动执行,这说明了 Python 依然会加载并执行模块中的代码。所以,使用 from ... import 导入模块中的成员的本质是:将模块中的全部代码加载到内存并执行,然后只导入指定变量、函数等成员单元,并不会将整个模块导入,此时在使用 print(type(print.shape)) 语句时,会得到错误提示 NameError: name 'print_shape' is not defined。

    在导入模块后,可在模块文件所在目录下看到一个名为 “__pycache__” 的文件夹,在该文件夹下可看到 Python 为每个模块都生成一个 *.cpython-36.pyc 文件,比如 Python 为 print_shape 模块生成一个 print_shape.cpython-36.pyc 文件,该文件是Python 为模块编译生成的字节码,用于提升该模块的运行效率。其中的 36 表示 Python 的版本号。

    4、 模块的 __all__ 变量
    在使用 “from 模块名 import *” 语句导入模块时,会在当前程序中导入该模块中所有不以下划线开头的程序单元。除此之外,还可以在模块中定义 __all__ 变量,该变量的值是一个列表,只有在该列表中的程序单元才会被暴露出来(允许被导入)。示例如下,在 print_shape 模块中定义 __all__ 变量,代码如下:
     1 _name = 'michael'       # 测试以下划线开头的变量能否被导入
     2 my_list = ['python', 'java', 'javascript']
     3 print('hello world')
     4 def print_triangle(n):
     5     """打印由星号组成的三角形"""
     6     if n <= 0:
     7         raise ValueError("n 必须大于 0")
     8     for i in range(n):
     9         print(" " * (n - i - 1), end=" ")
    10         print("*" * (i * 2 + 1), end=" ")
    11         print()
    12 
    13 # 下面是测试代码
    14 def test_print_triangle():
    15     print_triangle(5)
    16     print_triangle(3)
    17     print_triangle(8)
    18 
    19 # 定义 __all__ 变量,默认只导入 my_list 和 print_triangle 两个成员,注意列表的元素是字符串形式
    20 __all__ = ['my_list', 'print_triangle']
    21 
    22 if __name__ == '__main__':
    23     test_print_triangle()
    在这个 print_shape.py 模块中,定义了一个以下划线开头的变量_name、一条可执行语句 print、一个列表变量 my_list和两个函数,现在在当前模块所在的目录下的另一个源代码文件中导入这个模块,代码如下所示:
    from print_shape import *
    # 把 print_shape.py 模块中的 __all__ 变量这一行注释掉后可测试下面这行代码
    # print(_name)        # 提示:NameError: name '_name' is not defined
    print(my_list)
    print_triangle(3)
    test_print_triangle()       # 提示函数未定义
    
    运行代码,输出结果如下所示:
    hello world
    ['python', 'java', 'javascript']
       *
      ***
     *****
    Traceback (most recent call last):
      File "cp9_exercise.py", line 15, in <module>
        test_print_triangle()
    NameError: name 'test_print_triangle' is not defined
    从输出结果可知,使用 “from print_shape import * ” 导入了 print_shape 模块下的程序单元,但由于该模块有 __all__ 变量,因此该语句只导入 __all__ 变量所列出的程序单元。另外,模块中以下划线开头的成员同样可以放在 __all__ 变量中,该下划线开头的成员同样可以被导入。要注意的是,__all__ 变量列表的元素只能是字符串,模块中的成员只能以字符串的形式出现在 __all__ 变量的列表中

    __all__ 变量为模块定义了一个开放的公共接口。在大型模块中可能有大量不需要使用的变量、函数和类等,可通过设置 __all__ 变量来把它们过滤掉。如果仍然希望使用模块内 __all__ 列表之外的程序单元,有两种方法可用:
    (1)、第一种是使用 “import 模块名” 导入模块。用过种方式导入模块,总可以使用模块名前缀来调用模块内的成员。
    (2)、第二种是使用 “from 模块名 import 程序单元” 来导入指定程序单元,即使要导入的程序单元没有在 __all__ 列表中,也可以导入。
  • 相关阅读:
    java学习之实例变量初始化
    rip中的连续子网以及不连续子网
    扫描工具
    WScript.SendKeys()的sendkeys发送组合键以及特殊字符
    sql 查询包含字符的数量统计
    leetcode题1Two sum 练习
    vs 2015密钥
    前端 边界圆角
    前端 字体样式
    前端 高级选择器 伪类选择器
  • 原文地址:https://www.cnblogs.com/Micro0623/p/11813638.html
Copyright © 2011-2022 走看看