不关心故事背景的朋友们可以直接跳到“解决方案”~
故事背景
最近在用thrift的时候遇到一个问题,thrift生成的python文件之间的引用都是相对引用的:
thrift_folder
├── base
│ ├── constants.py
│ ├── __init__.py
│ └── ttypes.py
├── model
│ ├── __init__.py
│ ├── constants.py
│ ├── ttypes.py
# model/ttypes.py
import base.ttypes
如果python起始运行文件路径不在thrift_folder内,那么引用就会有错误。
历史方案
老前辈们提供了两种解决方案
- 修改所有的import
- 修改os.path
前者的问题在于,即使用脚本在每个thrift前面写from thrift_folder import base(不能图省事先加一个__all__ = bulabula然后from thrift_folder import *,这样可能会循环引用),在thrift文件里面还是会藏着很多base.ttypes.XXX的鬼东西,需要一个个改……第一位老前辈就一个个改完了。当然可能他的thrift很少,我现在看着几十万行的thrift真的改不了= =
后者的问题在于os.path可能失效,这是为啥呢,请看下文分解——
解决方案
坑1:sys.path不能用append
在查找modules的位置时,python按照sys.path顺次查询,如果你用了append,很可能引用到了其他的模块。那么解决方案是
sys.path.insert(0,thrift_folder)
坑2:sys.modules缓存路径(insert0还是不行啊喂)
python在import module前,先会去sys.modules看看曾经有没有导入过这个模块,这是一个类似于modules path cache的地方。模块在sys.modules中不代表当前文件能调用它(可能是其他文件的代码中import了,然后把sys.modules更新了);但是调用新模块的时候会优先查找sys.modules有没有,有的话就不管sys.path了,直接从这里导入… 【想了好久也没懂这个机制可能快一丢丢之外有什么用途】
于是需要查看一下sys.modules有没有曾经导入模块留下的痕迹,如果有的话需要把它们都解决掉,这样就可以正常引用啦~ 当然,成功导入thrift之后别忘记把原来的sys.modules/sys.path还原。
# 如果在该模块导入之前有过import lv_thrift下的子模块同名模块,会导致从
# sys.modules中加载路径,所以同时对sys.modules进行修改
import_modules = [ 'base', 'model' ...]
import_modules += [s+'.ttypes' for s in import_modules]
sys_modules_backup = dict()
for mod in import_modules:
if sys.modules.has_key(mod):
sys_modules_backup[mod] = sys.modules[mod]
del sys.modules[mod]
full_path = os.path.realpath(__file__)
thrift_path = os.path.join(os.path.dirname(full_path),"thrift_folder")
sys.path.insert(0,thrift_path)
# import thrift
from base import xxx
from model import yyy
from model.ttypes import zzz
...
# 恢复sys.path & sys.modules
sys.path.remove(thrift_path)
for mod,path in sys_modules_backup.items():
sys.modules[mod] = path

本文介绍了在使用thrift时遇到的Python import问题,由于thrift生成的Python文件采用相对引用,当起始运行文件路径不在thrift_folder内时,会导致引用错误。文章探讨了修改所有import和修改os.path的两种历史解决方案及其存在的问题,并重点讲解了如何避免sys.path.append的陷阱以及处理sys.modules缓存导致的导入问题,提供了解决此类问题的详细步骤。
2146





