查找哪个Python包提供了特定的导入模块

不要感到困惑,有大量关于安装 Python 包、如何导入生成的模块以及列出可用包的问题。但是,如果您没有 pip 样式文件或 Pipenv Pipfile ,--what-provides似乎没有与pip等效选项。此问题与上一个问题类似,但要求提供父包,而不是其他元数据。也就是说,这些其他问题并没有得到很多关注或许多接受的答案 - 例如。如何查找给定模块的 python 包元数据信息。就这样砥砺前行……requirements.txt

举例来说,有两个包(仅举几例)将安装名为的模块serial- 即pyserialserial。因此,假设安装了其中一个软件包,我们可以使用 pip list 找到它:

python3 -m pip list | grep serial

但是,如果包的名称与模块的名称不匹配,或者您只是想找出要安装的包(在旧服务器或开发计算机上工作),就会出现问题。

您可以检查导入模块的路径 - 这可以给您提供线索。但继续这个例子......

>>> import serial

>>> print(serial.__file__)

/usr/lib/python3.6/site-packages/serial/__init__.py

它位于一个serial目录中,但实际上只安装了pyserial ,而不是serial:


> python3 -m pip list | grep serial

pyserial                 3.4

我能想到的最接近的是生成一个可能在依赖子文件上失败的requirements.txtvia (就像我所做的那样),或者通过Pipenvpipreqs ./反向检查依赖关系(这会带来一整套新问题来完成所有设置):


> pipenv graph --reverse

cymysql==0.9.15

ftptool==0.7.1

netifaces==0.10.9

pip==20.2.2

PyQt5-sip==12.8.1

    - PyQt5==5.15.0 [requires: PyQt5-sip>=12.8,<13]

setuptools==50.3.0

wheel==0.35.1

有谁知道我错过了一个简单的解决方案来查找 pip 包提供特定模块的命令?


泛舟湖上清波郎朗
浏览 147回答 2
2回答

互换的青春

使用( 或)packages_distributions()中的函数。例如,在您的情况下, “导入包”的名称是:importlib.metadataimportlib-metadataserialimport importlib.metadata  # or: `import importlib_metadata` importlib.metadata.packages_distributions()['serial']这应该返回一个包含 的列表,它是“分发包pyserial”的名称(应该用于pip -install 的名称)。对于较旧的 Python 版本和/或较旧版本的importlib-metadata...我相信类似以下的内容应该有效:#!/usr/bin/env python3import importlib.utilimport pathlibimport importlib_metadatadef get_distribution(file_name):    result = None    for distribution in importlib_metadata.distributions():        try:            relative = (                pathlib.Path(file_name)                .relative_to(distribution.locate_file(''))            )        except ValueError:            pass        else:            if distribution.files and relative in distribution.files:                result = distribution                break    return resultdef alpha():    file_name = importlib.util.find_spec('serial').origin    distribution = get_distribution(file_name)    print("alpha", distribution.metadata['Name'])def bravo():    import serial    file_name = serial.__file__    distribution = get_distribution(file_name)    print("bravo", distribution.metadata['Name'])if __name__ == '__main__':    alpha()    bravo()这只是一个代码示例,显示如何获取特定模块所属的已安装项目的元数据。重要的是函数get_distribution,它接受文件名作为参数。它可以是模块或包数据的文件名。如果该文件名属于环境中安装的项目(pip install例如通过),则importlib.metadata.Distribution返回该对象。

狐的传说

此问题现已通过 importlib_metadata 库解决。请参阅提供从“Python 包”到“分发包”的映射,特别是“注释 2”处理这个确切的问题。因此,请参阅@sinoroc的评论,您可以使用如下内容找到该包(例如,提供模块“serial”的包“pyserial”):>>> import importlib_metadata>>> print(importlib_metadata.packages_distributions()['serial'])['pyserial']我提出了以下代码(合并了提到的 importlib.util.find_spec 方法,但对返回路径中的 RECORD 文件进行基于 bash 的搜索)。作为“python3 python_find-module-package.py -m [module-name-here] -d”运行,这也将打印调试。关闭“-d”开关即可仅返回包名称(和错误)。TLDR:github 上的代码。#!/usr/bin/python3import sysimport os.pathimport importlib.utilimport importlib_metadataimport pathlibimport subprocessimport argparseparser = argparse.ArgumentParser()parser.add_argument("-m", "--module", help="Find matching package for the specified Python module",                            type=str)#parser.add_argument("-u", "--username", help="Database username",#                           type=str)#parser.add_argument("-p", "--password", help="Database password",#                           type=str)parser.add_argument("-d", "--debug", help="Debug messages are enabled",                            action="store_true")args = parser.parse_args()TESTMODULE='serial'def debugPrint (message="Nothing"):    if args.debug:        print ("[DEBUG] %s" % str(message))class application ():    def __init__(self, argsPassed):        self.argsPassed = argsPassed        debugPrint("Got these arguments:\n%s" % (argsPassed))    def run (self):        #debugPrint("Running with args:\n%s" % (self.argsPassed))        try:            if self.argsPassed.module is not None:                self.moduleName=self.argsPassed.module  #i.e. the module that you're trying to match with a package.            else:                self.moduleName=TESTMODULE                print("[WARN] No module name supplied - defaulting to %s!" % (TESTMODULE))            self.location=importlib.util.find_spec(self.moduleName).origin            debugPrint(self.location)        except:            print("[ERROR] Parsing module name!")            exit(1)        try:            self.getPackage()        except Exception as e:            print ("[ERROR] getPackage failed: %s" % str(e))        try:            distResult=self.getDistribution(self.location)            self.packageStrDist=distResult.metadata['Name']            print(self.packageStrDist)        except Exception as e:            print ("[ERROR] getDistribution failed: %s" % str(e))        debugPrint("Parent package for \"%s\" is: \"%s\"" % (self.moduleName, self.packageStr))        return self.packageStr    def getPackage (self):        locationStr=self.location.split("site-packages/",1)[1]        debugPrint(locationStr)        #serial/__init__.py        locationDir=self.location.split(locationStr,1)[0]        debugPrint(locationDir)        #/usr/lib/python3.6/site-packages        cmd='find \"' + locationDir + '\" -type f -iname \'RECORD\' -printf \'\"%p\"\\n\' | xargs grep \"' + locationStr + '\" -l -Z'        debugPrint(cmd)        #find "/usr/lib/python3.6/site-packages" -type f -iname 'RECORD' -printf '"%p"\n' | xargs grep "serial/__init__.py" -l -Z        #return_code = os.system(cmd)        #return_code = subprocess.run([cmd], stdout=subprocess.PIPE, universal_newlines=True, shell=False)        #findResultAll = return_code.stdout        findResultAll = subprocess.check_output(cmd, shell=True)    # Returns stdout as byte array, null terminated.        findResult = str(findResultAll.decode('ascii').strip().strip('\x00'))        debugPrint(findResult)        #/usr/lib/python3.6/site-packages/pyserial-3.4.dist-info/RECORD        findDir = os.path.split(findResult)        self.packageStr=findDir[0].replace(locationDir,"")        debugPrint(self.packageStr)    def getDistribution(self, fileName=TESTMODULE):        result = None        for distribution in importlib_metadata.distributions():            try:                relative = (pathlib.Path(fileName).relative_to(distribution.locate_file('')))            #except ValueError:            #except AttributeError:            except:                pass            else:                if relative in distribution.files:                    result = distribution        return resultif __name__ == '__main__':    result=1    try:        prog = application(args)        result = prog.run()    except Exception as E:        print ("[ERROR] Prog Exception: %s" % str(E))    finally:        sys.exit(result)    # exit the program if we haven't alreadyprint ("Shouldn't get here.")sys.exit(result)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python