飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

模块导入

模块是包含 Python 定义和语句的 .py 文件。当程序规模增长时,将代码拆分到不同模块中可以避免重复编写,也便于维护。模块中的函数、类和变量可以通过 import 机制被其他脚本复用。

import 语句的多种形式

最基本的导入方式是 import 模块名,它把模块对象绑定到当前命名空间的一个变量上,模块内部的所有名称都保留在该模块自己的命名空间中:

# 假设 mathdemo.py 中定义了 add 和 PI
import mathdemo
print(mathdemo.PI)        # 3.14159
print(mathdemo.add(2, 3)) # 5

这种写法的好处是命名空间隔离——模块中的全局变量不会与用户代码中的同名变量冲突。模块作者可以放心使用 PI、add 这类常见名称,因为调用者必须通过 mathdemo.PI 才能访问到它。

如果某个函数使用频繁,可以把它赋值给局部变量,但这只是创建了一个指向原函数的引用,并不会复制函数本身:

from mathdemo import add
my_add = add
my_add(10, 20)  # 30

from...import 与命名空间注入

from 模块 import 名称 的语义是:找到指定模块,执行其顶层语句(如果是首次导入),然后把被导入的名称直接放入当前模块的命名空间。此后无需前缀即可使用:

from mathdemo import add, PI
print(add(1, PI))  # 4.14159

注意,这种写法不会把模块名本身引入当前命名空间。下面的代码会报错:

from mathdemo import add
print(mathdemo.add(2, 3))  # NameError: name 'mathdemo' is not defined

from 模块 import * 会导入模块中所有不以下划线开头的名称。它本质上是一种批量 from...import,但风险在于可能覆盖当前命名空间中已有的同名对象:

# 当前模块已定义了自己的 add 函数
def add(a, b, c):
    return a + b + c

from mathdemo import *   # mathdemo.add 覆盖了上面的 add
print(add(1, 2))         # TypeError: add() missing 1 required positional argument

因此,在生产代码中应避免使用 import *,除非在交互式会话中为了少敲几次键盘。

as 别名:解决名称冲突与简化书写

当模块名过长,或者两个模块提供了同名对象时,as 子句可以创建别名。import ... as 给模块本身起别名:

import json
import mypackage.serializer.json as json_compat  # 与标准库 json 同名时避免冲突

data = json.loads('{"a": 1}')
legacy = json_compat.parse_old_format('a=1')  # 调用自定义模块

from ... import ... as 则给导入的单个对象起别名:

from mathdemo import add as math_add
from string import capwords as title_case

print(math_add(5, 7))           # 12
print(title_case("hello world")) # "Hello World"

别名在科学计算领域尤为常见,例如 import numpy as np、import pandas as pd,这已经成为社区约定俗成的写法。

导入的执行机制与缓存

模块中的可执行语句(包括函数定义、类定义和顶层表达式)在首次遇到 import 时执行一次。函数定义的执行效果是把函数名加入模块的全局命名空间;类定义同理。如果同一个模块被再次导入,Python 会直接返回已缓存的模块对象,不会重新执行文件内容:

# counter.py
print("counter 模块被加载了")
count = 0
def increment():
    global count
    count += 1
    return count

在交互式会话中测试:

>>> import counter
counter 模块被加载了
>>> import counter        # 再次导入,无输出
>>> counter.increment()
1
>>> counter.increment()
2

这种单次执行的设计保证了模块级状态(如全局变量 count)在多次导入间保持一致。但也带来一个问题:如果你在编辑器中修改了 counter.py,已运行的解释器不会自动感知变化。此时需要重启解释器,或者在交互式会话中使用 importlib.reload:

>>> import importlib
>>> importlib.reload(counter)
counter 模块被加载了
<module 'counter' from 'counter.py'>

reload 会重新执行模块代码,但有一个重要细节:已经通过 from counter import increment 获取到 increment 引用的代码,仍然指向旧函数对象;只有通过 counter.increment 访问的调用方才会使用新定义。

name 与双用途模块

每个模块都有一个全局变量 __name__,其值为模块名字符串。当文件被直接作为脚本运行时,__name__ 被特殊地设置为 "__main__";而被导入时,__name__ 就是文件名去掉 .py 后缀后的名称。利用这一性质,可以在模块末尾放置测试代码或命令行接口,使其仅在直接运行时生效:

# fibo.py
def fib(n):
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a + b
    print()

def fib2(n):
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a + b
    return result

if __name__ == "__main__":
    import sys
    # 命令行参数:python fibo.py 50
    fib(int(sys.argv[1]))

直接运行 python fibo.py 50 会打印斐波那契数列;而 import fibo 时测试代码不会执行。这种模式也常用于单元测试:把测试套件写在 if __name__ == "__main__": 块里,既不影响导入,又能直接运行文件做自测。

模块间的交叉导入

模块可以导入其他模块。按照惯例,import 语句放在文件开头,但这并非强制要求。如果模块 A 导入了模块 B,那么 B 的名称会被加入 A 的全局命名空间:

# a.py
import b
print(b.B_VAR)  # 访问 b 模块中的全局变量

# b.py
B_VAR = 100
import a        # 如果 a.py 又 import b,不会无限循环,因为 b 正在初始化中

循环导入(A 导入 B,B 又导入 A)不会导致无限递归,因为 Python 的导入系统会在模块开始加载时就将其放入 sys.modules 缓存。但如果在循环中访问尚未完成初始化的属性,可能遇到 AttributeError。解决循环导入的最佳方案是重构代码,把共享定义移到第三个模块中。

导入失败的情况

当模块不存在或存在语法错误时,导入会抛出异常。ModuleNotFoundError 表示找不到模块;SyntaxError 或 ImportError 表示找到文件但无法正确加载:

>>> import nonexistent
ModuleNotFoundError: No module named 'nonexistent'

>>> from mathdemo import subtract
ImportError: cannot import name 'subtract' from 'mathdemo'

如果模块文件存在但名称拼写错误(如 MathDemo 与 mathdemo),Python 同样视为模块不存在,因为模块名区分大小写。

总结

import 语句的核心是命名空间管理:import mod 把模块对象引入当前命名空间,保持隔离;from mod import name 把具体名称注入当前命名空间,方便但需警惕覆盖;as 解决冲突和长名问题;importlib.reload 支持开发时的热重载;__name__ 让模块同时具备可导入性和可执行性。理解这些机制,才能写出既模块化又安全的 Python 代码。

下一页
模块搜索路径