不用再等待慢吞吞的模块加载,也不用继续依赖临时性的绕路写法:python 3.15 新特性 —— lazy imports 机制已经给出了更直接的解法。
我们知道,在 python 中导入模块时,解释器必须先把这个模块的代码完整求值,程序才能继续往下执行。
对大多数模块来说,听起来没什么问题。
但如果某个模块的启动过程很长、很重,那么程序就会在导入它的那一刻被拖慢。
以前,python 开发者通常会通过调整导入位置来绕开这个问题,让导入只在真正需要时才发生。
最常见的做法就是把 import 放进函数体里,而不是写在模块顶层。
不过,这终究是一种不太优雅的权宜之计,也会让程序流程变得更难梳理。
python 3.15 引入了一个新特性:lazy imports。
它为加载缓慢的模块提供了一种更高层次、也更正式的解决方案。
当一次导入被声明为“惰性”时,它不会在第一次导入时立刻求值,而是等到第一次真正被使用时才求值。
这样一来,慢速导入的成本就可以被推迟到真正需要那段代码的时候再支付。
而且,虽然惰性导入带来了新语法,你依然可以提前让现有代码适配它,而不必大面积改写既有的导入语句。
立即与惰性
要理解惰性导入解决了什么问题,最好的方式还是先看一个例子。
开发者应该经常这样写代码,假设同一个目录下有这样两个文件:
# main.py
print("program starting")
from other import some_fn
print("other module imported")
some_fn()
print("program ended")
# other.py
print("other module evaluation started")
from time import sleep
sleep(2)
# ^ this simulates a slow-loading module
print("other module evaluation ended")
def some_fn():
print("some_fn run")
如果运行 main.py,输出大概会是这样:
program starting
other module evaluation started
[two-second delay]
other module evaluation ended
other module imported
some_fn run
program ended
仅仅因为导入了 other,程序就几乎被卡住了。
甚至在我们真正调用导入进来的函数之前,更别说继续执行后续逻辑之前,这段等待就已经发生了。
现在把 main.py 改成使用惰性导入,再看看会发生什么。
这只适用于 python 3.15 或更高版本。
print("program starting")
lazy from other import some_fn
print("other module imported")
some_fn()
print("program ended")
这时再运行程序,行为就会发生变化:
program starting
other module imported
other module evaluation started
[two-second delay]
other module evaluation ended
some_fn run
program ended
现在,导入本身已经不会造成任何延迟。
真正的等待,只有在我们调用那个导入进来的函数时才会出现。
那底层到底做了什么?
当 python 识别到惰性导入时,通常是因为导入语句前面出现了 lazy 关键字,就像上面的例子那样,它不会走平常的导入流程。
取而代之的是,它会为被导入的模块创建一个“代理对象”,也就是一个占位用的替身。
这个代理会一直待命,直到程序真的开始对这个模块做某些操作。
到了那一刻,真正的导入才会触发,模块也才会被求值。
如果你想把某一行导入声明成惰性导入,那么 lazy 必须始终是该行的第一个词:
# lazily imports foo lazy import foo # lazily imports bar from foo lazy from foo import bar # same with the use of "as": lazy import foo as foo1 lazy from foo import bar as bar1
用在哪里
惰性导入最常见的用途,就是替代那些为了避免启动阶段高成本导入而写出来的传统变通方案。
正如前面提到的,把导入写在函数内部而不是模块顶层,的确能让导入只在函数执行时才发生。
但这样做也带来了另一个问题:这个导入只存在于函数作用域内。
因此,除非你再叠加另一层绕路方案,否则模块里的其他代码都没法直接使用它。
而使用惰性导入后,你依然可以把导入保留在模块顶层,就像平常那样写。
你唯一需要做的,只是在代码前面加上 lazy 关键字。
自动启用惰性导入
你还可以在现有代码库里自动启用惰性导入,而不必逐条改写 import 语句。
python 3.15 为 sys 模块新增了一些能力,用来控制惰性导入的行为。
例如,你可以通过编程方式声明:从程序执行的某个时间点开始,之后所有导入都采用惰性方式处理。
import sys
sys.set_lazy_imports("all")
如果给 sys.set_lazy_imports() 传入 "all",那么从那一刻起,程序中的每一次导入都会变成惰性导入,不管它是否显式写了 lazy 关键字。
如果(给该函数)传入 "normal",则只有显式声明的才会惰性导入
如果传入 "none",则彻底关闭惰性导入。
以编程方式控制惰性导入
你也可以在运行时接管惰性导入的行为。
这样一来,你就能够进一步控制到底哪些模块会被惰性导入。
import sys
def mod_filter(importing, imported, fromlist):
return imported == "module"
sys.set_lazy_imports_filter(mod_filter)
sys.set_lazy_imports_filter() 允许你传入一个函数,这个函数会接收三个参数:
- 发起导入的模块
- 被导入的模块
- 被导入名称组成的列表
借助这个函数,你可以写出自己的判断逻辑:返回 true 时,允许某次导入按惰性方式进行;返回 false 时,则强制它按普通方式立即导入。
这样,你既可以在测试里为惰性导入维护允许列表和阻止列表,也可以把这套规则直接纳入程序自身的运行机制。
使用惰性导入的两种方式
python 一直以来都很重视让新特性能平滑进入已有代码库。
惰性导入同样可以按这种方式接入。
你可以在程序启动时先检查这个能力是否存在,然后再通过 sys.set_lazy_imports() 自动把惰性导入应用到整个项目中。
最直接的办法,是先判断 python 版本号:
import sys
if sys.version_info >= (3, 15):
... # set up lazy imports
或者,也可以直接检查 sys 中是否提供了这些惰性导入控制接口:
import sys
if hasattr(sys, "set_lazy_imports"):
... # set up lazy imports
到此这篇关于python实现自动启用惰性导入的实战指南的文章就介绍到这了,更多相关python惰性导入内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论