我想以编程方式根据包名列表安装和导入包。对于大多数软件包,这是没有问题的,因为软件包和模块名称是相同的。
但是,PyYAML
包是一个例外,因为它的模块只被调用yaml
,并且可能有更多例外。
这是我用来安装和导入软件包/模块的python函数:
def install_and_import(package):
import importlib
try:
importlib.import_module(package) #needs module name!
except ImportError:
import pip
pip.main(['install', package]) #needs package name
finally:
globals()[package] = importlib.import_module(package)
为此列表中的每个包调用函数['backoff', 'pyyaml']
(从requirements.txt
解析),我得到:
Collecting backoff
Installing collected packages: backoff
Successfully installed backoff-1.4.3
Collecting pyyaml
Installing collected packages: pyyaml
Successfully installed pyyaml-3.12
[...Trackback...]
ModuleNotFoundError: No module named 'pyyaml'
有没有办法,只给出包名称(例如pyyaml
),找出我实际需要导入的模块的名称(例如yaml
)?
答案 0 :(得分:2)
使用distlib
(pip install distlib
)和hacky"猜测"在模块名称(这可以改进,但希望在我必须回到其他东西之前给你我想出的东西!)
import os.path
import sys
import distlib.database
def to_module(s):
parts = os.path.splitext(s)[0].split(os.sep)
if s.endswith('.py'):
if parts[-1] == '__init__':
parts.pop()
elif s.endswith('.so'):
parts[-1], _, _ = parts[-1].partition('.')
return '.'.join(parts)
def main():
dp = distlib.database.DistributionPath()
dist = dp.get_distribution(sys.argv[1])
for f, _, _ in dist.list_installed_files():
if f.endswith(('.py', '.so')):
print(to_module(f))
if __name__ == '__main__':
exit(main())
to_module
非常自我解释,我使用DistributionPath()
(表示"已安装"模块)来查询已安装的特定包。从那里我列出了文件,如果它们看起来像模块将它们转换为模块。请注意,这不会捕捉six
(动态添加six.moves
模块)之类的内容,但它是一个非常好的一阶近似值。
我也在这里对posix做出假设,对于你想要调整的其他平台(例如我认为会使用.pyd
的窗口。)
示例输出:
$ python test.py pyyaml
_yaml
yaml
yaml.composer
yaml.constructor
yaml.cyaml
yaml.dumper
yaml.emitter
yaml.error
yaml.events
yaml.loader
yaml.nodes
yaml.parser
yaml.reader
yaml.representer
yaml.resolver
yaml.scanner
yaml.serializer
yaml.tokens
$ python test.py coverage
coverage.pickle2json
coverage.execfile
coverage.python
coverage.summary
coverage.html
coverage.plugin
coverage.pytracer
coverage.config
coverage.__main__
coverage.data
coverage.debug
coverage.annotate
coverage.backward
coverage.parser
coverage.misc
coverage.files
coverage.multiproc
coverage.backunittest
coverage.env
coverage
coverage.control
coverage.cmdline
coverage.results
coverage.version
coverage.plugin_support
coverage.templite
coverage.collector
coverage.xmlreport
coverage.report
coverage.phystokens
coverage.bytecode
coverage.tracer
coverage.fullcoverage.encodings
答案 1 :(得分:0)
基于Anthony Sottile的excellent answer,我创建了一个简化版本,从包中提供一个模块。我的情况的大多数包都有一个主要模块。 (当然,使用多个“主”模块处理更复杂的包会很好。)
在 Windows上进行测试,我发现了.list_installed_files()
的一些问题(其中一些问题在此“解决方案”中得到了解决):
这将搜索第一个__init__.py
以通知模块名称。如果找不到,它只返回包名(包含90%的情况)。
def package_to_module(package):
dp = distlib.database.DistributionPath(include_egg=True)
dist = dp.get_distribution(package)
if dist is None:
raise ModuleNotFoundError
module = package # until we figure out something better
for filename, _, _ in dist.list_installed_files():
if filename.endswith(('.py')):
parts = os.path.splitext(filename)[0].split(os.sep)
if len(parts) == 1: # windows sep varies with distribution type
parts = os.path.splitext(filename)[0].split('/')
if parts[-1].startswith('_') and not parts[-1].startswith('__'):
continue # ignore internals
elif filename.endswith('.py') and parts[-1] == '__init__':
module = parts[-2]
break
return module
一些例子:
>>> package_to_module("pyyaml")
'yaml'
>>> package_to_module("click")
'click'
>>> package_to_module("six")
'six'
>>> package_to_module("pip")
'pip'
>>> package_to_module("doesntexist")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in package_to_module
ModuleNotFoundError