Что такое Pythoni c эквивалент const-ссылок? - PullRequest
2 голосов
/ 20 марта 2020

Было много дискуссий (по крайней мере, о SO) об отсутствии const -корректности и отсутствии истинных private членов в Python. Я пытаюсь привыкнуть к мышлению Pythoni c.

Предположим, я хочу реализовать топливный бак. Он имеет емкость, может быть пополнен или из него можно потреблять топливо. Поэтому я реализовал бы это следующим образом:

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, но, пожалуйста, используйте методы consume или refill, чтобы изменить его.

Теперь я реализую 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 publi c:

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

и сохранить дополнительные строки кода.

Итак, в заключение, я ' у нас есть три варианта:

  1. Запись метода-обертки в Car для каждого метода FuelTank, который разрешено использовать клиенту Car (не масштабируемый и трудно поддерживаемый).
  2. Сохранение _tank (номинально) закрытого типа и предоставление клиенту доступа к нему в качестве свойства только для получения. Это только защищает меня от чрезмерно «идиотского» клиента, который может попытаться установить tank на совершенно другой объект. Но в остальном все равно, что сделать tank publi c.
  3. Сделать tank publi c и попросить клиента "пожалуйста, не звоните Car.tank.consume"

Мне было интересно, какой вариант считается лучшим в мире Pythoni c?

Примечание в C ++ Я бы сделал level и capacity методы const в Tank класс и объявил tank как закрытый член Car с методом get_tank(), который возвращает const -ссылку на tank. Таким образом, мне понадобится только один метод-обертка для refill, и я предоставлю клиенту полный доступ ко всем const членам Tank (с нулевыми затратами на обслуживание в будущем). По вкусу я нахожу это важной особенностью, которой не хватает Python.


Разъяснение.

Я понимаю, чего можно достичь в C ++ почти наверняка невозможно достичь в Python (из-за их фундаментальных отличий). В основном я пытаюсь выяснить, какая из трех альтернатив является самой Pythoni c? Есть ли у варианта (2) какое-либо конкретное преимущество перед вариантом (3)? Есть ли способ сделать вариант (1) масштабируемым?

1 Ответ

2 голосов
/ 21 марта 2020

Поскольку Python не имеет стандартного способа маркировки метода const, не может быть встроенного способа предоставления значения ( т.е. , объект), который ограничивает доступ к ним. Однако есть две идиомы, которые могут быть использованы для обеспечения чего-то похожего, оба облегченные благодаря динамическим атрибутам Python c и средствам отражения.

Если класс должен быть разработан для поддержки этого варианта использования, вы может split его интерфейс: предоставить только интерфейс чтения для «реального» типа, затем предоставить оболочку, которая предоставляет интерфейс записи и перенаправляет любые неизвестные вызовы читателю:

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 не может express 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)

Это, очевидно, больше работы, так как вы должны сделать полный черный список (или белый список, хотя тогда будет немного сложнее создавать полезные сообщения об ошибках). Однако, если класс является кооперативным, вы можете использовать этот подход с тегами ( например , атрибут функции), чтобы избежать явного списка и иметь два класса.

...