Мэтиз. Изучаем Python. Crash course2 n d e d i t i o na h a n d s o n, p r o j e c t b a s e d i n t r o d u c t i o n t o p r o g r a m m i n g
Скачать 6.2 Mb.
|
173 Создадим экземпляр, представляющий конкретную собаку: class Dog(): ❶ my_dog = Dog('willie', 6) ❷ print(f"My dog's name is {my_dog.name}.") ❸ print(f"My dog is {my_dog.age} years old.") Использованный в данном случае класс Dog был написан в предыдущем примере. В точке мы приказываем Python создать экземпляр собаки с кличкой 'willie' и возрастом 6 лет. В процессе обработки этой строки Python вызывает метод __init__() класса Dog с аргументами 'willie' и 6. Метод __init__() создает экзем- пляр, представляющий конкретную собаку, и присваивает его атрибутам name и age переданные значения. Затем Python возвращает экземпляр, представляющий собаку. Этот экземпляр сохраняется в переменной my_dog . Здесь нелишне вспомнить согла- шения по записи имен: обычно считается, что имя, начинающееся с символа верхнего регистра (например, Dog ), обозначает класс, а имя, записанное в нижнем регистре (например, my_dog ), обозначает отдельный экземпляр, созданный на базе класса. Обращение к атрибутам Для обращения к атрибутам экземпляра используется «точечная» запись. В стро- ке мы обращаемся к значению атрибута name экземпляра my_dog : my_dog.name Точечная запись часто используется в Python. Этот синтаксис показывает, как Python ищет значения атрибутов. В данном случае Python обращается к экзем- пляру my_dog и ищет атрибут name , связанный с экземпляром my_dog . Это тот же атрибут, который обозначался self.name в классе Dog . В точке тот же прием ис- пользуется для работы с атрибутом age Пример выводит известную информацию о my_dog : My dog's name is Willie. My dog is 6 years old. Вызов методов После создания экземпляра на основе класса Dog можно применять точечную запись для вызова любых методов, определенных в Dog : class Dog(): my_dog = Dog('willie', 6) my_dog.sit() my_dog.roll_over() 174 Глава 9 • Классы Чтобы вызвать метод, укажите экземпляр (в данном случае my_dog ) и вызываемый метод, разделив их точкой. В ходе обработки my_dog.sit() Python ищет метод sit() в классе Dog и выполняет его код. Строка my_dog.roll_over() интерпретируется аналогичным образом. Теперь экземпляр послушно выполняет полученные команды: Willie is now sitting. Willie rolled over! Это очень полезный синтаксис. Если атрибутам и методам были присвоены содер- жательные имена (например, name , age , sit() и roll_over() ), разработчик сможет легко понять, что делает блок кода — даже если он видит этот блок впервые. Создание нескольких экземпляров На основе класса можно создать столько экземпляров, сколько вам потребуется. Создадим второй экземпляр Dog с именем your_dog : class Dog(): my_dog = Dog('willie', 6) your_dog = Dog('lucy', 3) print(f"My dog's name is {my_dog.name}.") print(f"My dog is {my_dog.age} years old.") my_dog.sit() print(f"\nYour dog's name is {your_dog.name}.") print(f"Your dog is {your_dog.age} years old.") your_dog.sit() В этом примере создаются два экземпляра с именами Willie и Lucy . Каждый эк- земпляр обладает своим набором атрибутов и способен выполнять действия из общего набора: My dog's name is Willie. My dog is 6 years old. Willie is now sitting. Your dog's name is Lucy. Your dog is 3 years old. Lucy is now sitting. Даже если второй собаке будет назначено то же имя и возраст, Python все равно создаст отдельный экземпляр класса Dog . Вы можете создать сколько угодно эк- земпляров одного класса при условии, что эти экземпляры хранятся в переменных с разными именами или занимают разные позиции в списке либо словаре. Работа с классами и экземплярами 175 УПРАЖНЕНИЯ 9.1. Ресторан: создайте класс с именем Restaurant . Метод __init__() класса Restaurant должен содержать два атрибута : restaurant_name и cuisine_type . Создайте метод describe_ restaurant() , который выводит два атрибута, и метод open_restaurant() , который выводит сообщение о том, что ресторан открыт. Создайте на основе своего класса экземпляр с именем restaurant . Выведите два атрибута по отдельности, затем вызовите оба метода. 9.2. Три ресторана: начните с класса из упражнения 9.1. Создайте три разных экземпляра, вызовите для каждого экземпляра метод describe_restaurant() 9.3. Пользователи: создайте класс с именем User . Создайте два атрибута first_name и last_ name , а затем еще несколько атрибутов, которые обычно хранятся в профиле пользователя. Напишите метод describe_user() , который выводит сводку с информацией о пользователе. Создайте еще один метод greet_user() для вывода персонального приветствия для поль- зователя. Создайте несколько экземпляров, представляющих разных пользователей. Вызовите оба метода для каждого пользователя. Работа с классами и экземплярами Классы могут использоваться для моделирования многих реальных ситуаций. По- сле того как класс будет написан, разработчик проводит большую часть времени за работой с экземплярами, созданными на основе этого класса. Одной из первых задач станет изменение атрибутов, связанных с конкретным экземпляром. Атрибу- ты экземпляра можно изменять напрямую или же написать методы, изменяющие атрибуты по особым правилам. Класс Car Напишем класс, представляющий автомобиль. Этот класс будет содержать инфор- мацию о типе машины, а также метод для вывода краткого описания: car.py class Car(): """Простая модель автомобиля.""" ❶ def __init__(self, make, model, year): """Инициализирует атрибуты описания автомобиля.""" self.make = make self.model = model self.year = year ❷ def get_descriptive_name(self): """Возвращает аккуратно отформатированное описание.""" long_name = f"{self.year} {self.manufacturer} {self.model}" return long_name.title() 176 Глава 9 • Классы ❸ my_new_car = Car('audi', 'a4', 2019) print(my_new_car.get_descriptive_name()) В точке в классе 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() показывает, с какой машиной работает программа: 2019 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): """Выводит пробег машины в милях.""" Работа с классами и экземплярами 177 print(f"This car has {self.odometer_reading} miles on it.") my_new_car = Car('audi', 'a4', 2019) print(my_new_car.get_descriptive_name()) my_new_car.read_odometer() Когда Python вызывает метод __init__() для создания нового экземпляра, этот метод сохраняет фирму-производителя, модель и год выпуска в атрибутах, как и в предыдущем случае. Затем Python создает новый атрибут с именем odometer_ reading и присваивает ему исходное значение 0 . Также в класс добавляется новый метод read_odometer() , который упрощает чтение пробега машины в милях. Сразу же после создания машины ее пробег равен 0: 2019 Audi A4 This car has 0 miles on it. Впрочем, у продаваемых машин одометр редко показывает ровно 0, поэтому нам понадобится способ изменения значения этого атрибута. Изменение значений атрибутов Значение атрибута можно изменить одним из трех способов: изменить его прямо в экземпляре, задать значение при помощи метода или изменить его с приращением (то есть прибавлением определенной величины) при помощи метода. Рассмотрим все эти способы. Прямое изменение значения атрибута Чтобы изменить значение атрибута, проще всего обратиться к нему прямо через эк- земпляр. В следующем примере на одометре напрямую выставляется значение 23: class Car: my_new_car = Car('audi', 'a4', 2019) 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: 2019 Audi A4 This car has 23 miles on it. 178 Глава 9 • Классы Иногда подобные прямые обращения к атрибутам допустимы, но чаще разработчик пишет вспомогательный метод, который изменяет значение за него. Изменение значения атрибута с использованием метода В класс можно включить методы, которые изменяют некоторые атрибуты за вас. Вместо того чтобы изменять атрибут напрямую, вы передаете новое значение ме- тоду, который берет обновление атрибута на себя. В следующем примере в класс включается метод update_odometer() для изменения показаний одометра: class Car: ❶ def update_odometer(self, mileage): """Устанавливает заданное значение на одометре.""" self.odometer_reading = mileage my_new_car = Car('audi', 'a4', 2019) 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() выводит текущие показания: 2019 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!") Работа с классами и экземплярами 179 Теперь update_odometer() проверяет новое значение перед изменением атрибута. Если новое значение mileage больше или равно текущему, self.odometer_reading , показания одометра можно обновить новым значением . Если же новое значение меньше текущего, вы получите предупреждение о недопустимости обратной под- крутки . Изменение значения атрибута с приращением Иногда значение атрибута требуется изменить с заданным приращением (вместо того, чтобы присваивать атрибуту произвольное новое значение). Допустим, вы купили подержанную машину и проехали на ней 100 миль с момента покупки. Следующий метод получает величину приращения и прибавляет ее к текущим показаниям одометра: class Car(): def update_odometer(self, mileage): ❶ def increment_odometer(self, miles): """Увеличивает показания одометра с заданным приращением.""" self.odometer_reading += miles ❷ my_used_car = Car('subaru', 'outback', 2015) print(my_used_car.get_descriptive_name()) ❸ my_used_car.update_odometer(23_500) 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() , которому передается значение 23 500 . В точке вызывается метод increment_odometer() , которому передает- ся значение 100, чтобы увеличить показания одометра на 100 миль, пройденные с момента покупки: 2015 Subaru Outback This car has 23500 miles on it. This car has 23600 miles on it. При желании можно легко усовершенствовать этот метод, чтобы он отклонял отрицательные приращения; тем самым вы предотвратите обратную подкрутку одометра. 180 Глава 9 • Классы ПРИМЕЧАНИЕ Подобные методы управляют обновлением внутренних значений эк- земпляров (таких, как показания одометра), однако любой пользователь, имеющий до- ступ к программному коду, сможет напрямую задать атрибуту любое значение . Эффек- тивная схема безопасности должна уделять особое внимание таким подробностям, не ограничиваясь простейшими проверками . УПРАЖНЕНИЯ 9.4. Посетители: начните с программы из упражнения 9.1 (с. 175). Добавьте атрибут number_served со значением по умолчанию 0 ; он представляет количество обслуженных посетителей. Создайте экземпляр с именем restaurant . Выведите значение number_served , потом измените и выведите снова. Добавьте метод с именем set_number_served() , позволяющий задать количество обслужен- ных посетителей. Вызовите метод с новым числом, снова выведите значение. Добавьте метод с именем increment_number_served() , который увеличивает количество об- служенных посетителей на заданную величину. Вызовите этот метод с любым числом, ко- торое могло бы представлять количество обслуженных клиентов, — скажем, за один день. 9.5. Попытки входа: добавьте атрибут login_attempts в класс User из упражнения 9.3 (с. 175). Напишите метод increment_login_attempts() , увеличивающий значение login_ attempts на 1 . Напишите другой метод с именем reset_login_attempts() , обнуляющий зна- чение login_attempts Создайте экземпляр класса User и вызовите increment_login_attempts() несколько раз. Выведите значение login_attempts , чтобы убедиться в том, что значение было измене- но правильно, а затем вызовите reset_login_attempts() . Снова выведите login_attempts и убедитесь в том, что значение обнулилось. Наследование Работа над новым классом не обязана начинаться с нуля. Если класс, который вы пишете, представляет собой специализированную версию ранее написанного класса, вы можете воспользоваться наследованием. Один класс, наследующий от другого, автоматически получает все атрибуты и методы первого класса. Исходный класс называется родителем, а новый класс — потомком. Класс-потомок наследует атрибуты и методы родителя, но при этом также может определять собственные атрибуты и методы. Метод __init__() класса-потомка При написании нового класса на базе существующего класса часто приходится вы- зывать метод __init__() класса-родителя. При этом происходит инициализация любых атрибутов, определенных в методе __init__() родителя, и эти атрибуты становятся доступными для класса-потомка. Например, попробуем построить модель электромобиля. Электромобиль пред- ставляет собой специализированную разновидность автомобиля, поэтому новый класс ElectricCar можно создать на базе класса Car , написанного ранее. Тогда Наследование 181 нам останется добавить в него код атрибутов и поведения, относящегося только к электромобилям. Начнем с создания простой версии класса 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 = f"{self.year} {self.manufacturer} {self.model}" return long_name.title() def read_odometer(self): print(f"This car has {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', 2019) print(my_tesla.get_descriptive_name()) В точке строится экземпляр Car . При создании класса-потомка класс-родитель должен быть частью текущего файла, а его определение должно предшествовать определению класса-потомка в файле. В точке определяется класс-потомок ElectricCar . В определении потомка имя класса-родителя заключается в круглые скобки. Метод __init__() в точке получает информацию, необходимую для создания экземпляра Car Функция super() в строке — специальная функция, которая позволяет вы- звать метод родительского класса. Эта строка приказывает Python вызвать метод __init__() класса Car , в результате чего экземпляр ElectricCar получает доступ |