一、开篇:看起来一样,实际上不同
很多初学者认为import math和from math import sqrt只是"写法的区别"——一种要多写模块名,一种不用。但实际上,两种方式在命名空间、变量绑定、模块重载等底层行为上有本质区别。
先看一个让人困惑的例子:
# 场景:一个模块中有一个可变的配置变量
# config.py 内容:
# settings = {"debug": false}
# 方式一:import config
import config
cfg1 = config.settings # cfg1引用config模块的settings
# 方式二:from config import settings
from config import settings as cfg2 # cfg2也是settings
# 修改源模块中的值
config.settings = {"debug": true, "new_key": "value"}
# 检查结果
print(cfg1) # {'debug': true, 'new_key': 'value'} ← 看到了新值!
print(cfg2) # {'debug': false} ← 还是旧值!
# 这就是 from import 的关键陷阱:
# from import 导入的是"那一刻的值引用",不会跟随源模块更新
这篇文章,我们深入探讨import和from import的底层差异、常见陷阱以及选型决策。
二、命名空间的本质区别
2.1 import导入了什么
# import 模块名 # 导入的是整个模块对象,在当前命名空间中创建一个指向该模块的变量 import math # math 是当前模块中的一个变量,指向math模块对象 print(math) # <module 'math' (built-in)> print(type(math)) # <class 'module'> # 通过math访问模块中的一切 print(math.pi) print(math.sqrt) # import 导入了模块的"命名空间" # 不会把模块内部的名称暴露到当前命名空间 # 所以不会有命名冲突
2.2 from import导入了什么
# from 模块名 import 名称 # 导入的是特定名称,在当前命名空间中创建指向该名称的变量 from math import sqrt, pi # sqrt和pi是当前模块中的变量 print(sqrt) # <built-in function sqrt> print(pi) # 3.141592653589793 # math本身没有被导入 # print(math) # nameerror: name 'math' is not defined # 💡 关键理解: # from math import sqrt 本质上做了: # import math # 1. 导入整个模块 # sqrt = math.sqrt # 2. 创建局部变量sqrt,指向math.sqrt # del math # 3. 删除math模块的引用(但模块仍在sys.modules中)
2.3 内存角度对比
import sys
# 两种方式的内存占用
# import 模块 —— 导入整个模块对象(包含所有函数、类、变量)
import math
print(sys.getsizeof(math)) # 模块对象本身很小
# from 模块 import 名称 —— 同样导入了整个模块!
from math import sqrt
# math模块仍然被加载到了sys.modules中
print("math" in sys.modules) # true
# 只是当前命名空间中没有math这个变量名而已
# 💡 from import 不会省内存!
# 无论哪种导入方式,整个模块都会被加载到内存
# 区别只是当前命名空间中有什么名字而已
三、变量绑定的经典陷阱
3.1 from import 是"快照"而非"引用"
# 模拟两个模块来演示陷阱
# ===== counter.py =====
"""
count = 0
def increment():
global count
count += 1
return count
"""
# ===== test_import.py =====
# import counter # 方式一
# from counter import count, increment # 方式二
# 创建临时文件来演示
import tempfile, os
# 创建counter模块
counter_code = '''
count = 0
def increment():
global count
count += 1
return count
'''
# 省略文件创建细节——实际使用时这样做:
# 1. 创建 counter.py 包含上述代码
# 2. 在另一个文件中测试:
# import counter
# print(counter.count) # 0
# print(counter.increment()) # 1
# print(counter.count) # 1 —— 正确!
# from counter import count, increment
# print(count) # 0
# print(increment()) # 1
# print(count) # 0 —— 错误!count没有变化!
# 因为 from import 的count只是导入时的值(整数0)的副本
# 而模块中的count已经变成了1
3.2 可变对象的特殊情况
# 如果导入的是可变对象(list, dict等),修改其内容
# 两种方式都能看到变化——因为修改的是同一个对象
# ===== data_module.py =====
"""
items = [] # 列表——可变对象
def add_item(item):
items.append(item)
"""
# import data_module
# data_module.add_item("a")
# print(data_module.items) # ['a']
# from data_module import items, add_item
# add_item("b")
# print(items) # ['b'] —— 看到了!
# 因为items指向的是同一个列表对象
# 💡 但如果是给变量重新赋值:
# data_module.items = ["new"] # 修改模块中的引用
# 用from import的items变量仍然指向原来的列表
四、模块重载时的行为差异
4.1 reload对两种导入方式的影响
import importlib # 假设有一个模块 undergoing_development.py # 你在开发中频繁修改它,希望重新加载 # 方式一:import 模块 import undergoing_development as ud ud = importlib.reload(ud) # ✅ ud现在指向重新加载后的模块 # 之后通过ud.xxx访问的都是新版本的 # 方式二:from import from undergoing_development import my_function # importlib.reload(undergoing_development) 没有用 # 因为my_function仍然指向旧版本的函数对象! # 💡 开发建议: # 如果你需要频繁重载模块(调试、开发阶段),用 import 模块名 # 不要用 from import
五、循环导入时的差异
# 循环导入时,两种方式的表现不同
# ===== module_a.py =====
# print("a开始")
# from module_b import func_b # 可能报错!因为b还没完全加载
# def func_a():
# return "a"
# ===== module_b.py =====
# print("b开始")
# import module_a # 使用 import(而不是from import)
# def func_b():
# return module_a.func_a() # 延迟访问:在调用时才查找
# ✗ from import:在导入时就需要名称存在 → 更容易报错
# ✓ import 模块名:在访问时才查找名称 → 对循环导入更宽容
# ✅ 解决循环导入的方法:
# 1. 把import放在函数内部(延迟导入)
# 2. 重构——提取公共依赖
# 3. 使用 import 模块名 而不是 from import
六、选型决策
使用 import 模块名 的情况:
- 需要使用模块中的多个功能
- 模块名不长
- 需要修改模块级变量并看到最新值
- 需要重新加载模块
- 存在循环导入的风险
使用 from import 的情况:
- 只需要模块中的1-2个特定功能
- 被导入的名称不会在模块中被修改
- 需要代码简洁(如from collections import defaultdict)
- 导入子模块(from os import path)
一个实用的折中方案:
如果你想要简洁但又不想丢失命名空间信息:
from collections import defaultdict as dd # 保留原名语义
但不推荐过度使用别名
七、总结
import和from import的核心区别不在于功能(功能上都能完成任务),而在于变量绑定方式和命名空间管理。
核心要点:
import x→ 导入了模块对象,通过x.name访问,总是看到最新值from x import name→ 导入了值的引用,如果源模块重新赋值,这里不会更新- 两种方式的内存占用相同——整个模块都会被加载
- 默认选择
import 模块名——更安全、更清晰 - 只在确实需要时使用
from import——而且只导入少量名称
记住:from import 复制的是"那一刻的指针值",不是"对源模块变量的实时引用"。这是所有陷阱的根源。
到此这篇关于python基础指南之from-import与import的区别和常见陷阱详解的文章就介绍到这了,更多相关python from-import与import内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论