我使用python的setuptools一直有一个奇怪的150ms启动惩罚,我构建了一个最小的测试用例,问题仍然存在于那里:
这个最小案例的项目布局是:
- setup.py
- setuptest
- - __init__.py
- - __main__.py
setup.py文件包含:
from setuptools import setup
setup(
name = 'setuptest',
version = '0.1',
packages = ['setuptest'],
entry_points = {
'console_scripts' : ['setuptest = setuptest.__main__:main']
} ,
)
__main__.py文件只包含:
#!/usr/bin/env python2
def main ():
print "hai"
if __name__ == '__main__':
main()
在项目根目录中执行此操作:
—— — time python2 setuptest
hai
real 0m0.021s
user 0m0.017s
sys 0m0.004s
然而,在运行sudo python2 setup.py install并执行以下操作后,我总共执行了21 ms的脚本:
—— — time setuptest
hai
real 0m0.158s
user 0m0.144s
sys 0m0.012s
—— —
给我158ms.这个150s的启动延迟时间是一致的,当我使用setuptools时会发生这种情况,但是我没有通过包管理器安装的东西或者手动安装其他人的项目,这让我觉得我显然做了非常错误的事情.
解决方法:
好吧,当你使用setuptools安装软件时,它会在bin目录中生成可执行脚本,如下所示:
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.exit(
load_entry_point('<PACKAGE_NAME>', 'console_scripts', '<ENTRY_POINT>')()
)
因为load_entry_point()将解析sys.path中可用的所有包,所以安装的位置和包越多,构建列表所需的时间越长,然后查找它.
有关更多详细信息,我们需要查看setuptools的load_entry_point()实现:
从setuptools.py:load_entry_point()
:
def load_entry_point(dist, group, name):
"""Return `name` entry point of `group` for `dist` or raise ImportError"""
return get_distribution(dist).load_entry_point(group, name)
从‘setuptools.py:get_distribution()’开始:
def get_distribution(dist):
"""Return a current distribution object for a Requirement or string"""
if isinstance(dist,basestring): dist = Requirement.parse(dist)
if isinstance(dist,Requirement): dist = get_provider(dist)
if not isinstance(dist,Distribution):
raise TypeError("Expected string, Requirement, or Distribution", dist)
return dist
从setuptools.py:Distribution.load_entry_point()
:
def load_entry_point(self, group, name):
"""Return the `name` entry point of `group` or raise ImportError"""
ep = self.get_entry_info(group,name)
if ep is None:
raise ImportError("Entry point %r not found" % ((group,name),))
return ep.load()
从setuptools.py:Distribution.get_entry_info()
:
def get_entry_info(self, group, name):
"""Return the EntryPoint object for `group`+`name`, or ``None``"""
return self.get_entry_map(group).get(name)
我会把它留在那里,你可以跟进它变得昂贵的地方.我想Distribution
中完成映射的方法(如_dep_map
属性)在执行时可能非常昂贵.