如果导入的模块来自C extension而不是纯Python模块,那么从Python中判断出正确或最强大的方法是什么?这很有用,例如,如果Python包具有同时具有纯Python实现和C实现的模块,并且您希望能够在运行时告知正在使用哪个模块。
一个想法是检查module.__file__
的文件扩展名,但我不确定应该检查所有文件扩展名,以及这种方法是否最可靠。
答案 0 :(得分:15)
<强> TL;博士强>
参见&#34;寻求完美&#34;以下小节为经过充分测试的答案。
作为对abarnert的helpful analysis的实用对立 Stackoverflow Productions™可以轻松识别C扩展,提供...... 实际答案。
我最不喜欢的Stackoverflow类型的答案是&#34;不要这样做,因为我说&#34;品种。不出所料,abarnert其他有用的分析开始于这样一个家长式的熨平板:
我认为这没什么用处。
可靠地区分C扩展和非C扩展的能力非常有用,没有它,Python社区就会变得贫穷。真实世界的用例包括:
我们都同意冻结,优化和最小化最终用户投诉是有用的。因此,确定C扩展名很有用。
我也不同意reference BLAS implementation的倒数第二个结论:
任何人为此提出的最好的启发式方法是
inspect
模块中实现的那些,所以最好的办法就是使用它。
不。任何人为此提出的最好的启发式方法是下面给出的。所有stdlib模块(包括 not 仅限于inspect
)都无法用于此目的。具体做法是:
inspect.getsource()
和inspect.getsourcefile()
函数模糊地返回None
两个C扩展(可以理解地没有纯Python源)和其他类型的模块也没有纯Python源(例如,仅字节码模块)。 无用 importlib
仅机械适用于abarnert可加载的模块,因此默认importlib
导入算法可见。 有用,但几乎不适用。当现实世界反复击中你的包裹时,PEP 302合规性的假设就会破裂。例如,您是否知道__import__()
内置实际上是PEP 302-compliant loaders? 这就是我们用来定制Python导入机制的方法 - 当地球仍然平坦时。 overriddable&#39; abarnert也有争议:
......没有完美的答案。
有一个完美的答案。就像Hyrulean传说中经常被怀疑的ultimate conclusion一样,每个不完美的问题都有一个完美的答案。
让我们找到它。
仅当传递的先前导入的模块对象是C扩展时,后面的纯Python函数才返回True
: 为简单起见, Python 3.x 是假设
import inspect, os
from importlib.machinery import ExtensionFileLoader, EXTENSION_SUFFIXES
from types import ModuleType
def is_c_extension(module: ModuleType) -> bool:
'''
`True` only if the passed module is a C extension implemented as a
dynamically linked shared library specific to the current platform.
Parameters
----------
module : ModuleType
Previously imported module object to be tested.
Returns
----------
bool
`True` only if this module is a C extension.
'''
assert isinstance(module, ModuleType), '"{}" not a module.'.format(module)
# If this module was loaded by a PEP 302-compliant CPython-specific loader
# loading only C extensions, this module is a C extension.
if isinstance(getattr(module, '__loader__', None), ExtensionFileLoader):
return True
# Else, fallback to filetype matching heuristics.
#
# Absolute path of the file defining this module.
module_filename = inspect.getfile(module)
# "."-prefixed filetype of this path if any or the empty string otherwise.
module_filetype = os.path.splitext(module_filename)[1]
# This module is only a C extension if this path's filetype is that of a
# C extension specific to the current platform.
return module_filetype in EXTENSION_SUFFIXES
如果它看起来很长,那是因为文档字符串,注释和断言是好的。它实际上只有六行。 吃掉你老人的心脏,Guido。
让我们用四个可移植的模块测试这个功能:
os.__init__
模块。 希望不是C扩展。 importlib.machinery
子模块。 希望不是C扩展。 _elementtree
C扩展名。numpy.core.multiarray
C扩展程序。即便:
>>> import os
>>> import importlib.machinery as im
>>> import _elementtree as et
>>> import numpy.core.multiarray as ma
>>> for module in (os, im, et, ma):
... print('Is "{}" a C extension? {}'.format(
... module.__name__, is_c_extension(module)))
Is "os" a C extension? False
Is "importlib.machinery" a C extension? False
Is "_elementtree" a C extension? True
Is "numpy.core.multiarray" a C extension? True
结束了所有人。
我们的代码细节非常无关紧要。很好,我们从哪里开始?
__loader__
类的实例,则此模块是C扩展名。importlib.machinery.ExtensionFileLoader
机制被覆盖(例如,低 - level bootloader将此Python应用程序作为特定于平台的冻结二进制文件运行。在任何一种情况下,都要回退测试此模块的文件类型是否是特定于当前平台的C扩展名。八行功能,有二十页解释。 我们是如何滚动的。
答案 1 :(得分:11)
首先,我认为这根本没用。模块在C扩展模块周围是纯Python包装器是很常见的 - 或者在某些情况下,如果C扩展模块可用,则是纯Python包装器,如果不可用,则是纯Python包装。
对于一些流行的第三方示例:numpy
是纯Python,即使重要的一切都是用C实现的; bintrees
是纯Python,即使它的类都可以用C或Python实现,具体取决于你如何构建它;等
从3.2开始的大多数stdlib都是如此。例如,如果你只是import pickle
,那么实现类将在CPython中用C构建(你曾经从2.7中的cpickle
获得),而它们将是PyPy中的纯Python版本,但无论哪种方式pickle
本身都是纯Python。
但如果你做想要这样做,你实际上需要区分三个事物:
sys
。cpickle
。pickle
。而且假设你只关心CPython;如果你的代码运行在Jython或IronPython中,那么实现可能是JVM或.NET而不是本机代码。
由于多种原因,您无法完全根据__file__
进行区分:
__file__
。 (这在一些地方有记录 - 例如inspect
文档中的Types and members表。)请注意,如果您使用py2app
或cx_freeze
之类的内容,那么计为“内置”可能与独立安装不同。easy_install
相同,pip
较少)将有空白或无用的__file__
。在3.1+中,导入过程已经大量清理,大部分都是用Python重写的,并且主要暴露在Python层中。
因此,您可以使用importlib
模块查看用于加载模块的加载器链,最终您将转到BuiltinImporter
(内置),ExtensionFileLoader
(。所以/ .pyd / etc。),SourceFileLoader
(。py)或SourcelessFileLoader
(。pyc / .pyo)。
您还可以在当前目标平台上看到分配给四个中每个的后缀,作为importlib.machinery
中的常量。所以,你可以检查any(pathname.endswith(suffix) for suffix in importlib.machinery.EXTENSION_SUFFIXES))
,但实际上并没有帮助,例如鸡蛋/拉链盒,除非你已经走完了链条。
任何人为此提出的最好的启发式方法是inspect
模块中实现的那些,所以最好的办法就是使用它。
最佳选择是getsource
,getsourcefile
和getfile
中的一个或多个;哪个最好取决于你想要的启发式方法。
内置模块会为其中任何一个引发TypeError
。
扩展模块应该为getsourcefile
返回一个空字符串。这似乎适用于我所拥有的所有2.5-3.4版本,但我没有2.4左右。对于getsource
,至少在某些版本中,它返回.so文件的实际字节,即使它应该返回空字符串或引发IOError
。 (在3.x中,你几乎肯定会得到UnicodeError
或SyntaxError
,但你可能不想依赖它......)
如果在egg / zip / etc中,纯Python模块可能会为getsourcefile
返回一个空字符串。如果source可用,它们应该总是为getsource
返回一个非空字符串,即使在egg / zip / etc中也是如此,但如果它们是无源字节码(.pyc / etc。),它们将返回一个空字符串或引发IOError。
最好的办法是在您关心的分发/设置中试验您关心的平台上的版本。
答案 2 :(得分:0)
虽然Cecil Curry's回答有效(而且信息非常丰富,我可能会补充),即使它包含子模块,它也会为模块的“顶级”返回False使用C扩展名(例如numpy vs. numpy.core.multiarray)。
尽管可能没有那么强大,但以下内容适用于我当前的用例:
def is_c(module):
# if module is part of the main python library (e.g. os), it won't have a path
try:
for path, subdirs, files in os.walk(module.__path__[0]):
for f in files:
ftype = f.split('.')[-1]
if ftype == 'so':
is_c = True
break
return is_c
except AttributeError:
path = inspect.getfile(module)
suffix = path.split('.')[-1]
if suffix != 'so':
return False
elif suffix == 'so':
return True
is_c(os), is_c(im), is_c(et), is_c_extension(ma), is_c(numpy)
# (False, False, True, True, True)
答案 3 :(得分:0)
@Cecil Curry的功能非常出色。有两个小注释:首先,for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
for (int a = 0; a < dataGridView2.Rows.Count; a++)
{
if (dataGridView1.Rows[i].Cells[0].Value == dataGridView2.Rows[i].Cells[0].Value)
{
dataGridView1.Rows[i].Cells[2].Value = dataGridView2.Rows[a].Cells[2].Value.ToString();
dataGridView1.Rows[i].Cells[3].Value = dataGridView2.Rows[a].Cells[3].Value.ToString();
i++;
a++;
}
}
}
示例使用我的Python 3.5.6副本引发了_elementtree
。其次,正如@crld所指出的那样,了解模块是否包含 C扩展也很有帮助,但是更具移植性的版本可能会有所帮助。因此,更通用的版本可能是:
TypeError
答案 4 :(得分:0)
如果您和我一样,看到了@Cecil Curry 的精彩回答并想到了,如果没有@Rudolf Cardinal 复杂的子库遍历,我怎么能以超级懒惰的方式对整个需求文件执行此操作,请不要再观望!
>首先,将所有已安装的要求(假设您在虚拟环境中执行此操作并且此处没有其他内容)转储到带有 pip freeze > requirements.txt
的文件中。
然后运行以下脚本来检查每个要求。
注意:这是非常懒惰的,对于许多导入名称与其 pip 名称不匹配的库不起作用。
import inspect, os
import importlib
from importlib.machinery import ExtensionFileLoader, EXTENSION_SUFFIXES
from types import ModuleType
# function from Cecil Curry's answer:
def is_c_extension(module: ModuleType) -> bool:
'''
`True` only if the passed module is a C extension implemented as a
dynamically linked shared library specific to the current platform.
Parameters
----------
module : ModuleType
Previously imported module object to be tested.
Returns
----------
bool
`True` only if this module is a C extension.
'''
assert isinstance(module, ModuleType), '"{}" not a module.'.format(module)
# If this module was loaded by a PEP 302-compliant CPython-specific loader
# loading only C extensions, this module is a C extension.
if isinstance(getattr(module, '__loader__', None), ExtensionFileLoader):
return True
# Else, fallback to filetype matching heuristics.
#
# Absolute path of the file defining this module.
module_filename = inspect.getfile(module)
# "."-prefixed filetype of this path if any or the empty string otherwise.
module_filetype = os.path.splitext(module_filename)[1]
# This module is only a C extension if this path's filetype is that of a
# C extension specific to the current platform.
return module_filetype in EXTENSION_SUFFIXES
with open('requirements.txt') as f:
lines = f.readlines()
for line in lines:
# super lazy pip name to library name conversion
# there is probably a better way to do this.
lib = line.split("=")[0].replace("python-","").replace("-","_").lower()
try:
mod = importlib.import_module(lib)
print(f"is {lib} a c extension? : {is_c_extension(mod)}")
except:
print(f"could not check {lib}, perhaps the name for imports is different?")