Внутри метода __repr__() мы используем self.__class__.__qualname__ вместо того, чтобы жестко фиксировать строку 'WizCoin'; при субклассировании WizCoin унаследованный метод __repr__() будет использовать имя субкласса, а не 'WizCoin'. Кроме того, если класс WizCoin будет переименован, метод __repr__() автоматически использует обновленное имя.
Имя функции Python super() выбрано неудачно, потому что возвращает она не родительский класс, а следующий класс в MRO. Это означает, что при вызове getBoardStr() для объекта HybridBoard следующим классом в MRO после HintBoard будет MiniBoard, а не родительский класс TTTBoard. Таким образом, вызов super().getBoardStr() вызывает метод getBoardStr() класса MiniBoard, который возвращает компактное игровое поле. Оставшийся код getBoardStr() класса HintBoard после этого вызова super() присоединяет текст подсказки к строке.
Функции len() можно передать строку, чтобы узнать, сколько символов она содержит, но len() также можно передать список или словарь, чтобы узнать, сколько элементов или пар «ключ — значение» они содержат. Эта разновидность полиморфизма называется параметрическим полиморфизмом, или обобщением, потому что она может работать с объектами многих разных типов.
Термином «полиморфизм» также иногда обозначают ситуативный (ad hoc) полиморфизм, или перегрузку операторов, когда операторы (такие как + или *) демонстрируют разное поведение в зависимости от типа объектов, с которыми они работают. Например, оператор + выполняет математическое сложение для двух целых чисел или чисел с плавающей точкой, но с двумя строками он выполняет конкатенацию.
Слово «инкапсуляция» имеет два распространенных и взаимосвязанных определения. Первое: инкапсуляцией называется объединение взаимосвязанных данных и кода в одно целое. В сущности, именно это и делают классы: они объединяют взаимосвязанные атрибуты и методы. Например, наш класс WizCoin инкапсулирует три целых числа (knuts, sickles и galleons) в одном объекте WizCoin.
Второе определение: инкапсуляцией называется механизм сокрытия информации, позволяющий объектам скрывать сложные подробности реализации, то есть внутреннее устройство объекта. Пример такого рода встречался в подразделе «Приватные атрибуты и приватные методы», с. 324, где объекты BankAccount предоставляют методы deposit() и withdraw() для сокрытия подробностей работы с атрибутами _balance. Функции позволяют осуществить похожую цель создания «черного ящика» — например, алгоритм вычисления квадратного корня функцией math.sqrt() не виден пользователю. Все, что вам нужно знать, — эта функция возвращает квадратный корень того числа, которое ей было передано.
Композиция — прием проектирования классов, основанный на включении объектов в класс (вместо наследования классов этих объектов).
Если вы захотите больше узнать обо всем этом, я рекомендую доклад «Schemas for the Real World» Карины Зона (Carina C. Zona) на конференции PyCon 2015 (https://youtu.be/PYYfVqtcWQY/) и доклад «Hi! My name is…» Джеймса Беннета (James Bennett) на конференции North Bay Python 2018 (https://youtu.be/NIebelIpdYk/). Также заслуживают внимания популярные публикации в блоге «Falsehoods Programmers Believe»; в них рассматриваются такие темы, как карты, адреса электронной почты и другие виды данных, которые программисты часто представляют неправильно. Подборка ссылок на эти статьи доступна на https://github.com/kdeldycke/awesome-falsehood/. Кроме того, удачный пример неудачного отражения сложности реального мира показан в видеоролике CGP Grey «Social Security Cards Explained» (https://youtu.be/Erp8IAUouus/).
>>> type(42).__qualname__ # Атрибут __qualname__ предоставляет более понятную информацию.
'int'
Если обозначить переменной n размер данных, с которыми работает код, можно воспользоваться рядом общих правил.
• Если код не обращается ни к каким данным, это O(1).
• Если код последовательно перебирает данные, это O(1).
• Если код содержит два вложенных цикла, каждый из которых перебирает данные, это O(n2).
• Вызовы функций включаются в подсчеты не как один шаг, а как общее количество шагов кода внутри функции. См. подраздел «Порядок “О-большое” для часто используемых функций», с. 283.
• Если код содержит операцию «разделяй и властвуй», которая многократно делит данные надвое, это O(log n).
• Если код содержит операцию «разделяй и властвуй», которая выполняется по одному разу для каждого элемента данных, это O(n log n).
• Если код перебирает все возможные комбинации значений в данных с размером n, это O(2n) или другой экспоненциальный порядок.
• Если код перебирает все возможные перестановки (то есть варианты упорядочения) значений данных, это O(n!).
• Если код включает сортировку данных, это как минимум O(n log n).
len(s) — операция O(1), потому что Python хранит количество элементов в последовательности, чтобы их не приходилось заново пересчитывать при каждом вызове len().
Число делений любого списка размера n надвое составляет log2(n). (К сожалению, это просто математический факт, который вы должны знать.) Таким образом, у цикла while порядок нотации «О-большое» составляет O(log n).
