Сам_себе_программист._Как_научиться_программировать_и_устроиться. Guide to Programming Professionally
Скачать 3.94 Mb.
|
Глава 14. Еще об объектно- ориентированном программировании Относитесь к своему коду как к поэзии и сводите его к абсолютному минимуму. Илья Дорман В этой главе я описываю дополнительные концепции, относящиеся к объектно- ориентированному программированию. Переменные класса и переменные экземпляра В Python классы являются объектами. Идея пошла из Smalltalk, влиятельного языка программирования, положившего начало объектно ориентированному программи- рованию. Каждый класс в Python — объект, являющийся экземпляром «типа» класса. Python_ex258.py 1 class Square: 2 pass 3 print(Square) >> В этом примере класс Square является объектом, и вы его вывели. У классов есть два типа переменных — переменные класса и переменные эк- земпляра класса. Переменные, которые вам встречались до этого, были перемен- ными экземпляра класса, определяемыми синтаксисом self. _ = _ . Переменные экземпляра класса принадлежат объектам. Python_ex259.py 1 class Rectangle(): 2 def __init__( self , w, l): 3 self .width = w 4 self .len = l 5 def print_size( self ): 6 print("""{} {} 7 """.format( self .width, 8 self .len)) 9 my_rectangle = Rectangle(10, 24) 10 my_rectangle.print_size() >> 10 24 В этом примере width и len — переменные экземпляра класса. 128 Часть II Переменные класса принадлежат объекту, который Python создает для каж- дого определения класса, и объектам, которые они создают. Переменные класса определяются как обычные переменные (но определение осуществляется вну- три класса). Вы можете получить к ним доступ с помощью объектов класса и объ- ектов, созданных объектами класса. Вы получаете к ним доступ так же, как и к переменным экземпляра, добавляя перед именем переменной self. Перемен- ные класса полезны — они позволяют обмениваться данными между всеми экзем- плярами класса без полагания на глобальные переменные. Python_ex260.py 1 class Rectangle(): 2 recs = [] 3 def __init__( self , w, l): 4 self .width = w 5 self .len = l 6 self .recs.append(( self .width, 7 self .len)) 8 def print_size( self ): 9 print("""{} {} 10 """.format( self .width, 11 self .len)) 12 r1 = Rectangle(10, 24) 13 r2 = Rectangle(20, 40) 14 r3 = Rectangle(100, 200) 15 print(Rectangle.recs) >> [(10, 24), (20, 40), (100, 200)] В этом примере вы добавили переменную recs в класс Rectangle. Вы опре- делили ее вне метода __init__, поскольку Python вызывает метод __init__ только при создании объекта, а вы хотите получить доступ к переменной класса, используя объект класса (что не вызывает метод __init__). Затем вы создали три объекта Rectangle. Всякий раз, как создается объект Rectangle , код в методе __init__ присоединяет кортеж, содержащий шири- ну и длину нового объекта, к списку recs. С помощью этого кода каждый но- вый объект Rectangle автоматически добавляется в список recs. Благодаря использованию переменной класса вы смогли обменяться данными между раз- личными объектами, созданными классом, без необходимости использовать гло- бальную переменную. 129 Введение в объектно-ориентированное программирование Магические методы Каждый класс в Python наследуется от родительского класса Object. Python ис- пользует методы, унаследованные от Object, в различных ситуациях — напри- мер, когда вы выводите объект. Python_ex261.py 1 class Lion: 2 def __init__(self, name): 3 self.name = name 4 lion = Lion("") 5 print(lion) >> <__main__.Lion object at 0x101178828> Когда вы выводите объект Lion, Python вызывает в нем магический метод __repr__ , унаследованный этим объектом от Object, и выводит то, что воз- вращает __repr__. Можно переопределить унаследованный метод __repr__, чтобы изменить его вывод. Python_ex262.py 1 class Lion: 2 def __init__( self , name): 3 self .name = name 4 def __repr__( self ): 5 return self .name 6 lion = Lion("") 7 print(lion) >> Поскольку вы переопределили метод __repr__, унаследованный от Object, и изменили его таким образом, чтобы он выводил имя объекта Lion, то когда вы выводите этот объект, выводится его имя — в этом случае — вместо чего-то вроде <__main__.Lion object at 0x101178828>, что возвращал бы метод __repr__. Операнды в выражении должны иметь магический метод, который опера- тор может использовать для оценки выражения. Например, в выражении 2 + 2 каждый объект целого числа имеет магический метод __add__, вызываемый Python при оценке выражения. Если вы определите метод __add__ в классе, то сможете использовать создаваемые им объекты как операнды в выражении с оператором сложения. 130 Часть II Python_ex263.py 1 class AlwaysPositive: 2 def __init__( self , number): 3 self .n = number 4 def __add__( self , other): 5 return abs( self .n + 6 other.n) 7 x = AlwaysPositive(-20) 8 y = AlwaysPositive(10) 9 print(x + y) >> 10 Объекты AlwaysPositive могут использоваться в качестве операндов в выражении с оператором сложения, поскольку вы определили метод __add__. Когда Python определяет значение выражения с оператором сложения, он вы- зывает метод __add__ в первом объекте операнда, передает второй объект опе- ранда в __add__ в качестве параметра и возвращает результат. В этом случае __add__ использует встроенную функцию abs , чтобы вернуть абсолютное значение двух сложенных в выражении чисел. Поскольку вы опре- делили __add__ таким образом, два объекта AlwaysPositive в выражении с оператором сложения всегда будут давать абсолютную величину суммы двух объ- ектов, следовательно, результат выражения всегда будет положительным. Ключевое слово is Ключевое слово is возвращает значение True, если два объекта являются од- ним и тем же объектом, и False в противном случае. Python_ex264.py 1 class Person: 2 def __init__( self ): 3 self .name = ' ' 4 bob = Person() 5 same_bob = bob 6 print(bob is same_bob) 7 another_bob = Person() 8 print(bob is another_bob) >> True >> False 131 Введение в объектно-ориентированное программирование Когда вы используете ключевое слово is в выражении с объектами bob и same_ bob в качестве операндов, выражение принимает значение True, так как обе пере- менные указывают на один и тот же объект Person. Когда вы создаете новый объ- ект Person и сравниваете его с исходным объектом bob, выражение принимает значение False, поскольку переменные указывают на различные объекты Person. Используйте ключевое слово is, чтобы проверить, присвоено ли перемен- ной значение None. Python_ex265.py 1 x = 10 2 if x is None : 3 print("x None :( ") 4 else: 5 print("x None") 6 x = None 7 if x is None : 8 print("x None") 9 else: 10 print("x None :( ") >> x None >> x None :( Словарь терминов Закрытый метод: метод, к которому объект может получить доступ, а клиент — нет. Закрытая переменная: переменная, к которой объект может получить доступ, а клиент — нет. Открытая переменная: переменная, к которой клиент может получить доступ. Переменная класса: переменные класса принадлежат объекту класса и объек- там, которые он создает. Переменная экземпляра: переменная экземпляра принадлежит объекту. Практикум 1. Добавьте переменную square_list в класс Square так, чтобы всякий раз, когда вы создаете новый объект Square, он добавлялся в список. 2. Измените класс Square так, чтобы когда вы выводите объект Square, выво- дилось сообщение с длинами всех четырех сторон фигуры. Например, если вы создадите квадрат при помощи Square(29) и осуществите вывод, Python должен вывести строку 29 29 29 29. 3. Напишите функцию, которая принимает два объекта в качестве параметров и возвращает True, если они являются одним и тем же объектом, и False в противном случае. Решения: chall_1.py — chall_3.py. 132 Часть II Глава 15. Практикум. Часть II Пока код не запустится, все это лишь разговоры. Уорд Каннингем В этой главе вы создадите популярную карточную игру «Пьяница». В этой игре каждый игрок берет из колоды по карте — побеждает тот, у которого карта стар- ше. При создании игры вы будете определять классы, представляющие карту, ко- лоду, игрока и, наконец, саму игру. Карты Ниже показан класс, который моделирует игру в карты. Python_ex266.py 1 class Card: 2 suits = ["", 3 "", 4 "", 5 ""] 6 values = [ None , None ,"2", "3", 7 "4", "5", "6", "7", 8 "8", "9", "10", 9 " ", " ", 10 " ", "B "] 11 def __init__( self , v, s): 12 """suit value — Y """ 13 self .value = v 14 self .suit = s 15 def __lt__( self , c2): 16 if self .value < c2.value: 17 return True 18 if self .value == c2.value: 19 if self .suit < c2.suit: 20 return True 21 else: 22 return False 23 return False 24 def __gt__( self , c2): 133 Введение в объектно-ориентированное программирование 25 if self .value > c2.value: 26 return True 27 if self .value == c2.value: 28 if self .suit > c2.suit: 29 return True 30 else: 31 return False 32 return False 33 def __repr__( self ): 34 v = self .values[ self .value] + " of " \ 35 + self .suits[ self .suit] 36 return v У класса Card есть две переменные класса, suits и values. suits — это список строковых значений, представляющих все масти, которые могут быть у карты: , , , . values — это список строковых значе- ний, представляющих различные номиналы карт: 2 — 10, , , - и B . Элементы под первыми двумя индексами в списке values являются None — для того, чтобы строки в списке совпадали с индексами, которые они представляют (так строка "2" в списке values имеет индекс 2). Объекты Card имеют две переменные экземпляра — suit и value, каждая из них представлена целым числом. Вместе эти переменные экземпляра класса представляют вид карты объекта Card. К примеру, вы создаете 2 путем создания объекта Card и передачи в него параметров 2 (для значения) и 1 (для масти, поскольку индекс червей в списке suits — 1). Определения в магических методах __lt__ и __gt__ позволяют вам срав- нивать два объекта Card в выражении при помощи операторов больше и мень- ше. Код в этих методах выясняет, больше или меньше карта, чем другая карта, пе- реданная в качестве параметра. Код в магических методах также обрабатывает ситуации, когда значение карт одинаковое, например, если обе карты — десятки. В таком случае методы используют масти, чтобы избежать ничьей. Масти упоря- дочены в списке suits по возрастанию силы — самая сильная масть располагает- ся последней, то есть она назначена наибольшему индексу, а самая слабая масть назначена наименьшему индексу. Python_ex267.py 1 card1 = Card(10, 2) 2 card2 = Card(11, 3) 3 print(card1 < card2) >> True 134 Часть II Python_ex268.py 1 card1 = Card(10, 2) 2 card2 = Card(11, 3) 3 print(card1 > card2) >> False Последний метод в классе Card — магический метод __repr__ . Он исполь- зует переменные экземпляра value и suit для нахождения значения и масти карты в списках values и suit, и возвращает их, чтобы вы могли вывести карту, которую представляет объект Card. Python_ex269.py 1 card = Card(3, 2) 2 print(card) >> 3 Колода Теперь нужно определить класс, представляющий колоду карт. Python_ex270.py 1 from random import shuf e 2 class Deck: 3 def __init__( self ): 4 self .cards = [] 5 for i in range(2, 15): 6 for j in range(4): 7 self .cards.append(Card(i, j)) 8 shuf e( self .cards) 9 def rm_card( self ): 10 if len( self .cards) == 0: 11 return 12 return self .cards.pop() Когда вы создаете объект Deck, два цикла for в __init__ создают объек- ты, представляющие все карты в 52-карточной колоде, и добавляют их в список cards . Первый цикл перебирает от 2 до 15, поскольку первое значение для кар- ты — это 2, а последнее — 14 (туз). При каждом прохождении внутреннего цикла создается новая карта путем использования целого числа из внешнего цикла в качестве значения (например, 14 для туза) и целого числа из внутреннего цик- ла в качестве масти (например, 2 для червей). Так создаются 52 карты, по карте 135 Введение в объектно-ориентированное программирование на каждую комбинацию масти и значения. После того как метод создает карты, метод shuf e из модуля random случайным образом перемешивает элементы в списке cards, имитируя перетасовку колоды карт. У нашей колоды есть еще один метод, rm_card, который изымает и возвра- щает карту из списка cards, или возвращает None, если список пуст. Вы можете использовать класс Deck для создания новой колоды карт и вывода каждой кар- ты в ней. Python_ex271.py 1 deck = Deck() 2 for card in deck.cards: 3 print(card) >> 4 >> 8 … Игрок Нужен класс для представления каждого игрока, чтобы отслеживать их карты и количество выигранных ими раундов. Python_ex272.py 1 class Player: 2 def __init__( self , name): 3 self .wins = 0 4 self .card = None 5 self .name = name У класса Player есть три переменных экземпляра: wins для отслеживания количества раундов, выигранных игроком; card для представления карты, кото- рую в данный момент держит игрок; name для отслеживания имени игрока. Игра Наконец, вы создаете класс, представляющий игру. Python_ex273.py 1 class Game: 2 def __init__( self ): 3 name1 = input(" 1: ") 4 name2 = input(" 2: ") 5 self .deck = Deck() 6 self .p1 = Player(name1) 7 self .p2 = Player(name2) 136 Часть II 8 def wins( self , winner): 9 w = "{} B " 10 w = w.format(winner) 11 print(w) 12 def draw( self , p1n, p1c, p2n, p2c): 13 d = "{} {}, {} {}" 14 d = d.format(p1n, p1c, p2n, p2c) 15 print(d) 16 def play_game( self ): 17 cards = self .deck.cards 18 print(" G !") 19 while len(cards) >= 2: 20 m = "$ & G . $ & WW W * ." 21 response = input(m) 22 if response == '': 23 break 24 p1c = self .deck.rm_card() 25 p2c = self .deck.rm_card() 26 p1n = self .p1.name 27 p2n = self .p2.name 28 self .draw(p1n, p1c, p2n, p2c) 29 if p1c > p2c: 30 self .p1.wins += 1 31 self .wins( self .p1.name) 32 else: 33 self .p2.wins += 1 34 self .wins( self .p2.name) 35 win = self .winner( self .p1, self.p2) 36 print("R . {} !".format(win)) 37 def winner( self , p1, p2): 38 if p1.wins > p2.wins: 39 return p1.name 40 if p1.wins < p2.wins: 41 return p2.name 42 return "$!" 137 Введение в объектно-ориентированное программирование Когда вы создаете объект игры, Python вызывает метод __init__, а функ- ция input принимает имена двух игроков и сохраняет их в переменных name1 и name2. Затем вы создаете новый объект Deck, сохраняете его в переменной эк- земпляра класса deck и создаете два объекта Player, используя имена в name1 и name2. Метод play_game в классе Game начинает игру. В методе есть цикл, который продолжает ведение игры до тех пор, пока в колоде остается две или более кар- ты, и пока переменная response не примет значение . При каждом прохожде- нии цикла вы назначаете переменную response пользовательскому вводу. Игра продолжается до тех пор, пока либо пользователь не введет "", либо в колоде останется менее двух карт. При каждом прохождении цикла вынимаются две карты, и метод play_ game присваивает первую карту p1, а вторую — p2. Затем он выводит имя каждо- го игрока и карты, которую он вынул, сравнивает две карты, выясняя, какая из них старше, увеличивает значение переменной экземпляра wins для игрока со старшей картой и выводит сообщение о том, кто победил. У класса Game также есть метод winner, который принимает два объекта игроков, «смотрит» на количество раундов, выигранных каждым игроком, и воз- вращает игрока, который выиграл больше раундов. Когда в объекте Deck заканчиваются карты, метод play_game выводит со- общение о том, что игра окончена, вызывает метод winner (передавая в него p1 и p2) и выводит сообщение с итогом игры — именем победившего игрока. «Пьяница» Ниже приведен полный код игры. Python_ex274.py 1 from random import shuf e 2 class Card: 3 suits = ["", 4 "", 5 "", 6 ""] 7 values = [ None , None ,"2", "3", 8 "4", "5", "6", "7", 9 "8", "9", "10", 10 " ", " ", 11 " ", "B "] 12 def __init__( self , v, s): 13 """suit value — Y """ 138 Часть II 14 self .value = v 15 self .suit = s 16 def __lt__( self , c2): 17 if self .value < c2.value: 18 return True 19 if self .value == c2.value: 20 if self .suit < c2.suit: 21 return True 22 else: 23 return False 24 return False 25 def __gt__( self , c2): 26 if self .value > c2.value: 27 return True 28 if self .value == c2.value: 29 if self .suit > c2.suit: 30 return True 31 else: 32 return False 33 return False 34 def __repr__( self ): 35 v = self .values[ self .value] + " " \ 36 + self .suits[ self .suit] 37 return v 38 class Deck: 39 def __init__( self ): 40 self .cards = [] 41 for i in range(2, 15): 42 for j in range(4): 43 self .cards.append(Card(i, j)) 44 shuf e( self .cards) 45 def rm_card( self ): 46 if len( self .cards) == 0: 47 return 48 return self .cards.pop() 139 Введение в объектно-ориентированное программирование 50 class Player: 51 def __init__( self , name): 52 self .wins = 0 53 self .card = None 54 self .name = name 55 class Game: 56 def __init__( self ): 57 name1 = input(" 1: ") 58 name2 = input(" 2: ") 59 self .deck = Deck() 60 self .p1 = Player(name1) 61 self .p2 = Player(name2) 62 def wins( self , winner): 63 w = "{} B " 64 w = w.format(winner) 65 print(w) 66 def draw( self , p1n, p1c, p2n, p2c): 67 d = "{} {}, {} {}" 68 d = d.format(p1n, p1c, p2n, p2c) 69 print(d) 70 def play_game( self ): 71 cards = self .deck.cards 72 print(" G !") 73 while len(cards) >= 2: 74 m = "$ & G . $ & WW W * ." 75 response = input(m) 76 if response == '': 77 break 78 p1c = self .deck.rm_card() 79 p2c = self .deck.rm_card() 80 p1n = self .p1.name 81 p2n = self .p2.name 82 self .draw(p1n, p1c, p2n, p2c) 83 if p1c > p2c: 84 self .p1.wins += 1 85 self .wins(self.p1.name) 86 else: 87 self .p2.wins += 1 88 self .wins( self .p2.name) 89 win = self .winner( self .p1, self.p2) 90 print("R .{} !".format(win)) 91 def winner( self , p1, p2): 92 if p1.wins > p2.wins: 93 return p1.name 94 if p1.wins < p2.wins: 95 return p2.name 96 return "$!" 97 game = Game() 98 game.play_game() >> " 1: " … |