您可以通过升级到Python 3.3或替换标准的导入系统以避免这种情况。在我两周前在PyOhio的
strace
演讲中,我讨论了旧的导入机制的不幸的O(nm)性能(对于n个目录和m个可能的后缀),请从
这张幻灯片开始。我展示了如何使用
easy_install
加上Zope-powered web框架仅产生73,477个系统调用,就足以进行足够的导入并运行。
例如,在我的笔记本电脑上快速安装
bottle
虚拟环境后,我发现仅需要1,000次调用才能使Python导入该模块并运行。
$ strace -c -e stat64,open python -c 'import bottle'
% time seconds usecs/call calls errors syscall
100.00 0.000179 0 1519 1355 open
0.00 0.000000 0 475 363 stat64
100.00 0.000179 1994 1718 total
然而,如果我跳转到os.py
文件中,我可以添加一个缓存导入器,即使是使用非常简单的实现,也可以将未命中次数减少近千次:
$ strace -c -e stat64,open python -c 'import bottle'
% time seconds usecs/call calls errors syscall
100.00 0.000041 0 699 581 open
0.00 0.000000 0 301 189 stat64
100.00 0.000041 1000 770 total
我选择研究os.py
模块,因为使用strace
可以看到它是Python导入的第一个模块,而且我们越早安装自己的导入器,Python就能少导入一些标准库模块,避免使用原来那个缓慢可怕的机制。
class CachingImporter(object):
def __init__(self):
self.directory_listings = {}
def find_module(self, fullname, other_path=None):
filename = fullname + '.py'
for syspath in sys.path:
listing = self.directory_listings.get(syspath, None)
if listing is None:
try:
listing = listdir(syspath)
except OSError:
listing = []
self.directory_listings[syspath] = listing
if filename in listing:
modpath = path.join(syspath, filename)
return CachingLoader(modpath)
class CachingLoader(object):
def __init__(self, modpath):
self.modpath = modpath
def load_module(self, fullname):
if fullname in sys.modules:
return sys.modules[fullname]
import imp
mod = imp.new_module(fullname)
mod.__loader__ = self
sys.modules[fullname] = mod
mod.__file__ = self.modpath
with file(self.modpath) as f:
code = f.read()
exec code in mod.__dict__
return mod
sys.meta_path.append(CachingImporter())
当然,这个方法还有一些不足之处——它不会尝试检测
.pyc
文件、
.so
文件或Python可能寻找的其他扩展名。它也不知道
__init__.py
文件或包内部的模块(这需要在
sys.path
条目的子目录中运行
lsdir()
)。但至少它说明了可以通过类似这样的东西消除数千个额外的调用,并演示了您可能尝试的方向。当它找不到一个模块时,正常的导入机制会启动。
我想知道是否已经有一个很好的缓存导入器可在PyPI或其他地方使用?这似乎是各种公司已经写过数百次的东西。我认为Noah Gift曾经写过一个并将其放在博客文章或其他什么地方,但我找不到一个链接来证实我的记忆。
编辑:如
@ncoglan在评论中提到的那样,在PyPI上有一个新的Python 3.3+导入系统的alpha版迁移给Python 2.7使用:
http://pypi.python.org/pypi/importlib2——不幸的是,问答者仍然停留在2.6上。
sys.modules
中缓存的模块对象,因此如果这是查找的本质,可能没有太多可获得的。 - BrenBarnvirtualenv
创建一个独立的虚拟环境怎么样?如果你的库太大,本地驱动器太小,那可能不是一个选项,但如果是的话,那应该完全避免这个问题。 - abarnert