如何访问在维护和可读的方式邻近的包?(How can I access sibling packag

2019-10-29 06:06发布

我经常在一个封装中需要使用一个兄弟包的情况下结束。 我要澄清的是,我不问了Python如何可以导入邻近的包,已经被问了很多次。 相反,我的问题是关于编写维护的代码的最佳做法。

  1. 比方说,我们有一个tools包,功能tools.parse_name()取决于tools.split_name() 。 最初,既可以生活在这里的一切很容易同一个文件:

     # 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) 
  2. 现在,在某些时候,我们决定该功能已经长大,并将其分为两个文件:

      # 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

  3. 技术方案是导入tools.split_name而不是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将是一场噩梦,因为现在所有的模块别名应该改变。

它想避免的导入功能直接,而是导入包,让那么很显然,一个函数读取代码时就来了。 我该如何处理在可读性和可维护性的方式这种情况呢?

Answer 1:

我可以从字面上问你你需要什么语法和提供。 我不会,但你可以自己做到这一点。

“问题是, parse_name.py不能只导入工具包,它是自身的一部分。”

这看起来像一个错误的和奇怪的事情,确实如此。

“至少,这不会允许它使用自己的路线如下工具tools/__init__.py

答应了,但同样的,我们并不需要,如果事情是正确的结构。

为了简化讨论,并降低了自由度,我认为几件事情在下面的例子。

然后,您可以适应不同但相似的情况下,因为你可以修改代码以适应您的导入语法的要求。

我给一些提示进行到底的变化。

场景:

你想建立一个导入包命名tools

你有很多的功能在里面,要提供给客户端代码client.py 。 此文件使用包tools通过导入它。 为了保持简单起见,我让所有的功能(来自世界各地)提供以下工具的命名空间,通过使用from ... import *表格。 这是危险的,应该在真实的情景进行修改,以避免名称冲突与和子包名之间。

您可以通过进口包对它们进行分组的内部组织的功能整合在一起tools包(子包)。

该子包有(按照定义)自己的文件夹和至少一个__init__.py里面。 我选择把子封装单个代码模块中的每个子包文件夹中,除了__init__.py 。 你可以有多个模块和/或内包装。

.
├── client.py
└── tools
    ├── __init__.py
    ├── splitter
    │   ├── __init__.py
    │   └── splitter.py
    └── formatter
        ├── __init__.py
        └── formatter.py

我把__init__.py空的,除了一个外,负责让所有想要的名字提供给客户端导入代码,在tools的命名空间。 这是可以改变的,当然。

#/tools/__init.py___
# note that relative imports avoid using the outer package name
# which is good if later you change your mind for its name

from .splitter.splitter import * 
from .formatter.formatter import * 



# tools/client.py
# this is user code

import tools

text = "foo bar"

splits = tools.split(text) # the two funcs came 
                           # from different subpackages
text = tools.titlefy(text)

print(splits)
print(text)




# tools/formatter/formatter.py
from ..splitter import splitter # tools formatter sibling
                                # subpackage splitter,
                                # module splitter

def titlefy(name):
  splits = splitter.split(name)
  return ' '.join([s.title() for s in splits])




# tools/splitter/splitter.py
def split(name):
    return name.split()

实际上,你可以定制进口语法你的口味,回答关于他们的样子您的评论。

from需要用于相对导入形式。 否则的前缀来路径中使用绝对导入tools.

__init__.py s时,可以用于将导入的名称调整到进口商代码,或初始化模块。 它们也可以是空的,或者真正开始作为分包唯一的文件,里面有所有的代码,然后在其他模块分裂,尽管我不喜欢这个“一切__init__.py办法”为多。

他们是正义的进口运行的代码。

您也可以避开或者使用不同的名称重复进口路径名,或者把一切在__init__.py ,丢弃模块重复名称,或在使用别名__init__.py进口或名称归属存在。 您也可以限制在*形式使用的进口商通过向指定的名称是什么都输出__all__列表。

您可能希望更安全的可读性的变化是迫使client.py使用的名称是时指定分包,

name1 = tools.splitter.split('foo bar')

更改__init__.py只导入子模块,就像这样:

from .splitter import splitter
from .formatter import formatter


Answer 2:

我不建议这在实践中实际使用,但只是为了好玩,这里是使用的解决方案pkgutilinspect

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)


文章来源: How can I access sibling packages in a maintainable and readable way?