如何以可维护和可读的方式访问兄弟包? [英] How can I access sibling packages in a maintainable and readable way?

查看:163
本文介绍了如何以可维护和可读的方式访问兄弟包?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我常常遇到一个包需要使用兄弟包的情况。我想澄清一下,我并没有问过Python如何允许你导入兄弟包,这已被多次询问。相反,我的问题是关于编写可维护代码的最佳实践。

I often end up in a situation where one package needs to use a sibling package. I want to clarify that I'm not asking about how Python allows you to import sibling packages, which has been asked many times. Instead, my question is about a best practice for writing maintainable code.


  1. 假设我们有 tools package,函数 tools.parse_name()取决于 tools.split_name()。最初,两者都可能存在于同一文件中,一切都很简单:

  1. Let's say we have a tools package, and the function tools.parse_name() depends on tools.split_name(). Initially, both might live in the same file where everything is easy:

# tools/__init__.py
from .name import parse_name, split_name

# tools/name.py
def parse_name(name):
  splits = split_name(name)  # Can access from same file.
  return do_something_with_splits(splits)

def split_name(name):
  return do_something_with_name(name)


  • 现在,在某些时候我们决定功能增长并将它们分成两个文件:

  • Now, at some point we decide that the functions have grown and split them into two files:

     # tools/__init__.py
    from .parse_name import parse_name
    from .split_name import split_name
    
    # tools/parse_name.py
    import tools
    
    def parse_name(name):
      splits = tools.split_name(name)   # Won't work because of import order!
      return do_something_with_splits(splits)
    
    # tools/split_name.py
    def split_name(name):
      return do_something_with_name(name)
    

    问题是 parse_name.py 不能只导入工具包它是自身的一部分。至少,这不允许它在 tools / __ init __。py 中使用下面列出的工具。

    The problem is that parse_name.py can't just import the tools package which it is part of itself. At least, this won't allow it to use tools listed below its own line in tools/__init__.py.

    技术解决方案是导入 tools.split_name 而不是工具

    The technical solution is to import tools.split_name rather than tools:

    # tools/__init__.py
    from .parse_name import parse_name
    from .split_name import split_name
    
    # tools/parse_name.py
    import tools.split_name as tools_split_name
    
    def parse_name(name):
      splits = tools_split_name.split_name(name)   # Works but ugly!
      return do_something_with_splits(splits)
    
    # tools/split_name.py
    def split_name(name):
      return do_something_with_name(name)
    


  • 此解决方案在技术上有效但如果使用的不仅仅是一个兄弟包,很快就会变得混乱。此外,将包 tools 重命名为 utilities 将是一场噩梦,因为现在所有模块别名也应该更改。

    This solution technically works but quickly becomes messy if more than just one sibling packages are used. Moreover, renaming the package tools to utilities would be a nightmare, since now all the module aliases should change as well.

    它希望避免直接导入函数而是导入包,以便在读取代码时清楚显示函数的来源。如何以可读和可维护的方式处理这种情况?

    It would like to avoid importing functions directly and instead import packages, so that it is clear where a function came from when reading the code. How can I handle this situation in a readable and maintainable way?

    推荐答案

    我不是建议在实践中使用它,但只是为了好玩,这是一个解决方案,使用 pkgutil 检查

    I'm not proposing this to be actually used in practice, but just for fun, here is a solution using pkgutil and inspect:

    import inspect
    import os
    import pkgutil
    
    
    def import_siblings(filepath):
      """Import and combine names from all sibling packages of a file."""
      path = os.path.dirname(os.path.abspath(filepath))
      merged = type('MergedModule', (object,), {})
      for importer, module, _ in pkgutil.iter_modules([path]):
        if module + '.py' == os.path.basename(filepath):
          continue
        sibling = importer.find_module(module).load_module(module)
        for name, member in inspect.getmembers(sibling):
          if name.startswith('__'):
            continue
          if hasattr(merged, name):
            message = "Two sibling packages define the same name '{}'."
            raise KeyError(message.format(name))
          setattr(merged, name, member)
      return merged
    

    问题中的示例变为:

    # tools/__init__.py
    from .parse_name import parse_name
    from .split_name import split_name
    
    # tools/parse_name.py
    tools = import_siblings(__file__)
    
    def parse_name(name):
      splits = tools.split_name(name)  # Same usage as if this was an external module.
      return do_something_with_splits(splits)
    
    # tools/split_name.py
    def split_name(name):
      return do_something_with_name(name)
    

    这篇关于如何以可维护和可读的方式访问兄弟包?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆