Python3pip:查找哪个包提供特定模块

问题描述

在不感到困惑的情况下,关于安装软件包,如何导入生成的模块以及列出可用的软件包,有很多问题。但是,如果您没有Requirements.txt或pipenv,则似乎没有pip的“ --what-provides”选项的等效项。该问题与上一个问题相似,但是要求父包,而不要求其他元数据。就是说,这些其他问题并没有引起足够的重视或许多公认的答案-例如。 How do you find python package metadata information given a module。所以向前迈进...。

通过示例,有两个软件包(仅举几个例子)将安装一个名为“ serial”的模块-即“ pyserial”和“ serial”。因此,假设已安装其中一个软件包,我们可以使用pip list来找到它:

python3 -m pip list | grep serial

但是,如果软件包名称与模块名称不匹配,或者您只是想找出要在旧版服务器或开发机上工作的软件包,则会出现问题。

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

>>> import serial
>>> print(serial.__file__)
/usr/lib/python3.6/site-packages/serial/__init__.py

它在“串行”目录中,但实际上仅安装了pyserial,而不是串行:

> python3 -m pip list | grep serial
pyserial                 3.4

我能找到的最接近的是通过“ pipreqs ./”生成一个requirements.txt,它可能在依赖的子文件上失败(对我来说也是这样),或者通过pipenv反向检查依赖项(这带来了整套设置所有新问题)

> 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包提供特定模块的简单命令吗?

解决方法

我相信类似以下的方法应该起作用:

#!/usr/bin/env python3

import importlib.util
import pathlib

import importlib_metadata

def 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 result

def 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对象。

,

在@sinoroc广为发布的答案的基础上,我想到了以下代码(合并了提到的importlib.util.find_spec方法,但在返回的路径中针对RECORD文件进行了基于bash的搜索)。我也尝试实现@sinoroc的版本-但没有成功。两种方法都包括在内以进行演示。

以“ python3 python_find-module-package.py -m [module-name-here] -d”运行,这还将打印调试信息。取消“ -d”开关,只获取返回的软件包名称(和错误)。

TLDR:Code on github

#!/usr/bin/python3

import sys
import os.path
import importlib.util
import importlib_metadata
import pathlib
import subprocess
import argparse
parser = 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 result

if __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 already
print ("Shouldn't get here.")
sys.exit(result)