R / Richie全部文章 ↑

Python · 4 分钟阅读

`import` 语句

目录


import 是 Python 用来引入模块 / 包的语句:让其它文件里定义的函数、
类、变量在当前命名空间可用。本章覆盖 import 的所有写法、模块搜索机制、
__init__.py、包、相对导入、循环引用处理,以及工程实践。


1. 基本用法

1.1 导入整个模块

import math

print(math.sqrt(16))    # 4.0
print(math.pi)

推荐:保留模块前缀,让代码里能一眼看出 sqrt 来自哪里。

1.2 导入特定名字

from math import pi, sqrt

print(pi)        # 3.14159...
print(sqrt(16))  # 4.0

适合:高频使用、名字不会冲突。
当心:from xxx import * 会污染命名空间、破坏可读性,几乎总是
坏味道。

1.3 别名

import numpy as np
import pandas as pd
from collections import defaultdict as ddict

as 让长模块名变短,让重名模块共存。


2. 导入一个包

包就是一个包含 __init__.py 的目录。可以嵌套:

myproject/
└── mypkg/
    ├── __init__.py
    ├── utils.py
    └── io/
        ├── __init__.py
        └── reader.py
import mypkg.utils                          # 引入模块
from mypkg.io import reader                 # 引入子包里的模块
from mypkg.io.reader import read_csv        # 直接引入函数

Python 3.3+ 也支持隐式命名空间包(没有 __init__.py 也能 import),
但仍建议每个包都加一个 __init__.py,方便设置包级变量与 __all__。


3. 相对导入

在包内部,用 . 表示“当前包”,.. 表示“上一级包”:

# 在 mypkg/io/reader.py 里:
from . import utils                 # mypkg.io.utils
from .. import config               # mypkg.config
from ..utils import path_join       # mypkg.utils.path_join

相对导入只能在包内模块使用;直接 python reader.py 跑会抛
ImportError: attempted relative import with no known parent package。


4. 模块搜索机制

当你写 import mypkg 时,Python 按以下顺序查找:

  1. 内置模块(sys.builtin_module_names)
  2. sys.path 列表里各目录:
    • 当前脚本所在目录
    • PYTHONPATH 环境变量里的目录
    • 标准库与第三方包的安装目录(site-packages)
import sys
print(sys.path)
print(sys.builtin_module_names)

想知道 xxx 真正从哪加载的?print(xxx.__file__)(标准库和 C 扩展可能是
None)。


5. 何时执行模块代码?

模块第一次被 import 时会自上而下执行整个文件,然后把得到的命名空间
缓存到 sys.modules。后续的 import 不会再执行,直接复用。

# config.py
print("loading config")
DEBUG = True
>>> import config    # 第一次:打印 "loading config"
>>> import config    # 第二次:什么都不做

想让某段代码“只在直接运行本文件时执行”,用:

if __name__ == "__main__":
    main()

6. 循环引用

# a.py
from b import b_func
def a_func(): ...

# b.py
from a import a_func    # 💥 ImportError 或部分加载
def b_func(): ...

解决方案:

  • 重构:把互相依赖的部分抽到第三个模块 c.py。
  • 延迟导入:在函数内部再 import,运行期才解析:
    def b_func():
        from a import a_func   # 用时再 import
        ...
    
  • 顶层 import 类型注解用 from __future__ import annotations(注解变成
    字符串,运行时才解析),可以缓解部分问题。

7. 条件导入 / 可选依赖

try:
    import ujson as json    # 更快
except ImportError:
    import json             # 退回标准库

选 ujson / orjson / simplejson 时常用这招;注意:标准库 json 已经
很快,第三方一般只在批量解析时才有明显收益。


8. 重新加载(开发调试)

import importlib
import mymodule
importlib.reload(mymodule)

生产环境不需要;交互式 REPL / Jupyter 里偶尔有用。


9. 性能:避免 import 时的重活

  • 重活放函数里而不是模块顶层。

  • 循环里 import 是大忌——每次都会查 sys.modules、建栈帧。

  • __all__ 列出公共 API:

    # mypkg/__init__.py
    from .utils import *
    __all__ = ["path_join", "load_yaml"]   # 限定 `from mypkg import *`
    

10. 最佳实践清单

  • ✅ 保留模块前缀(import math 而非 from math import *)。
  • ✅ 同名长名加别名(numpy as np)。
  • ✅ 集中 import 在文件顶部,避免在函数里 import(除非为了解决循环引用 / 可选依赖)。
  • ✅ 写库时用 from . import sibling 等相对导入;
    写应用时用绝对导入。
  • ✅ 给包加 __init__.py,哪怕是空的。
  • ✅ 写 pyproject.toml / requirements.txt,别让 import 错误靠 PYTHONPATH 救场。
  • ❌ 不要 from xxx import *。
  • ❌ 不要在 __init__.py 里做“启动服务、连接数据库、读全局配置”这类重活。

11. 小结

  • import 是 Python 的模块系统入口;模块 / 包 / 命名空间包的层级都靠
    __init__.py 与 sys.path 支撑。
  • 优先绝对导入;包内用相对导入。
  • 循环引用就用抽取 + 延迟导入。
  • 工程上把依赖固化到 pyproject.toml / requirements.txt,不要依赖环境变量
    PYTHONPATH 来运行。