模块

什么是模块?#

在本章以前,我们一直将编写的代码放在一个 .py 源文件文件中,但已经尝试过使用 Python 的内建模块,如 mathrandom。随着代码规模的增大,将不可避免地需要将代码放在多个 .py 源文件中,这能方便对代码的组织管理,这时就要使用模块的概念。

模块(Module)就是以 .py 结尾的 Python 源文件,它包含定义(变量、函数和类)和语句。模块是 Python 代码的组织单元,它实际上也是一种对象。模块对应一个名字空间,其中包含任意的对象。

创建模块#

要创建模块,只需将模块代码保存在文件扩展名为 .py 的文件中。以下是 say_hello.py 文件内容:


    hello_words = {'en-US': 'Hello!', 'zh-CN': '您好!',
           'zh-TW': '您好!', 'ja-JP': 'こんにちは',
           'nl-NL': 'Hallo', 'ko-KR': '안녕하세요'}

    def hello(lang='zh-CN'):
        print(hello_words[lang])

该文件对应的模块名称为 say_hello。模块命名和变量的命名习惯类似,即采用蛇形命名法。

导入并使用模块#

要在其他文件中使用刚创建的模块,必须先使用 import 语句导入此模块,有多种不同的导入方法。

最常规的导入方式是 import module_name,该语句会执行模块中的语句,进一步可以 module_name.name 的方式引用模块中的名称。如下为与 say_hello.py 同在一个目录下的 main.py 文件的内容:


    import say_hello

    say_hello.hello_words['it-IT'] = 'Ciao'
    say_hello.hello('nl-NL')  # Hallo
    say_hello.hello('it-IT')  # Ciao

在导入模块时,还可以在模块名后使用 import module_name as alias 为模块指定别名:


    import say_hello as say

    say.hello_words['it-IT'] = 'Ciao'
    say.hello('nl-NL')  # Hallo
    say.hello('it-IT')  # Ciao

可以使用 from module_name import name1, name2 的形式只导入模块中的特定名称到当前命名空间(namespace)。如果要同时导入多个名称,各项之间以逗号分割。如下所示:


    from say_hello import hello_words, hello

    hello_words['it-IT'] = 'Ciao'
    hello('nl-NL')  # Hallo
    hello('it-IT')  # Ciao

这时使用这些名称时不需要也不能使用前导的 module_name.,即应该用 hello('nl-NL') 而不是 say_hello.hello('nl-NL')

也可以使用 from module_name import * 的形式直接将模块中所有的公开名称(不以下划线 _ 开头的名称)导入到当前命名空间:


    from say_hello import *

    hello_words['it-IT'] = 'Ciao'
    hello('nl-NL')  # Hallo
    hello('it-IT')  # Ciao

但这种方式常常会导入大量的未预料的名称,这些名称甚至会遮蔽已有的名称,因此通常不建议使用

还可以使用 from module_name import name as alias 的形式为特定导入名称指定别名:


    from say_hello import hello as hi

    hi('nl-NL')  # Hallo

模块搜索路径#

当导入一个模块 module_name 时,会按如下顺序寻找该模块:

  • 检查该名称是否为内置模块

  • 如果不是内置模块,会在 sys.path 目录列表中查找此模块,其查找顺序为:

    • 输入脚本的目录(或未指定文件时的当前目录);
    • PYTHONPATH 环境变量所列出的目录
    • 依赖于安装的默认值(按照惯例包括一个 site-packages 目录,由 site 模块处理)。

更多信息,请沿着以上链接进一步学习。

查看模块信息#

① 使用 dir() 函数#

内置的 dir() 函数会返回经过排序的对象属性名称列表,可借此函数查看模块的属性:


    import say_hello

    print(dir())  # 没有实参时,返回当前本地作用域中的名称列表
    # ['__annotations__', '__builtins__', '__cached__',
    # '__doc__', '__file__', '__loader__', '__name__',
    # '__package__', '__spec__', 'say_hello']

    print(dir(say_hello))  # 有实参时,尝试返回该对象的有效属性列表
    # ['__builtins__', '__cached__', '__doc__',
    # '__file__', '__loader__', '__name__',
    # '__package__', '__spec__', 'hello', 'hello_words']

② 模块属性使用示例#

可以进一步使用这些内置的名称,如下所示:


    print(__name__)  # __main__
    print(__package__)  # None

    print(say_hello.__cached__)
    # C:\Users\jin\PycharmProjects\module\__pycache__\say_hello.cpython-310.pyc
    print(say_hello.__doc__)  # None
    print(say_hello.__file__)
    # C:\Users\jin\PycharmProjects\module\say_hello.py
    print(say_hello.__name__)  # say_hello

使用 PyCharm 建立 Python 项目时,其默认生成的 main.py 文件具有以下代码:


    if __name__ == '__main__':
        ...

'__main__' 始终是当前执行模块内置变量 __name__ 的值。此 if 语句的条件确保 main.py 文件对应的 main 模块在被导入到其他模块后,其语句体不会被执行(因为被导入后该模块的 __name__ 变量值是 'main' 而不是 '__main__')。这正是我们想要的,因为我们本不想让 main 模块被其他模块导入和执行,这使得该 if 语句的句体看起来像其他语言的 main() 入口函数。

③ 查看已被加载的模块#

sys 是 Python 中一个比较重要的内建模块,它提供一些和系统相关的变量和函数。其中 sys.modules 变量会存储已经加载的模块:


    import say_hello, sys

    print(sys.modules)
    # {'sys': <module 'sys' (built-in)>,
    # 'builtins': <module 'builtins' (built-in)>,
    # ...
    # 'say_hello': <module 'say_hello' from 'C:\\Users\\jin\\PycharmProjects\\module\\say_hello.py'>}

可以看出 sys.modules 是字典类型,它将模块名称映射到已经被加载的模块。可借此查看某一模块的信息:


    print(sys.modules['say_hello'])
    # <module 'say_hello' from 'C:\\Users\\jin\\PycharmProjects\\module\\say_hello.py'>
模块