再次循环导入

Circular imports again

我在 python3.8 中有一个像这样的模块结构:

module
|- __init__.py
|- foo.py
|- bar.py

# init.py
from .foo import Foo
from .bar import Bar
__all__ = ['Foo', 'Bar', ]

我现在想实现乘法foo * bar。所以我在写:

# foo.py
from .bar import Bar

class Foo:
  def __init__(self):
    self.value = 5

  def __mul__(self, other):
    if not isinstance(other, Bar): raise ValueError('')
    return self.value * other.number

# bar.py

from .foo import Foo

class Bar:
  def __init__(self):
    self.number = 2

  def __mul__(self, other):
    if not isinstance(other, Foo): raise ValueError('')
    return other * self

不幸的是,这没有按预期工作。在这种情况下有没有办法进行类型检查?我知道我可以在 Bar.__mul__ 中导入 Foo——但这对我来说似乎很不整洁。

在需要时导入 Foo:

# bar.py

class Bar:
  def __init__(self):
    self.number = 2

  def __mul__(self, other):
    from .foo import Foo
    if not isinstance(other, Foo): raise ValueError('')
    return other * self

本例中的栏是已知的,因此不再有问题

编辑开始

好吧,有点老套了,但是你可以在 类:

之外定义 __mul__ 函数
class Bar:
    def __init__(self, value=10):
        self.value = value

class Foo:
    def __init__(self, value=5):
        self.value = value

#    def __mul__(self, other):
#        return self.value * other.value

def mul_func(self, other):
    if isinstance(other, Bar):
        return self.value * other.value
    raise ValueError('')


Foo.__mul__ = mul_func

a = Foo(5)
b = Bar(10)
c = Foo(20)

a*b
a*c   # value error

编辑结束

我会有 2 种解决方案,要么使用基类的变通方法,要么使用 type 之外的其他方法来确定你做对了。

编辑:我很好奇最初的问题是什么。因为所有这些对我来说似乎很老套,或者我可能只是没有以好的方式看到问题。如果我们使用 mybase.py 解决方案,为什么不对原始 foo.pybar.py 使用单个文件。我想实现取决于你

解决方案 1:基类

您的架构如下:

module
|- __init__.py
|- foo.py
|- bar.py
|- mybase.py

然后,您的文件 mybase.py 例如...

class MyFooBase:
    pass

class MyBarBase:
    pass

然后,在您的文件 foo.pybar.py 中,您将拥有以下文件(我将只显示一个,因为很明显另一个看起来像)...

from .mybase import MyFooBase, MyBarBase

class Bar(MyBarBase):
  def __init__(self):
    self.number = 2

  def __mul__(self, other):
    if not isinstance(other, MyFooBase): raise ValueError('')
    return other.value * self.number

解决方案 2:使用类型以外的其他内容。

(见评论)

将逻辑单元分成单独的包和模块可能很有用,但坚持一个文件只包含一个 class 近乎教条。除非这些 classes 很大,否则最好的解决方案是将它们放在同一个模块中。考虑到您不得不采取各种技巧来避免循环导入,那么每个文件只保留一次 class 真的值得吗?

您可以做很多事情。

  1. 只导入模块而不导入class(from module import foo),并通过模块引用class(例如isinstance(obj, foo.Foo)
  2. 使用 Python 的“寻求宽恕,而不是许可”的口头禅。假设您的 classes 的用户正确使用它们并且相关属性存在于操作数中。如果您想要更多信息性错误消息,则可以将属性提取包装在 try: except AttributeError: 语句中,这会引发您喜欢的错误。您必须 确保在此类 try/except 块中放置最少的逻辑。例如。 other_value = other.value,然后在 try/except 块之外使用 other_value 名称。这将阻止您的 try/except 块在您的程序中隐藏可能的错误。
  3. 只能得到一个class来实现__mul__,这个class也可以实现__rmul__来弥补__mul__上的不足其他 class。这允许所有实例检查仅在 class 之一而不是两者中完成。

strint 是标准库使用 __rmul__ 计算结果的例子(例如 2 * 'a')。也就是说,当给定 2 * 'a' 时,解释器首先尝试 int.__mul__(2, 'a')。然而,这个returnsNotImplemented。结果,解释器将尝试 str.__rmul__('a', 2) 代替(returns 'aa')。只有当正常运算符函数 returns NotImplemented 时,你才会得到他的行为。如果存在异常,则传播此异常而不是尝试 __rmul__。在实现运算符的 r 变体时,您应该注意 self 将是表达式中的 right 操作数,有时这可能会改变你想计算结果(例如 3 - 1 != 1 - 3)。

第 3 点的示例。

class X:
    def __init__(self, val):
        self.val = val

class Y:
    def __init__(self, val):
        self.val = val

    def _mul(self, x):
        if not isinstance(x, X):
            return NotImplemented
        return Y(self.val * x.val)

    def __mul__(self, other):
        return self._mul(other)

    def __rmul__(self, other):
        return self._mul(other)


y = Y(2) * X(3)  # using mul
assert isinstance(y, Y)
assert y.val == 6
assert isinstance(X(3) * Y(2), Y) # using rmul