【问题标题】:Handling class properties with unknown default value in Python在 Python 中处理具有未知默认值的类属性
【发布时间】:2018-11-01 17:42:22
【问题描述】:

我有一个类似以下的课程:

class Invoice
    def __init__(self, invoice_id):
        self.invoice_id = invoice_id
        self._amount = None

    @property
    def amount(self):
        return self._amount

    @amount.setter
    def amount(self, amount):
        self._amount = amount

在上面的示例中,每当我尝试获取发票金额而不设置其值时,我都会得到一个无,如下所示:

invoice = Invoice(invoice_id='asdf234')
invoice.amount
>> None

但在这种情况下,无不是正确的金额默认值。我应该能够区分金额值为无与根本没有设置的金额值。所以问题如下:

  1. 当类属性没有正确的默认值时,我们如何处理?在上面的示例中,如果我从 init 中删除 self._amount = None,我将得到 self._amount 的 AttributeError,并且 self.amount 仅在我调用 invoice.amount = 5 后才会返回有效值。这是处理它的正确方法吗?但这也会导致对象状态不一致,因为应用程序会在运行时更改实例属性。
  2. 我保留了金额作为发票的属性,以便更好地理解和阅读发票及其属性。是否应该仅在我们知道其初始化/默认值时才使用类属性?

【问题讨论】:

  • 如果@property 的作用与字段完全一样,为什么还要存在呢?无论如何,决定您想要哪种方法的一些相关问题是:1)带有amountNone 的发票是什么意思? 2) 是一张从未设置过amount 的发票应该存在,还是它的存在总是表示编程错误?
  • 假设我的应用程序正在侦听一个外部源,它在两个不同的事件中向我发送发票 ID 及其金额。对于第一个事件,我创建一个没有金额的发票,根据是否设置金额做出一些决定。在第二个事件中,一个新实例也将具有金额值。提供者也可以为金额发送 None 值,在这种情况下,应用程序服务会执行某些其他操作。
  • 那么在你拥有 ID 和数量之前不要创建 Invoice 对象。
  • 但是我仍然需要调用不同的服务,这些服务将发票实例作为输入,并在收到发票 ID 时对第一个事件执行某些操作。
  • 如果发票上有金额,这些服务将不起作用?

标签: python python-3.x oop


【解决方案1】:

None 通常用于缺少值,但有时您需要它实际上是允许的值。如果您希望能够区分 None 和未设置的值,只需为此目的定义您自己的单例即可。

class Undefined:
    __str__ = __repr__ = lambda self: "Undefined"

Undefined = Undefined()

class Invoice
    def __init__(self, invoice_id):
        self.invoice_id = invoice_id
        self._amount = Undefined

    @property
    def amount(self):
        return self._amount

    @amount.setter
    def amount(self, amount):
        if amount is Undefined:
            raise ValueError("amount must be an actual value")
        self._amount = amount

当然,您现在可能需要在其他方法中测试Undefined,以确保在正确初始化实例之前它们没有被使用。更好的方法可能是在初始化期间设置属性并要求将其值传递给__init__()。这样,您可以避免 Invoice 处于无效(未完全初始化)状态。有人仍然可以将_amount 设置为无效值,但他们只会得到他们所要求的麻烦。我们都是成年人。

【讨论】:

    【解决方案2】:

    在这种情况下不要使用property。您的 getter 和 setter 除了返回和设置值之外什么都不做。只需使用普通属性。如果您以后想要控制访问,您可以稍后添加该属性,而无需更改您的类的接口!

    至于处理未设置的值,只需创建自己的对象来表示从未设置过的值。它可以很简单:

    >>> NOT_SET = object()
    >>> class Invoice:
    ...     def __init__(self, invoice_id):
    ...         self.invoice_id = invoice_id
    ...         self.amount = NOT_SET
    ...
    >>> inv = Invoice(42)
    >>> if inv.amount is NOT_SET:
    ...     inv.amount = 1
    ...
    

    如果您想更好地支持打字,也可以使用enum

    【讨论】:

      【解决方案3】:

      您可以创建一个静态类并使用它来确定是否设置了值。

      class AmountNotSet(object):
          pass
      
      
      class Invoice(object):
          def __init__(self, invoice_id):
              self.invoice_id = invoice_id
              self._amount = AmountNotSet
          # ...etc...
      

      然后你可以像这样检查发票是否设置:

      invoice1 = Invoice(1)
      invoice2 = Invoice(2)
      invoice2.amount = None
      
      invoice1.amount is AmountNotSet  # => True
      invoice2.amount is None          # => True
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-10-17
        • 2012-08-15
        • 2014-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-14
        相关资源
        最近更新 更多