【问题标题】:In Python how can one tell if a module comes from a C extension?在 Python 中,如何判断一个模块是否来自 C 扩展?
【发布时间】:2013-12-18 19:06:18
【问题描述】:

如果导入的模块来自 C extension 而不是纯 Python 模块,那么从 Python 中判断导入模块的正确或最可靠的方法是什么?这很有用,例如,如果 Python 包包含一个具有纯 Python 实现和 C 实现的模块,并且您希望能够在运行时判断正在使用哪个模块。

一个想法是检查module.__file__ 的文件扩展名,但我不确定应该检查的所有文件扩展名,以及这种方法是否一定是最可靠的。

【问题讨论】:

  • c 模块显示为“内置”。使用dir/查看文档以了解更多信息。
  • 许多模块实际上是一个精简的 Python 包装器,如果可用,则围绕 C 实现,如果不可用,则围绕纯 Python。大多数 3.2+ 标准库都以这种方式工作,许多最流行的第三方模块也是如此。所以,我怀疑这实际上不会告诉你你想要什么。比如numpy是纯Python模块,pickle是纯Python不管_Pickle和朋友是C加速器还是纯Python。
  • @Marcin:'显示为“内置”'在哪里?如果你只看repr,比如2.7 的cPickle,它有一个路径名,而不是字符串built-in。区分内置模块的唯一官方启发式方法是缺少__file__,而cPickle 又不是这样。
  • @abarnert 我可以只解决未使用介入的纯 Python 包装器的第三方模块的情况。在我知道的至少一种这样的情况下,当使用 C 实现时,扩展模块的 __file__ 属性以 .so 结尾,但我不知道是否总是或通常是这种情况。
  • @cjerdonek:绝对不会总是这样。有关详细信息,请参阅我的答案。

标签: python python-c-extension extension-modules


【解决方案1】:

如果您像我一样看到@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?")

【讨论】:

    【解决方案2】:

    @Cecil Curry 的功能非常出色。两个小 cmets:首先,_elementtree 示例使用我的 Python 3.5.6 副本引发了 TypeError。其次,正如@crld 所指出的,了解模块是否包含 C 扩展也很有帮助,但更便携的版本可能会有所帮助。因此,更通用的版本(使用 Python 3.6+ f-string 语法)可能是:

    from importlib.machinery import ExtensionFileLoader, EXTENSION_SUFFIXES
    import inspect
    import logging
    import os
    import os.path
    import pkgutil
    from types import ModuleType
    from typing import List
    
    log = logging.getLogger(__name__)
    
    
    def is_builtin_module(module: ModuleType) -> bool:
        """
        Is this module a built-in module, like ``os``?
        Method is as per :func:`inspect.getfile`.
        """
        return not hasattr(module, "__file__")
    
    
    def is_module_a_package(module: ModuleType) -> bool:
        assert inspect.ismodule(module)
        return os.path.basename(inspect.getfile(module)) == "__init__.py"
    
    
    def is_c_extension(module: ModuleType) -> bool:
        """
        Modified from
        https://stackoverflow.com/questions/20339053/in-python-how-can-one-tell-if-a-module-comes-from-a-c-extension.
    
        ``True`` only if the passed module is a C extension implemented as a
        dynamically linked shared library specific to the current platform.
    
        Args:
            module: Previously imported module object to be tested.
    
        Returns:
            bool: ``True`` only if this module is a C extension.
    
        Examples:
    
        .. code-block:: python
    
            from cardinal_pythonlib.modules import is_c_extension
    
            import os
            import _elementtree as et
            import numpy
            import numpy.core.multiarray as numpy_multiarray
    
            is_c_extension(os)  # False
            is_c_extension(numpy)  # False
            is_c_extension(et)  # False on my system (Python 3.5.6). True in the original example.
            is_c_extension(numpy_multiarray)  # True
    
        """  # noqa
        assert inspect.ismodule(module), f'"{module}" not a 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
    
        # If it's built-in, it's not a C extension.
        if is_builtin_module(module):
            return False
    
        # 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
    
    
    def contains_c_extension(module: ModuleType,
                             import_all_submodules: bool = True,
                             include_external_imports: bool = False,
                             seen: List[ModuleType] = None,
                             verbose: bool = False) -> bool:
        """
        Extends :func:`is_c_extension` by asking: is this module, or any of its
        submodules, a C extension?
    
        Args:
            module: Previously imported module object to be tested.
            import_all_submodules: explicitly import all submodules of this module?
            include_external_imports: check modules in other packages that this
                module imports?
            seen: used internally for recursion (to deal with recursive modules);
                should be ``None`` when called by users
            verbose: show working via log?
    
        Returns:
            bool: ``True`` only if this module or one of its submodules is a C
            extension.
    
        Examples:
    
        .. code-block:: python
    
            import logging
    
            import _elementtree as et
            import os
    
            import arrow
            import alembic
            import django
            import numpy
            import numpy.core.multiarray as numpy_multiarray
    
            log = logging.getLogger(__name__)
            logging.basicConfig(level=logging.DEBUG)  # be verbose
    
            contains_c_extension(os)  # False
            contains_c_extension(et)  # False
    
            contains_c_extension(numpy)  # True -- different from is_c_extension()
            contains_c_extension(numpy_multiarray)  # True
    
            contains_c_extension(arrow)  # False
    
            contains_c_extension(alembic)  # False
            contains_c_extension(alembic, include_external_imports=True)  # True
            # ... this example shows that Alembic imports hashlib, which can import
            #     _hashlib, which is a C extension; however, that doesn't stop us (for
            #     example) installing Alembic on a machine with no C compiler
    
            contains_c_extension(django)
    
        """  # noqa
        assert inspect.ismodule(module), f'"{module}" not a module.'
    
        if seen is None:  # only true for the top-level call
            seen = []  # type: List[ModuleType]
        if module in seen:  # modules can "contain" themselves
            # already inspected; avoid infinite loops
            return False
        seen.append(module)
    
        # Check the thing we were asked about
        is_c_ext = is_c_extension(module)
        if verbose:
            log.info(f"Is module {module!r} a C extension? {is_c_ext}")
        if is_c_ext:
            return True
        if is_builtin_module(module):
            # built-in, therefore we stop searching it
            return False
    
        # Now check any children, in a couple of ways
    
        top_level_module = seen[0]
        top_path = os.path.dirname(top_level_module.__file__)
    
        # Recurse using dir(). This picks up modules that are automatically
        # imported by our top-level model. But it won't pick up all submodules;
        # try e.g. for django.
        for candidate_name in dir(module):
            candidate = getattr(module, candidate_name)
            # noinspection PyBroadException
            try:
                if not inspect.ismodule(candidate):
                    # not a module
                    continue
            except Exception:
                # e.g. a Django module that won't import until we configure its
                # settings
                log.error(f"Failed to test ismodule() status of {candidate!r}")
                continue
            if is_builtin_module(candidate):
                # built-in, therefore we stop searching it
                continue
    
            candidate_fname = getattr(candidate, "__file__")
            if not include_external_imports:
                if os.path.commonpath([top_path, candidate_fname]) != top_path:
                    if verbose:
                        log.debug(f"Skipping, not within the top-level module's "
                                  f"directory: {candidate!r}")
                    continue
            # Recurse:
            if contains_c_extension(
                    module=candidate,
                    import_all_submodules=False,  # only done at the top level, below  # noqa
                    include_external_imports=include_external_imports,
                    seen=seen):
                return True
    
        if import_all_submodules:
            if not is_module_a_package(module):
                if verbose:
                    log.debug(f"Top-level module is not a package: {module!r}")
                return False
    
            # Otherwise, for things like Django, we need to recurse in a different
            # way to scan everything.
            # See https://stackoverflow.com/questions/3365740/how-to-import-all-submodules.  # noqa
            log.debug(f"Walking path: {top_path!r}")
            try:
                for loader, module_name, is_pkg in pkgutil.walk_packages([top_path]):  # noqa
                    if not is_pkg:
                        log.debug(f"Skipping, not a package: {module_name!r}")
                        continue
                    log.debug(f"Manually importing: {module_name!r}")
                    # noinspection PyBroadException
                    try:
                        candidate = loader.find_module(module_name)\
                            .load_module(module_name)  # noqa
                    except Exception:
                        # e.g. Alembic "autogenerate" gives: "ValueError: attempted
                        # relative import beyond top-level package"; or Django
                        # "django.core.exceptions.ImproperlyConfigured"
                        log.error(f"Package failed to import: {module_name!r}")
                        continue
                    if contains_c_extension(
                            module=candidate,
                            import_all_submodules=False,  # only done at the top level  # noqa
                            include_external_imports=include_external_imports,
                            seen=seen):
                        return True
            except Exception:
                log.error("Unable to walk packages further; no C extensions "
                          "detected so far!")
                raise
    
        return False
    
    
    # noinspection PyUnresolvedReferences,PyTypeChecker
    def test() -> None:
        import _elementtree as et
    
        import arrow
        import alembic
        import django
        import django.conf
        import numpy
        import numpy.core.multiarray as numpy_multiarray
    
        log.info(f"contains_c_extension(os): "
                 f"{contains_c_extension(os)}")  # False
        log.info(f"contains_c_extension(et): "
                 f"{contains_c_extension(et)}")  # False
    
        log.info(f"is_c_extension(numpy): "
                 f"{is_c_extension(numpy)}")  # False
        log.info(f"contains_c_extension(numpy): "
                 f"{contains_c_extension(numpy)}")  # True
        log.info(f"contains_c_extension(numpy_multiarray): "
                 f"{contains_c_extension(numpy_multiarray)}")  # True  # noqa
    
        log.info(f"contains_c_extension(arrow): "
                 f"{contains_c_extension(arrow)}")  # False
    
        log.info(f"contains_c_extension(alembic): "
                 f"{contains_c_extension(alembic)}")  # False
        log.info(f"contains_c_extension(alembic, include_external_imports=True): "
                 f"{contains_c_extension(alembic, include_external_imports=True)}")  # True  # noqa
        # ... this example shows that Alembic imports hashlib, which can import
        #     _hashlib, which is a C extension; however, that doesn't stop us (for
        #     example) installing Alembic on a machine with no C compiler
    
        django.conf.settings.configure()
        log.info(f"contains_c_extension(django): "
                 f"{contains_c_extension(django)}")  # False
    
    
    if __name__ == '__main__':
        logging.basicConfig(level=logging.INFO)  # be verbose
        test()
    

    【讨论】:

    • sn-p 失败,因为 1) log 对象丢失(通过添加例如 import logging ; log = logging.getLogger(__name__) 修复),以及 2) is_module_a_package() 函数丢失(我不知道如何实施它)
    • 谢谢 - 相应编辑;示例现在是独立的,并在 Python 3.6 下进行了测试。
    【解决方案3】:

    tl;dr

    请参阅下面的“寻找完美”小节以获得经过充分测试的答案。

    作为与abarnerthelpful analysis 对可移植识别C 扩展所涉及的微妙之处的务实对比,Stack Overflow Productions™ 提出了...一个实际的答案。

    可靠地区分 C 扩展和非 C 扩展的能力非常有用,没有它,Python 社区就会变得一贫如洗。实际用例包括:

    • 应用程序冻结, 将一个跨平台 Python 代码库转换为多个特定于平台的可执行文件。 PyInstaller 是这里的标准示例。识别 C 扩展对于稳健的冻结至关重要。如果被冻结的代码库导入的模块是 C 扩展,则所有由该 C 扩展传递链接到的外部共享库必须也被该代码库冻结。 可耻的告白:我 contribute 到 PyInstaller。
    • 应用程序优化, 静态到本机机器代码(例如,Cython以即时方式动态(例如,Numba)。出于不言而喻的原因,Python 优化器必须将已编译的 C 扩展与未编译的纯 Python 模块区分开来。
    • 依赖性分析,代表最终用户检查外部共享库。在our case 中,我们分析了一个强制依赖项(Numpy),以检测此依赖项与非并行共享库(例如reference BLAS implementation)链接的本地安装,并在出现这种情况时通知最终用户。为什么?因为当我们的应用程序由于我们无法控制的依赖项安装不当而导致性能不佳时,我们不希望受到责备。 糟糕的表现是你的错,倒霉的用户!
    • 可能还有其他基本的低级内容。 分析,也许?

    我们都同意冻结、优化和最大限度地减少最终用户投诉是有用的。因此,识别 C 扩展很有用。

    分歧加深

    我也不同意abarnert 的倒数第二个结论:

    任何人为此想出的最佳启发式方法是在 inspect 模块中实现的启发式方法,所以最好的办法就是使用它。

    否。 任何人为此想出的最佳启发式方法如下所示。所有 stdlib 模块(包括但限于inspect)对于此目的都无用。具体来说:

    • inspect.getsource()inspect.getsourcefile() 函数对于 C 扩展(可以理解,它们没有纯 Python 源代码)和其他类型的模块也没有纯 Python 源代码(例如,仅字节码)模棱两可地返回 None模块)。 没用
    • importlib机械适用于PEP 302-compliant loaders可加载的模块,因此默认importlib导入算法可见。 有用, 但几乎不普遍适用。当现实世界反复撞击您的包裹时,PEP 302 合规性的假设就会失效。例如,您是否知道内置的__import__() 实际上是overriddable这就是我们用来自定义 Python 导入机制的方式——回到地球还平坦的时候。

    abarnertultimate conclusion也有争议:

    ……没有完美的答案。

    有一个完美的答案。就像 Hyrulean 传说中经常被怀疑的 Triforce 一样,每个不完美的问题都有一个完美的答案。

    让我们找到它。

    追求完美

    仅当传递的先前导入的模块对象是 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
    

    如果它看起来很长,那是因为文档字符串、cmets 和断言都很好。实际上只有六行。 Guido,吃掉你的老心脏。

    布丁中的证明

    让我们用四个可移植的可导入模块对这个函数进行单元测试:

    • stdlib 纯 Python os.__init__ 模块。 希望不是 C 扩展。
    • stdlib 纯 Python importlib.machinery 子模块。 希望不是 C 扩展。
    • stdlib _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
    

    一切都结束了。

    如何做到这一点?

    我们代码的细节是无关紧要的。很好,我们从哪里开始?

    1. 如果传递的模块由符合 PEP 302 的加载程序加载(常见情况),PEP 302 specification 需要在导入此模块时分配的属性来定义特殊的 __loader__ 属性其值是加载此模块的加载器对象。因此:
      1. 如果此模块的此值是特定于 CPython 的 importlib.machinery.ExtensionFileLoader 类的实例,则此模块是 C 扩展。
    2. 否则,(A) 活动的 Python 解释器不是官方 CPython 实现(例如,PyPy)或 (B) 活动的 Python 解释器是 CPython,但该模块没有由符合 PEP 302 的加载程序加载,通常是由于默认的 __import__() 机器被覆盖(例如,由运行此的低级引导加载程序) Python 应用程序作为特定于平台的冻结二进制文件)。无论哪种情况,都回退到测试此模块的文件类型是否是特定于当前平台的 C 扩展的文件类型。

    八行函数,二十页解释。 我们就是这样滚动的。

    【讨论】:

      【解决方案4】:

      虽然Cecil Curry's 答案有效(并且提供了非常丰富的信息,就像abarnert's 一样,我可能会补充)它会为模块的“顶级”返回 False,即使它包含使用 C 扩展的子模块(例如 numpy 与 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)
      

      【讨论】:

        【解决方案5】:

        首先,我认为这根本没有用。模块通常是 C 扩展模块的纯 Python 包装器,或者,在某些情况下,如果 C 扩展模块可用,则为纯 Python 包装器,或者如果不可用,则为纯 Python 实现。

        对于一些流行的第三方示例:numpy 是纯 Python,尽管所有重要的东西都是用 C 实现的; bintrees 是纯 Python,尽管它的类可能全部用 C 或 Python 实现,具体取决于您如何构建它;等等

        从 3.2 开始,大多数标准库都是如此。例如,如果您只是import pickle,则实现类将在 CPython 中用 C 构建(您过去从 2.7 中的 cpickle 获得),而在 PyPy 中它们将是纯 Python 版本,但无论哪种方式@ 987654327@本身就是纯Python。


        但是如果你想这样做,你其实需要区分三个的东西:

        • 内置模块,如sys
        • C 扩展模块,如 2.x 的 cpickle
        • 纯 Python 模块,例如 2.x 的 pickle

        假设你只关心 CPython;如果您的代码在 Jython 或 IronPython 中运行,则实现可能是 JVM 或 .NET 而不是本机代码。

        您无法根据__file__ 完美区分,原因有很多:

        • 内置模块根本没有__file__。 (这在几个地方都有记录——例如,inspect 文档中的Types and members 表。)请注意,如果您使用的是py2appcx_freeze 之类的东西,那么算作“内置”的内容可能不同于独立安装。
        • 纯 Python 模块可能有一个 .pyc/.pyo 文件,而在分布式应用程序中没有一个 .py 文件。
        • 作为单文件 egg 安装的包中的模块(easy_install 常见,pip 较少)将有一个空白或无用的__file__
        • 如果您构建二进制发行版,您的整个库很可能会打包在一个 zip 文件中,从而导致与单文件鸡蛋相同的问题。

        在 3.1+ 中,导入过程已被大规模清理,大部分用 Python 重写,并且大部分暴露于 Python 层。

        因此,您可以使用importlib 模块查看用于加载模块的加载程序链,最终您将到达BuiltinImporter(内置)、ExtensionFileLoader(.so/.pyd/etc .)、SourceFileLoader (.py) 或 SourcelessFileLoader (.pyc/.pyo)。

        您还可以在当前目标平台上看到分配给四个中每一个的后缀,作为importlib.machinery 中的常量。因此,您可以检查any(pathname.endswith(suffix) for suffix in importlib.machinery.EXTENSION_SUFFIXES)),但这实际上并没有帮助,例如鸡蛋/拉链盒,除非您已经沿着链条向上移动。


        任何人为此想出的最佳启发式方法是在 inspect 模块中实现的启发式方法,所以最好的办法就是使用它。

        最佳选择是getsourcegetsourcefilegetfile 中的一个或多个;哪个最好取决于您想要哪种启发式方法。

        内置模块将为其中任何一个生成TypeError

        扩展模块应该为getsourcefile 返回一个空字符串。这似乎适用于我拥有的所有 2.5-3.4 版本,但我没有 2.4。对于getsource,至少在某些版本中,它返回.so 文件的实际字节,即使它应该返回一个空字符串或引发IOError。 (在 3.x 中,您几乎肯定会得到 UnicodeErrorSyntaxError,但您可能不想依赖它……)

        如果在 egg/zip/etc 中,纯 Python 模块可能会为 getsourcefile 返回一个空字符串。如果源可用,即使在 egg/zip/etc 中,它们也应该始终为getsource 返回一个非空字符串,但如果它们是无源字节码(.pyc/等),它们将返回一个空字符串或引发一个 IOError。

        最好的办法是在您关心的发行版/设置中在您关心的平台上试验您关心的版本。

        【讨论】:

        • 即使inspect.getsource也不能正常工作,对于二进制模块,它返回二进制ELF字符串内容,对于Pure py,它返回源代码。在 RedHat EL5、python 2.4.3 上进行测试。还有EL6,python 2.6.6。是不是太老了?
        • @PasteBT:它真的不应该返回 ELF 字符串内容……让我浏览一下历史记录,看看它是否在某个时候发生了变化。也许您想直接使用getsourcefile 和/或getsourcelines
        • Fedora 17 + python 2.7.3 工作。二进制模块引发异常。并且 getsourcefile 更符合您的描述。即使是旧版本
        • @PasteBT:我在 2.5 和 3.1 中复制了返回二进制“源”的二进制模块,尽管在 3.1 中你会得到一个 UnicodeErrorSyntaxError 试图处理 Mach-O/ELF/PE 标头作为文本。但是,至少在 2.7 和 3.4 中,getsource 有时适用于 getsourcefile 不适用的压缩源文件。所以,它们各有利弊,并没有完美的答案。
        • 一个完美的答案, 但 stdlib inspectimportlib 模块都不是。为什么?因为inspect.getsource() inspect.getsourcefile() 返回的值无法区分C 扩展(没有纯Python 源代码)和其他类型的模块 没有纯 Python 源代码(例如,仅字节码模块)。同样,importlib 机器仅适用于可使用符合 PEP 302 的加载器加载的模块 - 不一定必须如此。尽管如此,这个答案对于探索不明显的细微差别来说是极好的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-19
        • 2011-08-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多