我经常在一个封装中需要使用一个兄弟包的情况下结束。 我要澄清的是,我不问了Python如何可以导入邻近的包,已经被问了很多次。 相反,我的问题是关于编写维护的代码的最佳做法。
比方说,我们有一个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)
现在,在某些时候,我们决定该功能已经长大,并将其分为两个文件:
# 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
。
技术方案是导入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
将是一场噩梦,因为现在所有的模块别名应该改变。
它想避免的导入功能直接,而是导入包,让那么很显然,一个函数读取代码时就来了。 我该如何处理在可读性和可维护性的方式这种情况呢?
我可以从字面上问你你需要什么语法和提供。 我不会,但你可以自己做到这一点。
“问题是, 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
我不建议这在实践中实际使用,但只是为了好玩,这里是使用的解决方案pkgutil
和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)