cje*_*nek 8 python python-c-extension extension-modules
如果导入的模块来自C扩展而不是纯Python模块,那么从Python中判断出正确或最强大的方法是什么?这很有用,例如,如果Python包具有既包含纯Python实现又包含C实现的模块,并且您希望能够在运行时告知正在使用哪个模块.
一个想法是检查文件扩展名module.__file__,但我不确定应该检查所有文件扩展名,以及这种方法是否必须最可靠.
Cec*_*rry 16
TL;博士
有关经过充分测试的答案,请参阅下面的"寻找完美"小节.
作为abarnert 对可扩展识别C扩展所涉及的微妙性的有用分析的实用对照,Stackoverflow Productions™提供了...... 一个实际的答案.
我最不喜欢的Stackoverflow类型的答案是"不要这样做,因为我说"变种.不出所料,abarnert的其他有用的分析开始于这样一个家长式的同伴:
我认为这根本没用.
能够可靠地区分C扩展和非C扩展的能力是非常有用的,没有它,Python社区就会变得贫穷.真实世界的用例包括:
我们都同意冻结,优化和最小化最终用户投诉是有用的.因此,识别C扩展非常有用.
我也不同意abarnert的倒数第二个结论:
任何人都为此提出的最好的启发式是在
inspect模块中实现的那些,所以最好的办法就是使用它.
没有.任何人为此提出的最好的启发式方法是下面给出的.所有STDLIB模块(包括但不局限于inspect)是无用用于这一目的.特别:
inspect.getsource()和inspect.getsourcefile()功能不明确地返回None两个C扩展(其可以理解没有纯Python源)和其它类型的模块,也没有纯Python源(例如,仅字节代码模块)的.没用.importlib机器仅适用于可由符合PEP 302标准的装载机加载的模块,因此对默认importlib导入算法可见.有用,但几乎不适用.当现实世界反复击中你的包裹时,PEP 302合规性的假设就会破裂.例如,您是否知道__import__()内置实际上是可以覆盖的?这就是我们用来定制Python导入机制的方法 - 当地球仍然平坦时.......没有完美的答案.
有一个完美的答案.就像经常被怀疑的Hyrulean传奇的Triforce一样,每个不完美的问题都有一个完美的答案.
我们找到它.
True仅当传递的先前导入的模块对象是C扩展时,后面的纯Python函数才会返回:为简单起见,假设使用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
Run Code Online (Sandbox Code Playgroud)
如果看起来很长,那是因为文档字符串,注释和断言是好的.它实际上只有六行.Guido,你的老人心脏出去吃.
让我们用四个可移植的可导入模块对这个功能进行单元测试:
os.__init__模块.希望不是C扩展.importlib.machinery子模块.希望不是C扩展._elementtreeC扩展.numpy.core.multiarrayC扩展.以机智:
>>> 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
Run Code Online (Sandbox Code Playgroud)
一切都好了,结束了.
我们的代码的细节是非常无关紧要的.很好,我们从哪里开始?
__loader__属性,其值是加载该模块的加载器对象.因此:
importlib.machinery.ExtensionFileLoader类的实例,则此模块是C扩展.__import__()机器被覆盖(例如,由运行此Python应用程序的低级引导加载程序作为特定于平台的冻结二进制文件).在任何一种情况下,都要回退测试此模块的文件类型是否是特定于当前平台的C扩展名.八行功能,二十页解释.我们是如何滚动的.
aba*_*ert 11
首先,我认为这根本没用.模块在C扩展模块周围是纯Python包装器是很常见的 - 或者在某些情况下,如果C扩展模块可用,则是纯Python包装器,如果不可用,则是纯Python包装器.
对于一些流行的第三方示例:numpy纯Python,即使重要的一切都是用C实现的; bintrees是纯Python,即使它的类都可以用C或Python实现,具体取决于你如何构建它; 等等
从3.2开始,大多数stdlib都是如此.例如,如果你只是import pickle,实现类将cpickle在CPython 中用C(你曾经从2.7中获得)中构建,而它们将是PyPy中的纯Python版本,但两种方式pickle本身都是纯Python.
但是,如果你不希望这样做,你其实需要区分3件事情:
sys.cpickle.pickle.这假设你只关心CPython; 如果你的代码运行在Jython或IronPython中,那么实现可能是JVM或.NET而不是本机代码.
__file__由于以下原因,您无法完美区分:
__file__.(这是记录在几个地方,例如,类型和成员表中inspect的文档.)请注意,如果你使用像py2app或者cx_freeze,什么算是"内置"可能是从独立安装不同.easy_install,较少使用pip)将具有空白或无用__file__.在3.1+中,导入过程已经大量清理,大部分都是用Python重写的,并且主要是暴露给Python层.
因此,您可以使用该importlib模块查看用于加载模块的加载器链,最终您将获得BuiltinImporter(ExtensionFileLoaderbuiltins ),(.so/.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,但你可能不想依赖它...)
纯Python模块可能会返回一个空字符串,getsourcefile如果在egg/zip/etc中.getsource如果source可用,它们应该总是返回非空字符串,即使在egg/zip/etc中也是如此,但如果它们是无源字节码(.pyc/etc.),它们将返回空字符串或引发IOError.
最好的办法是在您关心的分发/设置中试验您关心的平台上您关注的版本.