const-references 的 Pythonic 等价物是什么?

What is the Pythonic equivalent of const-references?

关于const-正确性和 Python 中缺少真正的 private 成员的争论(至少在 SO 上)有很多。我正在努力适应 Pythonic 的思维方式。

假设我想实现一个油箱。它有容量,可以重新装满,也可以从中消耗燃料。所以我会按如下方式实现它:

class FuelTank:

    def __init__(self, capacity):

        if capacity < 0:
            raise ValueError("Negative capacity")
        self._capacity = capacity

        self._level = 0.0

    @property
    def capacity(self):
        return self._capacity

    @property
    def level(self):
        return self._level

    def consume(self, amount):

        if amount > self.level:
            raise ValueError("amount higher than tank level")

        self._level -= amount


    def refill(self, amount):

        if amount + self.level > self.capacity:
            raise ValueError("overfilling the tank")

        self._level += amount

到目前为止,我已经在我的代码中加入了一定程度的 const-正确性:通过不为 capacity 实现 属性 setter 我通知客户capacity对象构造完成后不能更改。 (尽管从技术上讲,这总是可以通过直接访问 _capacity 来实现。)同样,我告诉客户您可以阅读 level 但请使用 consumerefill 方法来更改它。

现在,我实现了一个 Car,它有一个 FuelTank:

class Car:
    def __init__(self, consumption):
        self._consumption = consumption
        self._tank = FuelTank(60.0)


    @property
    def consumption(self):
        return self._consumption

    def run(self, kms):

        required_fuel = kms * self._consumption / 100

        if required_fuel > self._tank.level:
            raise ValueError("Insufficient fuel to drive %f kilometers" %
                             kms)

        self._tank.consume(required_fuel)

    def refill_tank(self, amount):

        self._tank.refill(amount)

我再次暗示客户端不应该直接访问 _tank。他唯一能做的就是refill_tank.

一段时间后,我的客户抱怨说他(她)需要一种方法来了解油箱中还剩多少燃油。所以,我决定添加第二种方法 tank_level

    def tank_level(self):
        return self._tank.level

担心 tank_capacity 很快就会成为必需,我开始在 Car 中添加包装器方法以访问 FuelTank 中除 consume 之外的所有方法。这显然不是一个可扩展的解决方案。所以,我可以选择将以下 @property 添加到 Car

    @property
    def tank(self):
        return self._tank

但现在客户端无法理解 consume 方法不应被调用。事实上,这个实现只比将 tank 设置为 public 属性稍微安全一点:

    def __init__(self, consumption):
        self._consumption = consumption
        self.tank = FuelTank(60.0)

并节省额外的代码行。

所以,总而言之,我有三个选择:

  1. Car 中为 Car 的客户端允许使用的每个 FuelTank 方法编写一个包装器方法(不可扩展且难以维护)。
  2. 保持 _tank(名义上)私有并允许客户端作为 getter-only 属性 访问它。这只会保护我免受可能试图将 tank 设置为完全不同的对象的过度 'idiot' 客户端的影响。但是,除此之外就和制作 tank public.
  3. 一样好
  4. 制作tankpublic,询问客户"please do not call Car.tank.consume"

我想知道哪个选项被认为是 Pythonic 世界中的最佳实践?

注意在 C++ 中我会在 Tank class 中创建 levelcapacity 方法 const 并声明 tank 为私有Car 的成员,使用 get_tank() 方法 returns 对 tankconst 引用。这样,我只需要 refill 的一个包装器方法,并且我授予客户端对 Tank 的任何 const 成员的完全访问权限(未来维护成本为零)。就品味而言,我发现这是 Python 所缺少的重要功能。


澄清。

我知道在 C++ 中可以实现的东西在 Python 中几乎肯定是不可能实现的(由于它们的根本差异)。我主要想弄清楚的是三种选择中哪一种是最 Pythonic 的?选项 (2) 是否比选项 (3) 有任何特殊优势?有没有办法使选项 (1) 可扩展?

由于 Python 没有任何标准方法来 标记 方法 const,因此无法 构建-in 提供限制访问它们的值(,一个对象)的方式。然而,有两个习语可用于提供类似的东西,Python 的动态属性和反射功能使它们变得更容易。

如果要设计一个class来支持这个用例,你可以拆分它的接口:在“real”类型上只提供读接口,然后提供一个包装器,该包装器提供写入接口并将任何未知调用转发到 reader:

class ReadFoo:
  def __init__(self): self._bar=1
  @property
  def bar(self): return self._bar
class Foo:
  def __init__(self): self._foo=ReadFoo()
  def read(self): return self._foo
  def more(self): self._foo._bar+=1
  def __getattr__(self,n): return getattr(self._foo,n)

class Big:
  def __init__(self): self._foo=Foo()
  @property
  def foo(self): return self._foo.read()

请注意 Foo 不会 ReadFoo 继承 ; Python 和 C++ 的另一个区别是 Python 不能表达 Base &b=derived;,所以我们必须使用一个单独的对象。它也不能从一个构造:客户不能认为他们应该这样做以获得写访问权。

如果 class 不是为此设计的,您可以 反转 包装:

class ReadList:
  def __init__(self,l): self._list=l
  def __getattr__(self,n):
    if n in ("append","extend","pop",…):
      raise AttributeError("method would mutate: "+n)
    return getattr(self._list,n)

这显然需要更多的工作,因为您必须制作一个 完整的 黑名单(或白名单,尽管这样制作有用的错误消息会有点困难)。但是,如果 class 是合作的,您可以将此方法与 tags例如,一个函数属性)一起使用,以避免显式列出并有两个 classes.