Главная страница

Книга Изучаем Python


Скачать 4.68 Mb.
НазваниеКнига Изучаем Python
Дата10.12.2022
Размер4.68 Mb.
Формат файлаpdf
Имя файлаErik_Metiz_Izuchaem_Python_Programmirovanie_igr_vizualizatsia_da.pdf
ТипКнига
#837531
страница20 из 53
1   ...   16   17   18   19   20   21   22   23   ...   53
166 Глава 9 • Классы
В точке  в классе
Car определяется метод
__init__()
; его список параметров на- чинается с self
, как и в классе
Dog
. За ним следуют еще три параметра: make
, model и year
. Метод
__init__()
получает эти параметры и сохраняет их в атрибутах, ко- торые будут связаны с экземплярами, созданными на основе класса. При создании нового экземпляра
Car необходимо указать фирму-производителя, модель и год выпуска для данного экземпляра.
В точке  определяется метод get_descriptive_name()
, который объединяет год выпуска, фирму-производителя и модель в одну строку с описанием. Это избавит вас от необходимости выводить значение каждого атрибута по отдельности. Для работы со значениями атрибутов в этом методе используется синтаксис self.make
, self.model и self.year
В точке  создается экземпляр класса
Car
, который сохраняется в переменной my_new_car
. Затем вызов метода get_descriptive_name()
показывает, с какой ма- шиной работает программа:
2016 Audi A4
Чтобы класс был более интересным, добавим атрибут, изменяющийся со време- нем, — в нем будет храниться пробег машины в милях.
Назначение атрибуту значения по умолчанию
Каждый атрибут класса должен иметь исходное значение, даже если оно равно
0 или пустой строке. В некоторых случаях (например, при задании значений по умолчанию) это исходное значение есть смысл задавать в теле метода
__init__()
; в таком случае передавать параметр для этого атрибута при создании объекта не обязательно.
Добавим атрибут с именем odometer_reading
, исходное значение которого всегда равно 0. Также в класс будет включен метод read_odometer()
для чтения текущих показаний одометра:
class Car():
def __init__(self, make, model, year):
"""Инициализирует атрибуты описания автомобиля."""
self.make = make self.model = model self.year = year
 self.odometer_reading = 0
def get_descriptive_name(self):
 def read_odometer(self):
"""Выводит пробег машины в милях."""
print("This car has " + str(self.odometer_reading) + " miles on it.")
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()

Работа с классами и экземплярами 167
Когда Python вызывает метод
__init__()
для создания нового экземпляра, этот метод сохраняет фирму-производителя, модель и год выпуска в атрибутах, как и в предыдущем случае. Затем Python создает новый атрибут с именем odometer_
reading и присваивает ему исходное значение 0 . Также в класс добавляется новый метод read_odometer()
, который упрощает чтение пробега машины в милях.
Сразу же после создания машины ее пробег равен 0:
2016 Audi A4
This car has 0 miles on it.
Впрочем, у продаваемых машин одометр редко показывает ровно 0, поэтому нам понадобится способ изменения значения этого атрибута.
Изменение значений атрибутов
Значение атрибута можно изменить одним из трех способов: изменить его прямо в экземпляре, задать значение при помощи метода или изменить его с приращением
(то есть прибавлением определенной величины) при помощи метода. Рассмотрим все эти способы.
Прямое изменение значения атрибута
Чтобы изменить значение атрибута, проще всего обратиться к нему прямо через эк- земпляр. В следующем примере на одометре напрямую выставляется значение 23:
class Car():
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
 my_new_car.odometer_reading = 23
my_new_car.read_odometer()
В точке  точечная запись используется для обращения к атрибуту odometer_
reading экземпляра и прямого присваивания его значения. Эта строка приказывает
Python взять экземпляр my_new_car
, найти связанный с ним атрибут odometer_
reading и задать значение атрибута равным 23:
2016 Audi A4
This car has 23 miles on it.
Иногда подобные прямые обращения к атрибутам допустимы, но чаще разработчик пишет вспомогательный метод, который изменяет значение за него.
Изменение значения атрибута с использованием метода
В класс можно включить методы, которые изменяют некоторые атрибуты за вас.
Вместо того чтобы изменять атрибут напрямую, вы передаете новое значение ме- тоду, который берет обновление атрибута на себя.

168 Глава 9 • Классы
В следующем примере в класс включается метод update_odometer()
для изменения показаний одометра:
class Car():
 def update_odometer(self, mileage):
"""Устанавливает заданное значение на одометре."""
self.odometer_reading = mileage my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
 my_new_car.update_odometer(23)
my_new_car.read_odometer()
Класс
Car почти не изменился, в нем только добавился метод update_odometer()
.
Этот метод получает пробег в милях и сохраняет его в self.odometer_reading
В точке  мы вызываем метод update_odometer()
и передаем ему значение 23 в аргументе (соответствующем параметру mileage в определении метода). Метод устанавливает на одометре значение 23, а метод read_odometer()
выводит текущие показания:
2016 Audi A4
This car has 23 miles on it.
Метод update_odometer()
можно расширить так, чтобы при каждом изменении показаний одометра выполнялась некоторая дополнительная работа. Добавим проверку, которая гарантирует, что никто не будет пытаться сбрасывать показания одометра:
class Car():
def update_odometer(self, mileage):
"""
Устанавливает на одометре заданное значение.
При попытке обратной подкрутки изменение отклоняется.
"""
 if mileage >= self.odometer_reading:
self.odometer_reading = mileage else:
 print("You can't roll back an odometer!")
Теперь update_odometer()
проверяет новое значение перед изменением атрибута.
Если новое значение mileage больше или равно текущего, self.odometer_reading
, показания одометра можно обновить новым значением . Если же новое значение меньше текущего, вы получите предупреждение о недопустимости обратной под- крутки .
Изменение значения атрибута с приращением
Иногда значение атрибута требуется изменить с заданным приращением (вме- сто того чтобы присваивать атрибуту произвольное новое значение). Допустим,

Работа с классами и экземплярами 169
вы купили подержанную машину и проехали на ней 100 миль. Следующий метод получает величину приращения и прибавляет ее к текущим показаниям одометра:
class Car():
def update_odometer(self, mileage):
--snip--
 def increment_odometer(self, miles):
"""Увеличивает показания одометра с заданным приращением."""
self.odometer_reading += miles
 my_used_car = Car('subaru', 'outback', 2013)
print(my_used_car.get_descriptive_name())
 my_used_car.update_odometer(23500)
my_used_car.read_odometer()
 my_used_car.increment_odometer(100)
my_used_car.read_odometer()
Новый метод increment_odometer()
в точке  получает расстояние в милях и прибавляет его к self.odometer_reading
. В точке  создается экземпляр my_used_car
. Мы инициализируем показания его одометра значением 23 500; для этого вызывается метод update_odometer()
, которому передается значение
23500 . В точке  вызывается метод increment_odometer()
, которому передает- ся значение 100, чтобы увеличить показания одометра на 100 миль, пройденные с момента покупки:
2013 Subaru Outback
This car has 23500 miles on it.
This car has 23600 miles on it.
При желании можно легко усовершенствовать этот метод, чтобы он отклонял отрицательные приращения; тем самым вы предотвратите обратную подкрутку одометра.
ПРИМЕЧАНИЕ
Подобные методы управляют обновлением внутренних значений экземпляров (таких, как показа- ния одометра), однако любой пользователь, имеющий доступ к программному коду, сможет напря- мую задать атрибуту любое значение . Эффективная схема безопасности должна уделять особое внимание таким подробностям, не ограничиваясь простейшими проверками .
УПРАЖНЕНИЯ
9-4 . Посетители: начните с программы из упражнения 9-1 (с . 165) . Добавьте атрибут number_served со значением по умолчанию 0; он представляет количество обслуженных посетителей . Создайте экземпляр с именем restaurant . Выведите значение number_served, потом измените и выведите снова .
Добавьте метод с именем set_number_served(), позволяющий задать количество обслужен- ных посетителей . Вызовите метод с новым числом, снова выведите значение .

170 Глава 9 • Классы
Добавьте метод с именем increment_number_served(), который увеличивает количество обслуженных посетителей на заданную величину . Вызовите этот метод с любым числом, которое могло бы представлять количество обслуженных клиентов — скажем, за один день .
9-5 . Попытки входа: добавьте атрибут login_attempts в класс User из упражнения 9-3
(с . 165) . Напишите метод increment_login_attempts(), увеличивающий значение login_
attempts на 1 . Напишите другой метод с именем reset_login_attempts(), обнуляющий значе- ние login_attempts .
Создайте экземпляр класса User и вызовите increment_login_attempts() несколько раз . Вы- ведите значение login_attempts, чтобы убедиться в том, что значение было изменено пра- вильно, а затем вызовите reset_login_attempts() . Снова выведите login_attempts и убеди- тесь в том, что значение обнулилось .
Наследование
Работа над новым классом не обязана начинаться с нуля. Если класс, который вы пишете, представляет собой специализированную версию ранее написанного клас- са, вы можете воспользоваться наследованием. Один класс, наследующий от другого, автоматически получает все атрибуты и методы первого класса. Исходный класс называется родителем, а новый класс — потомком. Класс-потомок наследует атрибуты и методы родителя, но при этом также может определять собственные атрибуты и методы.
Метод __init__() класса-потомка
Первое, что делает Python при создании экземпляра класса-потомка, — присваивает значения всем атрибутам класса-родителя. Для этого методу
__init__()
класса-по- томка необходима помощь со стороны родителя.
Например, попробуем построить модель электромобиля. Электромобиль пред- ставляет собой специализированную разновидность автомобиля, поэтому новый класс
ElectricCar можно создать на базе класса
Car
, написанного ранее. Тогда нам останется добавить в него код атрибутов и поведения, относящегося только к электромобилям.
Начнем с создания простой версии класса
ElectricCar
, который делает все, что делает класс
Car
:
electric_car.py
 class Car():
"""Простая модель автомобиля."""
def __init__(self, make, model, year):
self.make = make self.model = model self.year = year self.odometer_reading = 0
def get_descriptive_name(self):
long_name = str(self.year) + ' ' + self.make + ' ' + self.model return long_name.title()

Наследование 171
def read_odometer(self):
print("This car has " + str(self.odometer_reading) + " miles on it.")
def update_odometer(self, mileage):
if mileage >= self.odometer_reading:
self.odometer_reading = mileage else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
self.odometer_reading += miles
 class ElectricCar(Car):
"""Представляет аспекты машины, специфические для электромобилей."""
 def __init__(self, make, model, year):
"""Инициализирует атрибуты класса-родителя."""
 super().__init__(make, model, year)
 my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
В точке  строится экземпляр
Car
. При создании класса-потомка класс-родитель должен быть частью текущего файла, а его определение должно предшествовать определению класса-потомка в файле. В точке  определяется класс-потомок
ElectricCar
. В определении потомка имя класса-родителя заключается в круглые скобки. Метод
__init__()
в точке  получает информацию, необходимую для создания экземпляра
Car
Функция super()
в строке  — специальная функция, которая помогает Python связать потомка с родителем. Эта строка приказывает Python вызвать метод
__init__()
класса, являющегося родителем
ElectricCar
, в результате чего экзем- пляр
ElectricCar получает все атрибуты класса-родителя. Имя super происходит из распространенной терминологии: класс-родитель называется суперклассом, а класс-потомок — субклассом.
Чтобы проверить, правильно ли сработало наследование, попробуем создать электромобиль с такой же информацией, которая передается при создании обычного экземпляра
Car
. В точке  мы создаем экземпляр класса
ElectricCar и сохраняем его в my_tesla
. Эта строка вызывает метод
__init__()
, определен- ный в
ElectricCar
, который в свою очередь приказывает Python вызвать метод
__init__()
, определенный в классе-родителе
Car
. При вызове передаются аргу- менты 'tesla'
,
'model s'
и
2016
Кроме
__init__()
класс еще не содержит никаких атрибутов или методов, специ- фических для электромобилей. Пока мы просто убеждаемся в том, что класс электромобиля содержит все поведение, присущее классу автомобиля:
2016 Tesla Model S
Экземпляр
ElectricCar работает так же, как экземпляр
Car
; можно переходить к определению атрибутов и методов, специфических для электромобилей.

172 Глава 9 • Классы
Наследование в Python 2 .7
В Python 2.7 наследование реализовано немного иначе. Класс
ElectricCar будет выглядеть примерно так:
class Car(object):
def __init__(self, make, model, year):
class ElectricCar(Car):
def __init__(self, make, model, year):
super(ElectricCar, self).__init__(make, model, year)
Функция super()
должна получать два аргумента: ссылку на класс-потомок и объ- ект self
. Эти аргументы необходимы для того, чтобы Python мог правильно связать родителя с потомком. Если вы используете наследование в Python 2.7, убедитесь в том, что родитель также определяется с синтаксисом object
Определение атрибутов и методов класса-потомка
После создания класса-потомка, наследующего от класса-родителя, можно пере- ходить к добавлению новых атрибутов и методов, необходимых для того, чтобы потомок отличался от родителя.
Добавим атрибут, специфический для электромобилей (например, мощность ак- кумулятора), и метод для вывода информации об этом атрибуте:
class Car():
class ElectricCar(Car):
"""Представляет аспекты машины, специфические для электромобилей."""
def __init__(self, make, model, year):
"""
Инициализирует атрибуты класса-родителя.
Затем инициализирует атрибуты, специфические для электромобиля.
"""
super().__init__(make, model, year)
 self.battery_size = 70
 def describe_battery(self):
"""Выводит информацию о мощности аккумулятора."""
print("This car has a " + str(self.battery_size) + "-kWh battery.")
my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()
В точке  добавляется новый атрибут self.battery_size
, которому присваивается исходное значение — скажем, 70. Этот атрибут будет присутствовать во всех экзем- плярах, созданных на основе класса
ElectricCar
(но не во всяком экземпляре
Car
).
Также добавляется метод с именем describe_battery()
, который выводит инфор-

Наследование 173
мацию об аккумуляторе в точке . При вызове этого метода выводится описание, которое явно относится только к электромобилям:
2016 Tesla Model S
This car has a 70-kWh battery.
Возможности специализации класса
ElectricCar беспредельны. Вы можете до- бавить сколько угодно атрибутов и методов, чтобы моделировать электромобиль с любой нужной точностью. Атрибуты или методы, которые могут принадлежать любой машине (а не только электромобилю), должны добавляться в класс
Car вме- сто
ElectricCar
. Тогда эта информация будет доступна всем пользователям класса
Car
, а класс
ElectricCar будет содержать только код информации и поведения, специфических для электромобилей.
Переопределение методов класса-родителя
Любой метод родительского класса, который в моделируемой ситуации делает не то, что нужно, можно переопределить. Для этого в классе-потомке определяется метод с тем же именем, что и у метода класса-родителя. Python игнорирует метод родителя и обращает внимание только на метод, определенный в потомке.
Допустим, в классе
Car имеется метод fill_gas_tank()
. Для электромобилей за- правка бензином бессмысленна, поэтому этот метод логично переопределить. На- пример, это можно сделать так:
def ElectricCar(Car):
def fill_gas_tank():
"""У электромобилей нет бензобака."""
print("This car doesn't need a gas tank!")
И если кто-то попытается вызвать метод fill_gas_tank()
для электромобиля,
Python игнорирует метод fill_gas_tank()
класса
Car и выполнит вместо него этот код. С применением наследования потомок сохраняет те аспекты родителя, которые вам нужны, и переопределяет все ненужное.
Экземпляры как атрибуты
При моделировании явлений реального мира в программах классы нередко до- полняются все большим количеством подробностей. Списки атрибутов и мето- дов растут, и через какое-то время файлы становятся длинными и громоздкими.
В такой ситуации часть одного класса нередко можно записать в виде отдельного класса. Большой код разбивается на меньшие классы, которые работают во взаи- модействии друг с другом.
Например, при дальнейшей доработке класса
ElectricCar может оказаться, что в нем появилось слишком много атрибутов и методов, относящихся к аккумулято- ру. В таком случае можно остановиться и переместить все эти атрибуты и методы в отдельный класс с именем
Battery
. Затем экземпляр
Battery становится атрибу- том класса
ElectricCar
:

174 Глава 9 • Классы class Car():
 class Battery():
"""Простая модель аккумулятора электромобиля."""
 def __init__(self, battery_size=70):
"""Инициализирует атрибуты аккумулятора."""
self.battery_size = battery_size
 def describe_battery(self):
"""Выводит информацию о мощности аккумулятора."""
print("This car has a " + str(self.battery_size) + "-kWh battery.") class ElectricCar(Car):
"""Представляет аспекты машины, специфические для электромобилей."""
def __init__(self, make, model, year):
"""
Инициализирует атрибуты класса-родителя.
Затем инициализирует атрибуты, специфические для электромобиля.
"""
super().__init__(make, model, year)
 self.battery = Battery()
my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
В точке  определяется новый класс с именем
Battery
, который не наследует ни от одного из других классов. Метод
__init__()
в точке  получает один пара- метр battery_size
, кроме self
. Если значение не предоставлено, этот необязатель- ный параметр задает battery_size значение 70. Метод describe_battery()
также перемещен в этот класс .
Затем в класс
ElectricCar добавляется атрибут с именем self.battery
. Эта стро- ка приказывает Python создать новый экземпляр
Battery
(со значением battery_
size по умолчанию, равным 70, потому что значение не задано) и сохранить его в атрибуте self.battery
. Это будет происходить при каждом вызове
__init__()
; теперь любой экземпляр
ElectricCar будет иметь автоматически создаваемый экземпляр
Battery
Программа создает экземпляр электромобиля и сохраняет его в переменной my_
tesla
. Когда потребуется вывести описание аккумулятора, необходимо обратиться к атрибуту battery
:
my_tesla.battery.describe_battery()
Эта строка приказывает Python обратиться к экземпляру my_tesla
, найти его атрибут battery и вызвать метод describe_battery()
, связанный с экземпляром
Battery из атрибута.
Результат выглядит так же, как и в предыдущей версии:
2016 Tesla Model S
This car has a 70-kWh battery.

Наследование
1   ...   16   17   18   19   20   21   22   23   ...   53


написать администратору сайта