如何优化带有复杂装饰器的Python模块pySFeel的导入耗时?
优化pySFeel模块导入耗时的方案
问题背景
我开发的pyDMNrules模块导入耗时达15秒,排查后发现核心耗时来自依赖的pySFeel模块(导入耗时14秒),而pySFeel依赖的sly模块导入仅需0.218秒:
$ time python3 -c "import pyDMNrules" real 0m14.625s user 0m0.000s sys 0m0.000s
$ time python3 -c "import pySFeel" real 0m13.555s user 0m0.000s sys 0m0.000s
$ time python3 -c "import sly" real 0m0.218s user 0m0.000s sys 0m0.000s
pySFeel是FEEL语言的编译/解释器,共6200行代码,包含大量小型函数与复杂装饰器,导入时会构建词法分析器和语法分析器,涉及大量正则表达式编译,这是导入慢的核心原因。
针对性优化方案
1. 延迟初始化词法/语法分析器
将原本在模块导入时执行的分析器构建逻辑,推迟到第一次实际使用FEEL功能时才执行:
# 优化前(导入时直接初始化) from sly import Lexer, Parser class FEEL_Lexer(Lexer): # 词法规则定义 ... class FEEL_Parser(Parser): # 语法规则定义 ... lexer = FEEL_Lexer() parser = FEEL_Parser() # 优化后(延迟初始化) _lexer = None _parser = None def get_lexer(): global _lexer if _lexer is None: from sly import Lexer class FEEL_Lexer(Lexer): # 词法规则定义 ... _lexer = FEEL_Lexer() return _lexer def get_parser(): global _parser if _parser is None: from sly import Parser class FEEL_Parser(Parser): # 语法规则定义 ... _parser = FEEL_Parser() return _parser
模块导入时仅加载基础代码,不会触发正则编译和分析器初始化,把耗时操作延后到实际调用阶段。
2. 预编译正则表达式并缓存
如果sly允许手动传入预编译的正则表达式(而非字符串规则),提前编译高频使用的正则并复用:
import re # 预编译常用正则表达式 RE_NUMBER = re.compile(r'\d+(\.\d+)?') RE_IDENTIFIER = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*') class FEEL_Lexer(Lexer): tokens = {NUMBER, IDENTIFIER} # 直接使用预编译的正则 NUMBER = RE_NUMBER IDENTIFIER = RE_IDENTIFIER # 其他规则定义...
避免sly在初始化时重复编译相同正则,减少冗余计算。
3. 拆分大模块结构
将6200行的单模块拆分为多个子模块,比如把词法分析、语法分析、执行逻辑分开,顶层模块仅导出必要API,按需导入子模块:
# pySfeel/__init__.py def evaluate_feel(expression): # 仅在实际调用时加载子模块 from .lexer import get_lexer from .parser import get_parser lexer = get_lexer() parser = get_parser() return parser.parse(lexer.tokenize(expression))
顶层模块导入时不会加载所有子模块代码,大幅降低导入阶段的工作量。
4. 延迟装饰器的耗时计算
如果模块中有大量复杂装饰器在导入时执行逻辑,将装饰器的耗时操作推迟到函数第一次调用时:
# 优化前(导入时执行装饰逻辑) def complex_decorator(func): # 耗时的初始化操作 ... return wrapper # 优化后(延迟到首次调用时执行) def lazy_complex_decorator(func): def wrapper(*args, **kwargs): if not hasattr(wrapper, '_initialized'): # 首次调用时才执行耗时逻辑 ... setattr(wrapper, '_initialized', True) return func(*args, **kwargs) return wrapper
避免装饰器在模块导入阶段就占用大量时间。
5. 用工具定位精准瓶颈
使用py-spy或cProfile找出导入时的具体耗时热点:
# 用py-spy生成导入过程的火焰图 py-spy record -- python3 -c "import pySFeel"
通过火焰图可以精准定位到哪些正则编译、装饰器或类初始化是耗时元凶,针对性优化。
内容的提问来源于stack exchange,提问作者Russell McDonell
相关产品推荐
相关产品推荐

