【问题标题】:Where should I check state / throw exception?我应该在哪里检查状态/抛出异常?
【发布时间】:2009-10-16 14:28:38
【问题描述】:

我的情况是这样的:

class AbstractClass:
    def __init__(self, property_a):
        self.property_a = property_a

    @property
    def some_value(self):
        """Code here uses property_a but not property_b to determine some_value"""

    @property
    def property_a(self):
        return self.property_a

    @property
    def property_b(self):
        """Has to be implemented in subclass."""
        raise NotImplementedError


class Concrete1(AbstractClass):
    """Code here including an implementation of property_b"""


class Concrete2(AbstractClass):
    """Code here including an implementation of property_b"""

还有一个条件,如果property_b小于property_a,那么property_a是无效的,那么some_value的结果也是无效的。

我的意思是……如果在对象的生命周期中的任何时候,调用property_b 会产生一个低于调用property_a 的数字,那就有问题了。但是,property_b 不是字段。它是根据 n 字段动态确定的,其中 n >= 1。在设置 property_b 时无法检查此条件,因为从未设置过 property_b 本身。实际上,预计不会在此处任何地方使用 setter。所有字段都可能在构造函数中设置,然后单独放置。这意味着 property_a 将在 AbstractClassproperty_b 的构造函数中知道评估具体类的构造函数之后。


根本问题是:我需要检查property_a 的有效性,但是当设置property_a 时(最直观的检查位置),property_b 未定义。

我想确保property_b 永远不会小于property_a。我该如何处理?

检查property_aproperty_b in...

  1. AbstractClass.__init__。这实际上是不可能的,因为尚未定义 property_b
  2. AbstractClass.property_a。这似乎有问题,因为我会在 getter 中抛出异常。
  3. property_b 的每个具体实现。我不仅会在 getter 中抛出异常,还会复制代码。此外property_b 在逻辑上不依赖于property_a
  4. AbstractClass.some_value。这仍然在 getter 中引发异常。此外,property_b一直小于property_a 在逻辑上是不可能的,不仅仅是在尝试确定some_value 时。此外,如果子类决定添加依赖于property_a 的其他属性,他们可能会忘记对照property_b 进行检查。
  5. property_b 的混凝土固定器。这些是不存在的。 property_b 有时由构造函数中设置的值确定,有时由多个值计算得出。另外,代码重复。
  6. 具体类__init__ 方法。代码重复。有人可能会忘记。
  7. ???

更新

我认为引起混淆的是property_b 不仅仅是一个字段。 property_b 依赖于计算。如果这样考虑的话,它实际上更像是一个函数而不是一个属性。

【问题讨论】:

  • 请记住,我是作为自己做过这件事的人说的:您是否认为您在某处使事情过于复杂?我不太了解您的问题,但定义三个属性肯定不会this 复杂。你确定没有 90% 的解决方案可以实施吗?
  • 好吧,我可以将属性 b 和一些值提取到子类中,但是我会重复代码,而我的架构的整个目标是让超类尽可能多地完成子类的工作尽可能小。我在这个模块中有 4 个级别的 12 个类,但实际代码只有 140 行。该架构几乎可以轻松解释,反映了问题域,并以“叶子”类结束,这些类非常小,即使有(大量)文档,通常也可以将它们放在一个屏幕上。
  • 我认为 90% 的解决方案将采用 Alex Martelli 的建议,但为了简化事情,让子类永远不会改变任何影响属性 b 的东西。然后我可以在每个构造函数的末尾调用验证器。

标签: python exception


【解决方案1】:

添加一个方法_validate_b(self, b)(单前导下划线表示“受保护”,即,可从派生类调用,但不能由一般客户端代码调用)验证 b 的值(只有子类知道)与 a (抽象超类确实知道)。

让子类负责调用验证方法,只要他们正在做的事情可能会改变他们对 b 的内部值。在您非常普遍的情况下,超类无法识别 b 的值何时发生变化;由于该更改的责任完全在于子类,因此触发验证的责任也必须与它们一起(父类可以执行验证,给定 b 的提议新值,但它不知道 when 必须检查有效性)。所以,清楚地记录下来。

如果大多数子类在影响其 b 值的策略方面属于广泛类别,您可以将其分解为任一中间抽象类(从通用类继承并专门用于“确定 b”的通用方法,包括验证调用),或(如果可行,更好)某种形式的策略设计模式(通常通过组合或混合继承实现)。但这更多是为了方便(对于具体子类作者)而不是“保证正确性”,因为给定的具体子类可能绕过这些机制。

如果需要,您可以提供“调试/测试模式”,其中在访问时对属性进行冗余验证(在生产使用中可能不建议使用,但对于调试和测试,这将有助于捕获未正确调用验证的错误子类方法)。

【讨论】:

  • @Daniel,不客气!我希望有一种更好的自动化方法,但我确实认为这种更明确的方法会更适合您的目的。
【解决方案2】:

黄金法则是“封装”property_b,以便子类提供部分实现,但不是全部。

class AbstractClass:
    def __init__(self, property_a):
        self._value_of_a = property_a

    @property
    def get_b( self ):
       self.validate_a()
       self._value_of_b = self.compute_b()
       self.validate_other_things()
       return self._value_of_b

   def compute_b( self ):
       raise NotImplementedError

当你有两个班级并且你询问责任分配时,很难准确地说出应该发生什么。

您似乎希望超类负责 a 和 b 之间关系的某些方面

您似乎希望子类负责计算 b 的其他方面,而不是负责关系。

如果这是您想要的,那么您的设计必须通过将事物分解为超类负责的部分和子类负责的部分来分配责任。

【讨论】:

  • 这让我想到了一些其他的东西。尽管我的解释不清楚,但感谢您坚持下去。
  • 不清楚的解释(有时)是一些简单的东西被埋在混乱或无用的复杂性之下的症状。 90% 的情况下,复杂性是未能正确分配责任的结果。这通常是因为没有简单明确地指定职责或参与者。当你简化和澄清你的解释时,你就去掉了无用的复杂性。继续简化。
【解决方案3】:

我建议您不要引发raise NotImplementedError,而是调用一个引发此错误的方法。然后子类必须覆盖该方法(而不是property_b)。在property_b中,调用方法,然后验证结果。

理由:您应该尽快检查该值(即有人更改它时)。否则,可能会设置一个非法值并在代码中导致问题,因为没有人能说出它是如何到达那里的。

或者,您可以存储值和堆栈跟踪。使用该值时,您可以检查该值并将原始堆栈跟踪打印为“值已在此处更改”。

【讨论】:

  • 我想我可能有点不清楚。这是案例 3。属性 b 不是真正需要检查的。属性 b 的有效性根本不依赖于属性 a。
  • 另外,当设置了属性a时,没有办法检查它。必须先定义属性 b,然后才能检查属性 a。
  • 您必须在拥有这两个属性后立即进行检查。创建一个 validate 方法,并在两个属性都有效时从两个 setter 调用它。在基类中执行此操作并让派生类实现辅助方法以避免重复代码。
猜你喜欢
  • 1970-01-01
  • 2013-02-08
  • 1970-01-01
  • 1970-01-01
  • 2016-03-10
  • 1970-01-01
  • 1970-01-01
  • 2019-01-15
  • 1970-01-01
相关资源
最近更新 更多