17. Наследование¶

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

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

Наследование - мощная функция. Некоторые программы, которые были бы сложными без наследования, можно было бы кратко и просто написать с его помощью. Кроме того, наследование может облегчить повторное использование кода, поскольку вы можете настраивать поведение родительских классов, не изменяя их. В некоторых случаях структура наследования отражает естественную структуру проблемы, что упрощает понимание программы.

С другой стороны, наследование может затруднить чтение программ. Когда вызывается метод, иногда неясно, где найти его определение. Соответствующий код может быть разбросан по нескольким модулям. Кроме того, многие вещи, которые можно сделать с помощью наследования, можно сделать так же элегантно (или даже более того) без него. Если естественная структура проблемы не поддается наследованию, такой стиль программирования может принести больше вреда, чем пользы.

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

17.2. Карточная рука¶

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

Рука также отличается от колоды. В зависимости от игры, в которую мы играем, мы можем захотеть выполнить некоторые операции с руками, которые не имеют смысла для колоды. Например, в покере мы можем классифицировать руку (стрит, флеш и т. Д.) Или сравнить ее с другой рукой. В бридже мы могли бы захотеть подсчитать счет для руки, чтобы сделать ставку.

Эта ситуация предполагает использование наследования. Если Hand является подклассом Deck , он будет иметь все методы Deck , и могут быть добавлены новые методы.

В определении класса имя родительского класса указано в скобках:

Этот оператор указывает, что новый класс Hand наследуется от существующего класса Deck .

Конструктор Hand инициализирует атрибуты руки, а именно имя и карты . Строка имя идентифицирует эту руку, вероятно, имя игрока , который держит его. Имя - это необязательный параметр с пустой строкой в ​​качестве значения по умолчанию. cards - это список карт в руке, инициализированный пустым списком:

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

Опять же, многоточие означает, что мы пропустили другие методы. Метод list append добавляет новую карточку в конец списка карточек.

17.3. Сдача карт¶

Теперь, когда у нас есть класс Hand , мы хотим раздать карты из колоды в руки. Не сразу очевидно, должен ли этот метод входить в класс Hand или в класс Deck , но, поскольку он работает с одной колодой и (возможно) несколькими руками, более естественно поместить его в Deck .

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

deal принимает два параметра: список (или кортеж) рук и общее количество карт для раздачи. Если в колоде недостаточно карт, метод раздает все карты и останавливается:

Второй параметр, num_cards , необязательный; по умолчанию используется большое число, что фактически означает, что все карты в колоде будут сданы.

Переменная цикла i изменяется от 0 до nCards-1 . Каждый раз в цикле карта удаляется из колоды с помощью метода списка pop , который удаляет и возвращает последний элемент в списке.

Оператор модуля ( % ) позволяет нам раздавать карты по круговой системе (по одной карте за раз в каждую руку). Когда i равно количеству рук в списке, выражение i% nHands переносится в начало списка (индекс 0).

17.4. Печать руки¶

Чтобы распечатать содержимое руки, мы можем воспользоваться методами printDeck и __str__, унаследованными от Deck . Например:

Это не самая лучшая рука, но у нее есть все признаки стрит-флеша.

Хотя наследовать существующие методы удобно, в объекте Hand есть дополнительная информация, которую мы могли бы включить при печати. Для этого мы можем предоставить метод __str__ в классе Hand, который переопределяет метод в классе Deck :

Изначально s - это строка, идентифицирующая руку. Если рука пуста, программа добавляет пустые слова и возвращает s .

В противном случае программа добавляет слово contains и строковое представление Deck , вычисленное путем вызова метода __str__ в классе Deck для себя .

Может показаться странным отправлять self , который относится к текущей руке , в метод Deck , пока вы не вспомните, что Hand - это разновидность колоды . Объекты Hand могут делать все, что могут объекты Deck , поэтому отправка Hand в метод Deck является законной .

В общем, всегда допустимо использовать экземпляр подкласса вместо экземпляра родительского класса.

17.5. CardGame class¶

Класс CardGame выполняет некоторые основные обязанности, общие для всех игр, такие как создание колоды и ее перемешивание:

Это первый случай, который мы видели, когда метод инициализации выполняет важные вычисления, помимо инициализации атрибутов.

Чтобы реализовать определенные игры, мы можем унаследовать от CardGame и добавить функции для новой игры. В качестве примера напишем симулятор Old Maid.

Цель Old Maid - избавиться от карт в руке. Вы делаете это, сопоставляя карты по рангу и цвету. Например, 4 треф соответствует 4 пик, поскольку обе масти черные. Червовый валет соответствует бубновому валету, так как оба красные.

Чтобы начать игру, трефовая дама удаляется из колоды, так что у пиковой дамы нет соперников. Пятьдесят одна оставшаяся карта раздается игрокам по круговой системе. После раздачи все игроки складывают и сбрасывают как можно больше карт.

Когда больше нет совпадений, начинается игра. В свою очередь, каждый игрок берет карту (не глядя) у ближайшего соседа слева, у которого все еще есть карты. Если выбранная карта совпадает с картой в руке игрока, пара удаляется. В противном случае карта добавляется в руку игрока. В конце концов, все возможные совпадения совпадают, и в руке проигравшего остается только Пиковая дама.

В нашем компьютерном моделировании игры все руки играет компьютер. К сожалению, теряются некоторые нюансы реальной игры. В реальной игре игрок со Старой Девой прилагает некоторые усилия, чтобы заставить своего соседа выбрать эту карту, показывая ее немного заметнее, или, возможно, не показывает ее более заметно, или даже не показывает эту карту. более заметно. Компьютер просто случайным образом выбирает карту соседа.

17.6. OldMaidHand класс¶

Рука для игры в Old Maid требует некоторых способностей, выходящих за рамки общих способностей руки . Мы определим новый класс OldMaidHand , который наследуется от Hand и предоставляет дополнительный метод remove_matches :

Мы начинаем с создания копии списка карт, чтобы мы могли перемещаться по копии, удаляя карты из оригинала. Поскольку self.cards изменяется в цикле, мы не хотим использовать его для управления обходом. Python может сильно запутаться, если просматривает список, который меняется!

Для каждой карты в руке мы выясняем, какая карта подходит, и ищем ее. Карта матча имеет тот же ранг, а другая масть того же цвета. Выражение 3 - card.suit превращает булаву (масть 0) в лопату (масть 3) и алмаз (масть 1) в сердце (масть 2). Вы должны убедиться, что противоположные операции также работают. Если карта матча также находится в руке, обе карты удаляются.

В следующем примере показано, как использовать remove_matches :

Обратите внимание, что для класса OldMaidHand нет метода __init__ . Мы наследуем его от Hand .

17.7. Класс OldMaidGame¶

Теперь мы можем обратить внимание на саму игру. OldMaidGame - это подкласс CardGame с новым методом play, который принимает список игроков в качестве параметра.

Поскольку __init__ унаследован от CardGame , новый объект OldMaidGame содержит новую перетасованную колоду:

Написание printHands () оставлено как упражнение.

Некоторые этапы игры разделены на методы. remove_all_matches просматривает список рук и вызывает remove_matches для каждой:

count - это аккумулятор, который суммирует количество совпадений в каждой руке и возвращает общее количество.

Когда общее количество матчей достигает двадцати пяти, пятьдесят карт удаляются из рук, что означает, что осталась только одна карта и игра окончена.

Переменная поворот отслеживает, какой сейчас ход игрока. Он начинается с 0 и каждый раз увеличивается на единицу; когда он достигает numHands , оператор модуля возвращает его обратно к 0.

Метод playOneTurn принимает параметр, указывающий, чья это очередь. Возвращаемое значение - количество совпадений, сделанных за этот ход:

Если рука игрока пуста, этот игрок выбывает из игры, поэтому он или она ничего не делает и возвращает 0.

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

Метод find_neighbor начинается с игрока слева и продолжается по кругу, пока не найдет игрока, у которого все еще есть карты:

Если find_neighbor когда-либо пройдет весь круг, не найдя карточек, он вернет None и вызовет ошибку в другом месте программы. К счастью, мы можем доказать, что этого никогда не произойдет (если конец игры определен правильно).

Мы пропустили метод print_hands . Вы можете написать это сами.

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

ПОПУЛЯРНЫЕ СТАТЬИ