【问题标题】:Python subpackages and namespacesPython 子包和命名空间
【发布时间】:2021-08-09 01:25:22
【问题描述】:

我正在为一个在我看来非常基本和常见的问题而苦苦挣扎,经过数小时的互联网搜索后我找不到任何答案的事实告诉我,我一定是做错了什么......

我只是想找到一种优雅的方式来处理我的包的导入。

背景:

我的包裹结构如下:

mypackage/
    __init__.py
    model/
        __init__.py
        A.py
        B.py
    controllers/
        __init__.py
        A.py
        B.py

# mypackage/model/A.py
class A:
    def __init__(self):
        print("This is A's model.")
# mypackage/model/B.py
from mypackage.model.A import A as AModel
class B:
    def __init__(self):
        self._a_model = AModel()
        print("This is B's model.")

# mypackage/controllers/A.py
class A:
    def __init__(self):
        print("This is A's controller.")
# mypackage/controllers/B.py
from mypackage.controllers.A import A as AController
class B:
    def __init__(self):
        self._a = AController()
        print("This is B's controller.")

问题:

有两件事真的让我对这个设计感到困扰。

首先:我想使用命名空间

我真的不喜欢写作

from mypackage.controllers.A import A as AController
...
self._a = AController()

感觉很麻烦而且不是很pythonic... 我更喜欢使用如下命名空间:

from mypackage import controllers
...
self._a = controllers.A.A()

但如果我尝试,我会得到AttributeError: module 'mypackage.controllers' has no attribute 'A'

第二:我真的不喜欢输入班级的文件名

我真的不喜欢写作

from mypackage.controllers.A import A as AController

我更喜欢:

from mypackage.controllers import A as AController

什么不起作用

将所有内容放在一个文件中

我知道我可以通过将所有控制器的类(A 和 B)定义放在一个文件 (controllers.py) 中来得到我想要的,并对模型做同样的事情......

我读过几次,将多个类定义放在一个文件中是 python 中很常见的事情......但对于大型单独的类,我不能。如果 A 和 B 有数百行并且彼此没有任何关系(除了作为控制器和模型),那么将它们的定义放在一个文件中是不可用的。

init.py 文件中使用导入

确实可以解决我所有的问题...

除了:

  1. 这会导致循环导入。 如您所见,我在 B 的模型中需要 A 的模型......因此,如果在我需要访问其中一个模型时导入所有模型,我就会陷入恶性循环。 ..
  2. 这似乎不是很pythonic。如果只是因为它强制用户加载每个模块。

我在这里……

我的推理有什么问题?

【问题讨论】:

  • 注意:这里所说的“命名空间”并不是 Python 中“命名空间”术语下通常引用的内容。但它并没有改变任何问题,只是一个旁白。
  • 您是否尝试将一些导入添加到控制器目录的 init.py 中。 from A import A?比你应该能够给我们from mypackage.controllers import A as AController。如果我没记错的话:-)

标签: python python-import python-packaging


【解决方案1】:

__init__.py 文件中使用导入

这就是要走的路。 对不起,如果它看起来对你来说太多样板,但如果你的项目很大,那就是需要的。

至于循环导入问题:无论您编写导入,它都会启动 在__init__ 文件中与否,这只是逻辑问题。 如果在您的__init__ 文件中,您以正确的顺序编写导入,就不会有循环问题。

即在你的myproject/models/__init__.py 你有:

from .A import A as AModel
from .B import B as BModel

当然,您将 .py 文件命名为与类相同的名称对您没有帮助 - 如果您至少放开文件名中的大小写,您可以编写:

from .a import A
from .b import B

否则,您可以这样做:

import myproject.models.A
import myproject.models.B

A = myproject.models.A.A
B = myproject.models.B.B

为了能够使用“myproject.models.A”作为类: __init__ 中的名称 A 将覆盖模块对象 同名。

一个写作 import myproject.models.A 将进入模块,但通过这样做 from myproject.models import A 你得到了模块。

如果这让人感到困惑...尽量不要为模块文件使用与类相同的名称。即使在忽略大小写的文件系统(如 Windows)中,您也会 反正模棱两可。遵守约定:snake_case 中的模块名,CamelCase 中的类名

回到循环导入问题:重点是在这个设计中,b.py 仅在 a.py 已经被导入之后才被读取,并且没有循环导入问题。

这并不总是可能的 - 有时需要子模块之间的交叉引用。在这些情况下可以做的是将import 行移动到 函数或方法,而不是作为全局语句。这样,当执行导入时,引用的模块已经初始化。

在你的例子中是:

mypackage.models.b.py

# mypackage/model/B.py

class B:
    def __init__(self):
        from mypackage.model.A import A as AModel
        self._a_model = AModel()
        print("This is B's model.")

【讨论】:

  • 谢谢。我又试了一次,我的循环导入问题消失了……也许我还有其他事情发生?我对文件命名约定不好:我确实尊重 pep8 约定,我只是在试图用一个简单的例子来反映我的实际项目时弄混了。所以:__init__.py 文件中的导入确实解决了我所有的问题。
  • 是的。如果在细粒度的方法或函数中使用内部导入模式,则可能会对性能产生影响(Python 不会重新读取文件或重新处理模块,但所有检查和名称绑定在底层都比只需创建一个变量) - 在这种情况下,您可能希望在函数内将导入的名称标记为 global 并使用 if 子句保护导入语句 - if AModel is not None: from ... import A as AModel
【解决方案2】:
# mypackage/controllers/__init__.py
from A import A

然后你可以在 mypackage 之外创建一个新文件。

# check.py
from mypackage.controllers import A as AController
from mypackage import controllers
a = controllers.A()
>>> This is A's controller.

让我们知道它是否适合您。

【讨论】:

  • 谢谢!它对我有用。我之前尝试过这个,但得到了一个循环导入错误,这似乎与此明显相关。事实证明它一定来自其他东西,因为它现在有效。
【解决方案3】:

[在我的脑海中,未经测试]

我真的不喜欢写作

from mypackage.controllers.A import A as AController
#...
    self._a = AController()

感觉很麻烦而且不是很pythonic...我更喜欢使用 命名空间,如:

from mypackage import controllers
# ...
    self._a = controllers.A.A()

mypackage/controllers/__init__.py 中,您需要:from . import A

我真的不喜欢写作

from mypackage.controllers.A import A as AController

我更喜欢:

from mypackage.controllers import A as AController

mypackage/controllers/__init__.py 中,您需要from A import A

【讨论】:

    猜你喜欢
    • 2014-01-15
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-21
    • 1970-01-01
    • 1970-01-01
    • 2020-04-28
    相关资源
    最近更新 更多