【问题标题】:Is there a standard way to list names of Python modules in a package?是否有一种标准方法可以列出包中 Python 模块的名称?
【发布时间】:2010-10-04 00:44:20
【问题描述】:

是否有不使用__all__ 的直接列出包中所有模块名称的方法?

例如,给定这个包:

/testpkg
/testpkg/__init__.py
/testpkg/modulea.py
/testpkg/moduleb.py

我想知道是否有标准或内置的方式来做这样的事情:

>>> package_contents("testpkg")
['modulea', 'moduleb']

手动方法是遍历模块搜索路径以找到包的目录。然后可以列出该目录中的所有文件,过滤掉唯一命名的 py/pyc/pyo 文件,去除扩展名,然后返回该列表。但这对于模块导入机制已经在内部做的事情来说似乎是相当多的工作。该功能是否暴露在任何地方?

【问题讨论】:

    标签: python module package


    【解决方案1】:
    import module
    help(module)
    

    【讨论】:

    • 虽然帮助确实在帮助文本的底部列出了包内容,但问题更多的是如何做到这一点:f(package_name) => ["module1_name", "module2_name"] .我想我可以解析帮助返回的字符串,但这似乎比列出目录更迂回。
    • @DNS: help() 打印东西,它不返回字符串。
    • 我同意这是一种迂回的方式,但它让我陷入了一个兔子洞,看看help() 是如何工作的。无论如何,内置的pydoc 模块可以帮助吐出help() 分页的字符串:import pydoc; pydoc.render_doc('mypackage')
    【解决方案2】:

    也许这会满足您的需求?

    import imp
    import os
    MODULE_EXTENSIONS = ('.py', '.pyc', '.pyo')
    
    def package_contents(package_name):
        file, pathname, description = imp.find_module(package_name)
        if file:
            raise ImportError('Not a package: %r', package_name)
        # Use a set because some may be both source and compiled.
        return set([os.path.splitext(module)[0]
            for module in os.listdir(pathname)
            if module.endswith(MODULE_EXTENSIONS)])
    

    【讨论】:

    • 我会将 'and module != "init.py"' 添加到最后的 'if' 中,因为 init.py 不是真的是包装的一部分。 .pyo 是另一个有效的扩展名。除此之外,使用 imp.find_module 是一个非常好的主意。我认为这是正确的答案。
    • 我不同意——你可以直接导入 init,那么为什么要特殊情况呢?它肯定不够特别,不能打破规则。 ;-)
    • 你应该使用imp.get_suffixes()而不是你的手写列表。
    • 另外,请注意这不适用于像xml.sax这样的子包
    • [os.path.splitext(module)[0] 的目的是什么?首先,模块没有定义......你的意思是'package_name'还是'pathname'(据我所知,它们总是相同的),然后为什么要从包名中去除扩展名?会有一个吗?就此而言,为什么要包含该软件包?
    【解决方案3】:
    def package_contents(package_name):
      package = __import__(package_name)
      return [module_name for module_name in dir(package) if not module_name.startswith("__")]
    

    【讨论】:

    • 这仅适用于模块,不适用于包。在 Python 的 logging 包上尝试一下,看看我的意思。 Logging 包含两个模块:handlers 和 config。您的代码将返回一个包含 66 个项目的列表,其中不包括这两个名称。
    【解决方案4】:

    使用python2.3 and above,您也可以使用pkgutil 模块:

    >>> import pkgutil
    >>> [name for _, name, _ in pkgutil.iter_modules(['testpkg'])]
    ['modulea', 'moduleb']
    

    编辑:请注意,pkgutil.iter_modules 的参数不是模块列表,而是路径列表,因此您可能需要执行以下操作:

    >>> import os.path, pkgutil
    >>> import testpkg
    >>> pkgpath = os.path.dirname(testpkg.__file__)
    >>> print([name for _, name, _ in pkgutil.iter_modules([pkgpath])])
    

    【讨论】:

    • 这是令人不安的无证,但似乎是最正确的方法。希望你不介意我添加了注释。
    • pkgutilpython2.3 and up actually 中。此外,虽然pkgutil.iter_modules() 不会递归工作,但还有一个pkgutil.walk_packages(),它递归。感谢您提供指向此软件包的指针。
    • 为什么iter_modules 不适用于像a.b.testpkg 这样的绝对导入?它给了我[]
    • 我无法确认pkgutil.walk_packages() 递归,它给我的输出与pkgutil.iter_modules() 相同,所以我认为答案不完整。
    • 如果有人关心,这在 python 3.8 中也可以完美运行(只是说原始帖子很老)
    【解决方案5】:

    不知道我是否忽略了什么,或者答案是否已经过时但是;

    正如 user815423426 所说,这仅适用于活动对象,并且列出的模块只是之前导入的模块。

    使用inspect 列出包中的模块似乎非常简单:

    >>> import inspect, testpkg
    >>> inspect.getmembers(testpkg, inspect.ismodule)
    ['modulea', 'moduleb']
    

    【讨论】:

    • 我已经输入了 import = import__('myproj.mymod.mysubmod') m = inspect.getmembers(i, inspect.ismodule) 但导入路径是 ~/myproj/__init.py 和 m 是一个带有 (mymod, '~/myproj/mymod/__init__.py') 的列表
    • @hithwen 不要在 cmets 中提问,特别是如果它们没有直接关系。做一个好撒玛利亚人:使用imported = import importlib; importlib.import_module('myproj.mymod.mysubmod')__import__ 导入顶层模块see the documentation
    • 嗯,这很有希望,但对我不起作用。当我执行import inspect, mypackageinspect.getmembers(my_package, inspect.ismodule) 时,我得到一个空列表,尽管其中肯定有各种模块。
    • 事实上,这似乎只有在我import my_package.foo 而不仅仅是import mypackage 时才有效,在这种情况下它会返回foo。但这违背了目的
    • @user815423426 你说得对 ;-) 好像我忽略了什么。
    【解决方案6】:

    基于 cdleary 的示例,这里是所有子模块的递归版本列表路径:

    import imp, os
    
    def iter_submodules(package):
        file, pathname, description = imp.find_module(package)
        for dirpath, _, filenames in os.walk(pathname):
            for  filename in filenames:
                if os.path.splitext(filename)[1] == ".py":
                    yield os.path.join(dirpath, filename)
    

    【讨论】:

      【解决方案7】:

      这是一个递归版本,适用于 python 3.6 及更高版本:

      import importlib.util
      from pathlib import Path
      import os
      MODULE_EXTENSIONS = '.py'
      
      def package_contents(package_name):
          spec = importlib.util.find_spec(package_name)
          if spec is None:
              return set()
      
          pathname = Path(spec.origin).parent
          ret = set()
          with os.scandir(pathname) as entries:
              for entry in entries:
                  if entry.name.startswith('__'):
                      continue
                  current = '.'.join((package_name, entry.name.partition('.')[0]))
                  if entry.is_file():
                      if entry.name.endswith(MODULE_EXTENSIONS):
                          ret.add(current)
                  elif entry.is_dir():
                      ret.add(current)
                      ret |= package_contents(current)
      
      
          return ret
      

      【讨论】:

      • 使用os.scandir 作为上下文管理器而不是直接迭代结果条目有什么好处?
      • @monkut 请参阅docs.python.org/3/library/os.html#os.scandir,它建议将其用作上下文管理器,以确保在完成操作后调用close,以确保释放所有持有的资源。
      • 这对re 不起作用,而是列出每个包,但将re. 添加到所有包中
      【解决方案8】:

      这应该列出模块:

      help("modules")
      

      【讨论】:

      • 这不是答案。它打印所有可用的模块,而作者要求给定包中的模块。
      【解决方案9】:

      如果您想在 python 代码之外(从命令提示符)查看有关您的包的信息,您可以使用 pydoc。

      # get a full list of packages that you have installed on you machine
      $ python -m pydoc modules
      
      # get information about a specific package
      $ python -m pydoc <your package>
      

      您将获得与 pydoc 相同的结果,但在解释器内部使用帮助

      >>> import <my package>
      >>> help(<my package>)
      

      【讨论】:

        【解决方案10】:

        这里的其他答案将在检查包时运行包中的代码。如果你不想这样,你可以 grep 像this answer这样的文件

        def _get_class_names(file_name: str) -> List[str]:
            """Get the python class name defined in a file without running code
            file_name: the name of the file to search for class definitions in
            return: all the classes defined in that python file, empty list if no matches"""
            defined_class_names = []
            # search the file for class definitions
            with open(file_name, "r") as file:
                for line in file:
                    # regular expression for class defined in the file
                    # searches for text that starts with "class" and ends with ( or :,
                    # whichever comes first
                    match = re.search("^class(.+?)(\(|:)", line) # noqa
                    if match:
                        # add the cleaned match to the list if there is one
                        defined_class_name = match.group(1).strip()
                        defined_class_names.append(defined_class_name)
            return defined_class_names
        

        【讨论】:

          猜你喜欢
          • 2012-05-17
          • 2011-12-21
          • 2013-01-27
          • 1970-01-01
          • 2011-12-08
          • 1970-01-01
          • 2013-04-12
          • 2021-02-19
          相关资源
          最近更新 更多