Недавно я писал о том, как работает некоторая магия в Python и как вы можете использовать эту магию, реализуя так называемые методы двойного подчеркивания. Чтобы проиллюстрировать это, я построил игрушечный пример структуры данных Set. Как и любая хорошая структура данных, набор имеет размер — если бы мы собирались протестировать его (а мы должны это сделать, потому что тесты покупают вам свободу и гибкость для легкого изменения вашего кода по мере его развития), он мог бы выглядеть так: :

def test_size():
    empty = Set()
    has_stuff = Set()
    has_stuff.add("waffles")
    has_stuff.add("cheese")

    assert empty.size() == 0
    assert has_stuff.size() == 2

Теперь, когда у нас есть провальный тест, давайте сделаем так, чтобы он прошел

class Set:
    def __init__(self):
        self.__values = []
        self.__size = 0

    def add(self, value):
        if value in self.__values:
            return
        self.__size += 1
        self.__values.append(value)

    def size(self):
        return self.__size

В этот момент я почти слышу, как какой-то благонамеренный питонист кричит:

"Привет! Это питон, нам весь этот багаж не нужен. Давайте просто воспользуемся переменной экземпляра и избежим лишнего кода. Это проще!»

def test_size():
    empty = ListSet()
    has_stuff = Set()
    has_stuff.add("waffles")
    has_stuff.add("cheese")

    assert empty.__size == 0
    assert has_stuff.__size == 2

Вздох. Питон, я люблю тебя, но иногда ты меня действительно ранишь.

Проблема с раскрытием переменных экземпляра

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

class Node:
    def __init__(self, val):
        self.__value = val
        self.__next = None

    def add(self, next_):
        self.__next = next_
 
    def value(self):
        return self.__value
 
    def next(self):
        return self.__next
 
    def size(self):
        # size must be at least the current node
        size = 1
        next_ = self.next()

        # continue to iterate while we have more nodes…