关于 Python 中缺乏 const
正确性和缺乏真正的 private
成员,存在很多争论(至少在 SO 上)。我正在努力适应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
的属性 setter ,我通知客户端 capacity
对象构造后就无法更改。 (尽管从技术上讲,通过直接访问 _capacity
总是可以实现这一点。)同样,我告诉客户端您可以读取 level
但请使用 consume
或 refill
方法来更改它。
现在,我实现了一辆带有 FuelTank
的 Car
:
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
的所有方法,除了消费
。这显然不是一个可扩展的解决方案。因此,我也可以将以下 @property
添加到 Car
@property
def tank(self):
return self._tank
但是现在客户端无法理解不应调用 consume
方法。事实上,此实现仅比仅将 tank
设为公共(public)属性稍微安全一些:
def __init__(self, consumption):
self._consumption = consumption
self.tank = FuelTank(60.0)
并节省额外的代码行。
总而言之,我有三个选择:
- 在
Car
中为允许Car
客户端使用的FuelTank
的每个方法编写一个包装方法(不可扩展且难以扩展)维持)。 - 保持
_tank
(名义上)私有(private),并允许客户端将其作为仅 getter 属性进行访问。这只能保护我免受过度“白痴”客户端的侵害,该客户端可能会尝试将tank
设置为完全不同的对象。但是,否则就和公开tank
一样好。 - 公开
tank
,并询问客户“请不要调用Car.tank.consume
”
我想知道哪个选项被认为是 Pythonic 世界中的最佳实践?
注意,在 C++ 中,我会在 Tank
类中创建 level
和 capacity
方法 const
并声明 tank
作为 Car
的私有(private)成员,使用 get_tank()
方法返回对 tank 的
。这样,我只需要一个用于 const
引用refill
的包装方法,并且我为客户端提供了对 Tank
的任何 const
成员的完全访问权限(零 future 的维护费用)。就个人喜好而言,我发现这是 Python 所缺乏的一个重要功能。
澄清。
我知道在 C++ 中可以实现的东西在 Python 中几乎肯定是不可能实现的(由于它们的根本差异)。我主要想弄清楚这三种选择中哪一种是最 Pythonic 的?选项(2)比选项(3)有什么特别的优势吗?有没有办法使选项(1)可扩展?
最佳答案
由于 Python 没有任何标记方法的标准方式 const
,不可能有一种内置方式来提供限制对它们的访问的值(即,一个对象)。然而,有两种习惯用法可以用来提供类似的东西,这两种习惯用法都可以通过 Python 的动态属性和反射工具变得更容易。
如果要设计一个类来支持此用例,您可以拆分其接口(interface):仅提供“真实”类型的读取接口(interface),然后提供提供写入接口(interface)的包装器并将任何未知调用转发给读者:
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 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)
这显然需要更多工作,因为您必须创建一个完整黑名单(或白名单,尽管这样会更难制作有用的错误消息)。不过,如果类是协作的,您可以将此方法与标签(例如,函数属性)结合使用,以避免显式列表和拥有两个类。
关于python - const 引用在 Python 中的等价物是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60778937/