跳转至

Python模块

约 2458 个字 123 行代码 预计阅读时间 10 分钟

模块

在退出Python的命令行解释器后,再次进入时,之前在 Python 解释器中定义的函数和变量就丢失了。因此,编写较长程序时,最好用文本编辑器代替解释器,执行文件中的输入内容,这就是编写脚本。随着程序越来越长,为了方便维护,最好把脚本拆分成多个文件。编写脚本还一个好处,不同程序调用同一个函数时,不用把函数定义复制到各个程序

为实现这些需求,Python 把各种定义存入一个文件,在脚本或解释器的交互式实例中使用。这个文件就是模块,也就是说每一个.py文件就属于一个模块,所以引入模块就相当于引入.py文件,在Python文件中,引入.py文件可以使用import关键字

例如下面的代码:

Python
1
import test_module

注意,当在函数内部使用import导入模块或者定义新的函数时,这些新引入的名字(导入的模块名、函数名等)默认情况下也是在局部作用域中定义的。这意味着它们仅在当前函数内可见

Python
1
2
3
4
def my_function():
    import math  # math 是局部于 my_function 的
    def inner(): pass  # inner 函数也是局部于 my_function 的
    ...

模块包含可执行语句及函数定义。这些语句用于初始化模块,且仅在import语句第一次遇到模块名时执行,也就是说,如果一个模块被导入,那么这个模块中所有的代码在当前文件执行时遇到import关键字都会被执行一遍,例如下面的代码:

Python
1
2
3
4
5
# test_module.py中
print("导入时就被执行了")

# test1.py中
import test_module

在上面的代码中,在test1.py文件下运行,就可以看到尽管test1.py文件没有任何内容,但是因为test_module.py文件有一句打印语句,所以最后的输出结果应该是:

Python
1
导入时就被执行了

如果需要为引入的模块取别名,可以使用as关键字,有了别名之后,在当前文件就只需要使用别名访问对应模块中的内容即可,例如下面的代码:

Python
1
2
# 将test_module取别名为tm
import test_module as tm

在Python中,每个模块都有自己的私有命名空间,它会被用作模块中定义的所有函数的全局命名空间。因此,模块作者可以在模块内使用全局变量而不必担心与用户的全局变量发生意外冲突,例如下面的代码:

Python
1
2
3
4
5
6
7
8
# test_module中的变量
a = 10

# test中的变量a
a = 20
print(a) # 20
# test_module中的变量a
print(test_module.a) # 10

在一个文件中如果想访问已经导入的文件中的内容时,可以使用模块名.模块内容的方式引入已经导入的文件中的内容,例如上面代码中的test_module.a就是访问test_module中的变量a

如果模块中有多个内容,但是又只想在当前文件中引入指定的内容,就可以使用from...import...语句,from后面跟着模块名或者包名: 1. 如果from后面跟着模块名,则import后面跟着就是模块中具体内容的名称(不需要指定模块名)。使用这种方式引入的内容在使用时不再需要指定包名,所以如果当前文件有重名内容会产生覆盖 2. 如果from后面跟着包名,则import后面跟着就是具体的模块名,这个过程也被称为引包(关于包后面会讲解,现在先提出这个步骤)

Note

如果使用第一种方式引入模块中的内容,则import关键字后面可以加上*表示引入指定模块中的所有不以下划线_开头的名称,但是不要用这个功能,这种方式向解释器导入了一批未知的名称,可能会覆盖已经定义的名称,并且这项操作经常让代码变得难以理解

例如下面的代码:

Python
1
2
3
4
5
6
from test_module import a, add

# 直接访问test_module中的变量a
print(a) # 10
# 直接访问test_module中的函数add
print(add(10, 20)) # 30

如果需要为导入的内容取别名,则可以在对应的内容后面使用as为其取别名,例如下面的代码:

Python
1
2
3
4
5
6
from test_module import a as value, add as func

# 直接访问test_module中的变量a(别名为value)
print(value) # 10
# 直接访问test_module中的函数add(别名为func)
print(func(10, 20)) # 30

主函数

Note

本部分可以参考官方文档:以脚本方式执行模块

在Python中,主函数(通常称为 main 函数)并不是一个内置的概念,不像C或C++那样有一个明确的 main 函数作为程序的入口点。然而,Python程序员通常会定义一个名为 main 的函数来组织和执行主要的逻辑代码,并使用 if __name__ == "__main__": 语句来确保该函数只在脚本直接运行时被调用(相当于PyCharm右键直接运行)

主函数通常是一个普通函数,名称可以是 main 或其他任何名称。这个函数包含了程序的主要逻辑

Python
1
2
3
def main():
    print("This is the main function.")
    # 其他主要逻辑代码

为了确保 main 函数只在脚本直接运行时被调用,而不是在模块被导入时被调用,可以使用 if __name__ == "__main__": 语句。

Python
1
2
3
4
5
6
def main():
    print("This is the main function.")
    # 其他主要逻辑代码

if __name__ == "__main__":
    main()

使用这种方式的优点:

  1. 模块重用:当你的脚本作为一个模块被导入到其他脚本中时,if __name__ == "__main__": 语句下的代码不会被执行。这样可以避免不必要的代码执行
  2. 测试和示例:你可以在 if __name__ == "__main__": 语句下编写一些测试代码或示例代码,这些代码只有在直接运行脚本时才会执行
  3. 清晰性:将主要逻辑封装在一个 main 函数中,并通过 if __name__ == "__main__": 调用它,可以使代码结构更清晰,便于维护

以下是一个完整的示例,展示了如何定义和使用 main 函数:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def greet(name):
    print(f"Hello, {name}!")

def main():
    print("Program started.")
    name = input("Enter your name: ")
    greet(name)
    print("Program finished.")

if __name__ == "__main__":
    main()

当直接运行这个脚本(在PyCharm中直接右键当前文件)时,输出将是:

Python
1
2
3
4
Program started.
Enter your name: Alice
Hello, Alice!
Program finished.

如果将这个脚本保存为 my_script.py 并从另一个脚本中导入它,例如:

Python
1
2
3
4
# another_script.py
import my_script

print("Importing my_script")

当运行 another_script.py 时,输出将是:

Python
1
Importing my_script

注意,main 函数中的代码没有被执行,因为 my_script 是作为一个模块被导入的,而不是直接运行的

列出导入模块的内容

在Python中,如果在一个文件中引入了一个模块,则可以在该文件中使用dir(导入的模块名)函数查看指定模块的内容,返回结果是经过排序的字符串列表,例如下面的代码:

Note

如果dir()函数不传递实参,则默认查看到的是当前文件中的内容

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import test_module
import test_module2

print(dir()) # 返回当前模块的所有内容
print(dir(test_module)) # 返回test_module模块的所有内容
print(dir(test_module2)) # 返回test_module2模块的所有内容

输出结果
['__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'test_module', 'test_module2']
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'add', 'b', 'sub']
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'div', 'mul']

注意dir()函数不会列出内置函数和变量的名称。这些内容的定义在标准模块builtins

在Python中,所谓包就是有着__init__.py文件的文件夹,而这个__init__.py文件可以为空也可以设置__all__变量,这个文件夹下的所有.py文件就是隶属于当前包的模块

在Python中,可以使用from...import...导入包和指定模块,例如下面的代码:

Python
1
2
# 导入pack包下的test_module模块
from pack import test_module

如果需要使用模块中的内容,有下面几种方式(注意使用其中内容的方式不同):

  1. 导入指定模块,通过模块调用

    Python
    1
    2
    3
    4
    from pack import test_module
    
    # 1. 导入指定模块,通过模块调用
    print(test_module.c) # 100
    
  2. 导入包和模块,引入指定内容

    Python
    1
    2
    3
    from pack.test_module import c
    # 2. 导入指定包中的模块,通过包名.模块名调用
    print(c) # 100
    
  3. 直接引入单个模块

    Python
    1
    2
    3
    import pack.test_module
    # 3. 直接引入单个模块
    print(pack.test_module.c) # 100
    

    Note

    使用import item.subitem.subsubitem句法时,除最后一项外,每个item都必须是包。最后一项可以是模块或包,但不能是上一项中定义的类、函数或变量

注意,前面在模块中提到如果使用import关键字后面跟着的是*就会导入该模块中所有的内容,但是在包这里就不建议这么做,因为包中可能还有大量的模块,直接使用*可能会导致花费很长的时间,并且可能会产生不想要的副作用,如果这种副作用被设计为只有在导入某个特定的子模块时才应该发生

解决上面这个问题的办法就是提供包的显示索引,在__init__.py文件中为变量__all__赋值为一个列表,列表中的元素就是指定的模块名,此时使用import *时就会导入__all__列表中指定的模块

例如下面的代码:

Python
1
2
3
4
5
6
7
8
9
# __init__.py文件
__all__ = ["test_module1"]

# test_module1.py文件
d = 200

# test1.py文件
from pack import *
print(test_module1.d) # 200

需要注意,如果在__init__.py文件中有与某个模块名重名的内容,那么此时再__all__列表中写的模块名就会指向对应的函数而不是模块,例如下面的代码:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# add.py文件
print("导入时就执行")

# __init__.py文件
__all__ = ["test_module1", "add"]

def add(a, b):
    return a + b

# test1.py文件
from pack import *
print(add(10, 20)) # 30

输出结果
30

在上面的代码中,并没有执行到add.py文件中的print("导入时就执行"),说明add.py并没有被导入到test1.py文件,原因就是__init__.py文件中的add函数覆盖了__all__列表中的add模块

包的相对导入

如果包中还有许多子包,则可以考虑使用相对导入的方式

假设当前包的结构如下:

Text Only
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sound/                          最高层级的包
      __init__.py               初始化 sound 包
      formats/                  用于文件格式转换的子包
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  用于音效的子包
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  用于过滤器的子包
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

则可以在surround.py文件中按照下面的方式导入指定的包:

Python
1
2
3
from . import echo # 导入当前包下的echo.py文件
from .. import formats # 导入当前包的上一级路径下的formats包
from ..filters import equalizer # 导入当前包的上一级路径下filters包中的equalizer.py模块

注意,相对导入基于当前模块名。因为主模块名永远是__main__,所以如果计划将一个模块用作Python应用程序的主模块,那么该模块内的导入语句必须始终使用绝对导入

假设当前项目结构如下:

Text Only
1
2
3
4
my_package/
├── __init__.py
├── module_a.py
└── module_b.py

module_a.py 中,有以下相对导入:

Python
1
2
3
4
5
# module_a.py
from . import module_b

def func_a():
    module_b.func_b()

如果直接运行 module_a.py

Bash
1
python module_a.py

会出现错误:

Text Only
1
ImportError: attempted relative import with no known parent package

这是因为当直接运行 module_a.py 时,其 __name__ 等于 '__main__',相对导入无法解析

解决方法:使用绝对导入

修改 module_a.py

Python
1
2
3
4
5
# module_a.py
from my_package import module_b

def func_a():
    module_b.func_b()

这样,无论模块是被导入还是直接运行,绝对导入都能正常工作