什么是构念的Pythonic等价物?

2024-10-04 07:33:56 发布

您现在位置:Python中文网/ 问答频道 /正文

关于Python中缺少const正确性和缺少真正的private成员,已经有很多争论(至少是这样)。我正在努力适应Python式的思维方式

假设我想要实现一个燃料箱。它有容量,可以重新加注,也可以消耗燃料。因此,我将按如下方式实施:

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的属性设置器,我通知客户机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。他唯一能做的事就是去做

过了一段时间,我的客户抱怨说他需要一种方法来知道油箱里还有多少燃油。因此,我决定添加第二个方法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作为公共属性稍微安全一些:

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

并保存额外的代码行

总之,我有三个选择:

  1. Car中为FuelTank的每个允许Car的客户端使用的FuelTank方法编写包装器方法(不可伸缩且难以维护)
  2. 保持_tank(名义上)私有,并允许客户端作为仅getter属性访问它。这只会保护我不受可能试图将tank设置为完全不同对象的过分“白痴”客户端的影响。但是,否则就等于公开了tank
  3. 公开tank,并询问客户“请不要打电话给Car.tank.consume

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

在C++中,我将在^ {< CD34>}类中做出^ {CD7>}和^ {CD4>}方法,并声明为{{CD22>}作为^ ^ < CD10>}的私有成员,其中^ {< CD37 > }方法返回^ {< CD1>} -引用到{{CD22>}。这样,我只需要一个refill的包装器方法,并且我允许客户机完全访问Tank的任何const成员(未来的维护成本为零)。就品味而言,我发现这是Python所缺乏的一个重要特性


澄清。

<>我明白在C++中可以实现的几乎是不可能在Python中实现的(因为它们的根本不同)。我主要想弄清楚的是,三个备选方案中哪一个是最具python风格的?方案(2)是否比方案(3)有任何特殊优势?有没有办法使选项(1)可扩展


Tags: 方法self客户端returndefpropertycarlevel
1条回答
网友
1楼 · 发布于 2024-10-04 07:33:56

由于Python没有任何标准的标记方法const,因此不可能有内置的方法来提供限制访问它们的值(,即,一个对象)。然而,有两种习惯用法可以用来提供类似的东西,这两种习惯用法都通过Python的动态属性和反射功能变得更容易

如果一个类被设计为支持这个用例,您可以拆分它的接口:只提供“real”类型的读接口,然后提供一个提供写接口并将任何未知调用转发给读卡器的包装器:

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不能表达^ {CD4}},所以我们必须使用一个单独的对象。它也不能由一个来构造:客户端不能认为他们应该这样做是为了获得写访问权

如果类不是为此而设计的,则可以反转包装:

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)

这显然是更多的工作,因为您必须创建一个完整的黑名单(或白名单,尽管这样做会更难生成有用的错误消息)。但是,如果类是协作的,您可以将此方法与标记一起使用(例如,一个函数属性),以避免显式列表和两个类

相关问题 更多 >