问题描述
在不感到困惑的情况下,关于安装软件包,如何导入生成的模块以及列出可用的软件包,有很多问题。但是,如果您没有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)