Книга Изучаем Python
Скачать 4.68 Mb.
|
175 Казалось бы, новый вариант требует большой дополнительной работы, но теперь аккумулятор можно моделировать с любой степенью детализации без загромож- дения класса ElectricCar . Добавим в Battery еще один метод, который выводит запас хода на основании мощности аккумулятора: class Car(): class Battery(): def get_range(self): """Выводит приблизительный запас хода для аккумулятора.""" if self.battery_size == 70: range = 240 elif self.battery_size == 85: range = 270 message = "This car can go approximately " + str(range) message += " miles on a full charge." print(message) class ElectricCar(Car): my_tesla = ElectricCar('tesla', 'model s', 2016) print(my_tesla.get_descriptive_name()) my_tesla.battery.describe_battery() my_tesla.battery.get_range() Новый метод get_range() в точке проводит простой анализ. Если мощность рав- на 70, то get_range() устанавливает запас хода 240 миль, а при мощности 85 kWh запас хода равен 270 милям. Затем программа выводит это значение. Когда вы захотите использовать этот метод, его придется вызывать через атрибут battery в точке . Результат сообщает запас хода машины в зависимости от мощности аккумулятора: 2016 Tesla Model S This car has a 70-kWh battery. This car can go approximately 240 miles on a full charge. Моделирование объектов реального мира Занявшись моделированием более сложных объектов — таких, как электромо- били, — вы столкнетесь со множеством интересных вопросов. Является ли запас хода электромобиля свойством аккумулятора или машины? Если вы описываете только одну машину, вероятно, можно связать метод get_range() с классом Battery Но, если моделируется целая линейка машин от производителя, вероятно, метод get_range() правильнее будет переместить в класс ElectricCar . Метод get_range() по-прежнему будет проверять мощность аккумулятора перед определением за- паса хода, но он будет сообщать запас хода для той машины, с которой он связан. 176 Глава 9 • Классы Также возможно связать метод get_range() с аккумулятором, но передавать ему параметр (например, car_model ). Метод get_range() будет определять запас хода на основании мощности аккумулятора и модели автомобиля. Если вы начнете ломать голову над такими вопросами, это означает, что вы мыс- лите на более высоком логическом уровне, не ограничиваясь уровнем синтаксиса. Вы думаете уже не о Python, а о том, как представить реальный мир в коде. И, до- стигнув этой точки, вы поймете, что однозначно правильного или неправильного подхода к моделированию реальных ситуаций часто не существует. Некоторые методы эффективнее других, но для того, чтобы найти наиболее эффективную реализацию, необходим практический опыт. Если ваш код работает именно так, как вы хотели, — значит, у вас все получается! Не огорчайтесь, если окажется, что вы по несколько раз переписываете свои классы для разных решений. На пути к написанию точного, эффективного кода все программисты проходят через этот процесс. УПРАЖНЕНИЯ 9-6 . Киоск с мороженым: киоск с мороженым — особая разновидность ресторана . Напишите класс IceCreamStand, наследующий от класса Restaurant из упражнения 9-1 (с . 165) или упражнения 9-4 (с . 169) . Подойдет любая версия класса; просто выберите ту, которая вам больше нравится . Добавьте атрибут с именем flavors для хранения списка сортов мороже- ного . Напишите метод, который выводит этот список . Создайте экземпляр IceCreamStand и вызовите этот метод . 9-7 . Администратор: администратор — особая разновидность пользователя . Напишите класс с именем Admin, наследующий от класса User из упражнения 9-3 (с . 165) или упражнения 9-5 (с . 170) . Добавьте атрибут privileges для хранения списка строк вида «разрешено добавлять сообщения», «разрешено удалять пользователей», «разрешено банить пользователей» и т . д . Напишите метод show_privileges() для вывода набора привилегий администратора . Создайте экземпляр Admin и вызовите свой метод . 9-8 . Привилегии: напишите класс Privileges . Класс должен содержать всего один атрибут privileges со списком строк из упражнения 9-7 . Переместите метод show_privileges() в этот класс . Создайте экземпляр Privileges как атрибут класса Admin . Создайте новый экземпляр Admin и используйте свой метод для вывода списка привилегий . 9-9 . Обновление аккумулятора: используйте окончательную версию программы electric_car .py из этого раздела . Добавьте в класс Battery метод с именем upgrade_battery() . Этот метод должен проверять размер аккумулятора и устанавливать мощность равной 85, если она имеет другое значение . Создайте экземпляр электромобиля с аккумулятором по умолчанию, вызовите get_range(), а затем вызовите get_range() во второй раз после вызова upgrade_battery() . Убедитесь в том, что запас хода увеличился . Импортирование классов С добавлением новой функциональности в классы файлы могут стать слишком длинными, даже при правильном использовании наследования. В соответствии с общей философией Python файлы не должны загромождаться лишними подроб- ностями. Для этого Python позволяет хранить классы в модулях и импортировать нужные классы в основную программу. Импортирование классов 177 Импортирование одного класса Начнем с создания модуля, содержащего только класс Car . При этом возникает неочевидный конфликт имен: в этой главе уже был создан файл с именем car .py , но этот модуль тоже должен называться car .py , потому что в нем содержится код класса Car . Мы решим эту проблему, сохранив класс Car в модуле с именем car .py , заменив им файл car .py , который использовался ранее. В дальнейшем любой программе, использующей этот модуль, придется присвоить более кон- кретное имя файла — например, my_car .py . Ниже приведен файл car .py с кодом класса Car : 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() 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 В точке включается строка документации уровня модуля с кратким описанием содержимого модуля. Пишите строки документации для каждого созданного вами модуля. Теперь мы создадим отдельный файл с именем my_car .py . Этот файл импортирует класс Car и создает экземпляр этого класса: my_car.py from car import Car 178 Глава 9 • Классы 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() Команда import в точке приказывает Python открыть модуль car и импорти- ровать класс Car . Теперь мы можем использовать класс Car так, как если бы он был определен в этом файле. Результат остается тем же, что и в предыдущей версии: 2016 Audi A4 This car has 23 miles on it. Импортирование классов повышает эффективность программирования. Пред- ставьте, каким длинным получился бы файл этой программы, если бы в него был включен весь класс Car . Перемещая класс в модуль и импортируя этот модуль, вы получаете ту же функциональность, но основной файл программы при этом остается чистым и удобочитаемым. Бульшая часть логики также может храниться в отдельных файлах; когда ваши классы работают так, как положено, вы можете забыть об этих файлах и сосредоточиться на высокоуровневой логике основной программы. Хранение нескольких классов в модуле В одном модуле можно хранить сколько угодно классов, хотя все эти классы долж- ны быть каким-то образом связаны друг с другом. Оба класса Battery и ElectricCar используются для представления автомобилей, поэтому мы добавим их в модуль car .py : car.py """Классы для представления машин с бензиновым и электродвигателем.""" class Car(): class Battery(): """Простая модель аккумулятора электромобиля.""" def __init__(self, battery_size=60): """Инициализация атрибутов аккумулятора.""" self.battery_size = battery_size def describe_battery(self): """Выводит информацию о мощности аккумулятора.""" print("This car has a " + str(self.battery_size) + "-kWh battery.") def get_range(self): """Выводит приблизительный запас хода для аккумулятора.""" if self.battery_size == 70: range = 240 elif self.battery_size == 85: range = 270 Импортирование нескольких классов из модуля 179 message = "This car can go approximately " + str(range) message += " miles on a full charge." print(message) class ElectricCar(Car): """Представляет аспекты машины, специфические для электромобилей.""" def __init__(self, make, model, year): """ Инициализирует атрибуты класса-родителя. Затем инициализирует атрибуты, специфические для электромобиля. """ super().__init__(make, model, year) self.battery = Battery() Теперь вы можете создать новый файл с именем my_electric_car .py , импортировать класс ElectricCar и создать новый экземпляр электромобиля: my_electric_car.py from car import ElectricCar my_tesla = ElectricCar('tesla', 'model s', 2016) print(my_tesla.get_descriptive_name()) my_tesla.battery.describe_battery() my_tesla.battery.get_range() Программа выводит тот же результат, что и в предыдущем случае, хотя бульшая часть ее логики скрыта в модуле: 2016 Tesla Model S This car has a 70-kWh battery. This car can go approximately 240 miles on a full charge. Импортирование нескольких классов из модуля В файл программы можно импортировать столько классов, сколько потребуется. Если вы захотите создать обычный автомобиль и электромобиль в одном файле, потребуется импортировать оба класса, Car и ElectricCar : my_cars.py from car import Car, ElectricCar my_beetle = Car('volkswagen', 'beetle', 2016) print(my_beetle.get_descriptive_name()) my_tesla = ElectricCar('tesla', 'roadster', 2016) print(my_tesla.get_descriptive_name()) Чтобы импортировать несколько классов из модуля, разделите их имена запя- тыми . После того как необходимые классы будут импортированы, вы можете создать столько экземпляров каждого класса, сколько потребуется. 180 Глава 9 • Классы В этом примере создается обычный автомобиль Volkswagen Beetle и электро- мобиль Tesla Roadster : 2016 Volkswagen Beetle 2016 Tesla Roadster Импортирование всего модуля Также возможно импортировать весь модуль, а потом обращаться к нужным клас- сам с использованием точечной записи. Этот способ прост, а полученный код легко читается. Так как каждый вызов, создающий экземпляр класса, включает имя моду- ля, в программе не будет конфликтов с именами, используемыми в текущем файле. my_cars.py import car my_beetle = car.Car('volkswagen', 'beetle', 2016) print(my_beetle.get_descriptive_name()) my_tesla = car.ElectricCar('tesla', 'roadster', 2016) print(my_tesla.get_descriptive_name()) В точке импортируется весь модуль car , после чего программа обращает- ся к нужным классам с использованием синтаксиса имя_модуля имя_класса. В точке снова создается экземпляр Volkswagen Beetle, а в точке — экземпляр Tesla Roadster. Импортирование всех классов из модуля Для импортирования всех классов из модуля используется следующий синтаксис: from имя_модуля import * Использовать этот способ не рекомендуется по двум причинам. Прежде всего, бывает полезно прочитать команды import в начале файла и получить четкое пред- ставление о том, какие классы используются в программе, а при таком подходе неясно, какие классы из модуля нужны программе. Также возможны конфликты с именами в файле. Если вы случайно импортируете класс с именем, уже при- сутствующим в файле, в программе могут возникнуть коварные ошибки. Почему я привожу описание этого способа? Хотя использовать его не рекомендуется, ско- рее всего, вы встретите его в коде других разработчиков. Итак, если вам нужно импортировать большое количество классов из модуля, лучше импортировать весь модуль и воспользоваться синтаксисом имя_модуля имя_класса. Хотя вы не видите перечень всех используемых классов в начале файла, по крайней мере ясно видно, где модуль используется в программе. Также предотвращаются потенциальные конфликты имен, которые могут возникнуть при импортировании каждого класса в модуле. Импортирование нескольких классов из модуля 181 Импортирование модуля в модуль Иногда классы приходится распределять по нескольким модулям, чтобы избежать чрезмерного разрастания одного файла и хранения несвязанных классов в одном модуле. При хранении классов в нескольких модулях может оказаться, что один класс из одного модуля зависит от класса из другого модуля. В таких случаях не- обходимый класс можно импортировать в первый модуль. Допустим, класс Car хранится в одном модуле, а классы ElectricCar и Battery — в другом. Мы создадим новый модуль с именем electric_car .py (он заменит файл electric_car .py , созданный ранее) и скопируем в него только классы Battery и ElectricCar : electric_car.py """Набор классов для представления электромобилей.""" from car import Car class Battery(): class ElectricCar(Car): Классу ElectricCar необходим доступ к классу-родителю Car , поэтому класс Car импортируется прямо в модуль в точке . Если вы забудете вставить эту команду, при попытке создания экземпляра ElectricCar произойдет ошибка. Также необ- ходимо обновить модуль Car , чтобы он содержал только класс Car : car.py """Простая модель автомобиля.""" class Car(): Теперь вы можете импортировать классы из каждого модуля по отдельности и создать ту разновидность машины, которая вам нужна: my_cars.py from car import Car from electric_car import ElectricCar my_beetle = Car('volkswagen', 'beetle', 2016) print(my_beetle.get_descriptive_name()) my_tesla = ElectricCar('tesla', 'roadster', 2016) print(my_tesla.get_descriptive_name()) В точке класс Car импортируется из своего модуля, а класс ElectricCar — из сво- его. После этого создаются экземпляры обоих разновидностей. Вывод показывает, что экземпляры были созданы правильно: 2016 Volkswagen Beetle 2016 Tesla Roadster 182 Глава 9 • Классы Выработка рабочего процесса Как видите, Python предоставляет много возможностей структурирования кода в крупных проектах. Вы должны знать все эти возможности, чтобы найти лучшие способы организации своих проектов, а также лучше понимать код других раз- работчиков. На первых порах постарайтесь поддерживать простую структуру своего кода. По- пробуйте разместить весь код в одном файле и, только когда все заработает, переме- стите классы в отдельные модули. Если вам нравится схема взаимодействия между модулями и файлами, попробуйте сохранить классы в модулях в начале работы над проектом. Найдите подход, при котором у вас получается работоспособный код, и двигайтесь дальше. УПРАЖНЕНИЯ 9-10 . Импортирование класса Restaurant: возьмите последнюю версию класса Restaurant и сохраните ее в модуле . Создайте отдельный файл, импортирующий класс Restaurant . Создайте экземпляр Restaurant и вызовите один из методов Restaurant, чтобы показать, что команда import работает правильно . 9-11 . Импортирование класса Admin: начните с версии класса из упражнения 9-8 (с . 176) . Сохраните классы User, Privileges и Admin в одном модуле . Создайте отдельный файл, соз- дайте экземпляр Admin и вызовите метод show_privileges(), чтобы показать, что все рабо- тает правильно . 9-12 . Множественные модули: сохраните класс User в одном модуле, а классы Privileges и Admin в другом модуле . В отдельном файле создайте экземпляр Admin и вызовите метод show_privileges(), чтобы показать, что все работает правильно . Стандартная библиотека Python Стандартная библиотека Python представляет собой набор модулей, включаемых в каждую установленную копию Python. Сейчас вы уже примерно понимаете, как работают классы, и можете начать использовать модули, написанные другими программистами. Чтобы использовать любую функцию или класс из стандартной библиотеки, достаточно включить простую команду import в начало файла. Для примера рассмотрим класс OrderedDict из модуля collections Как вы уже знаете, словари позволяют связывать информационные фрагменты, но они не отслеживают порядок добавления пар «ключ—значение». Если вы хотите создать словарь, но при этом сохранить порядок добавления пар «ключ—значение», воспользуйтесь классом OrderedDict из модуля collections . Экземпляры класса OrderedDict ведут себя практически так же, как и словари, если не считать того, что они отслеживают порядок добавления пар «ключ—значение». Вернемся к примеру favorite_languages .py из главы 6. На этот раз программа будет отслеживать порядок, в котором участники отвечают на опрос: favorite_languages.py from collections import OrderedDict favorite_languages = OrderedDict() Стандартная библиотека Python 183 favorite_languages['jen'] = 'python' favorite_languages['sarah'] = 'c' favorite_languages['edward'] = 'ruby' favorite_languages['phil'] = 'python' for name, language in favorite_languages.items(): print(name.title() + "'s favorite language is " + language.title() + ".") Сначала программа импортирует класс OrderedDict из модуля collections в точ- ке . В точке создается экземпляр класса OrderedDict , который сохраняется в favorite_languages . Обратите внимание на отсутствие фигурных скобок; вызов OrderedDict() создает пустой упорядоченный словарь и сохраняет его в favorite_ languages . Затем пары из имени и языка последовательно добавляются в словарь . Теперь при переборе favorite_languages в точке данные всегда будут выдаваться в порядке их добавления: Jen's favorite language is Python. Sarah's favorite language is C. Edward's favorite language is Ruby. Phil's favorite language is Python. Это чрезвычайно полезный класс, объединяющий основное преимущество списков (сохранение исходного порядка) с главной особенностью словарей (связывание двух видов информации). Когда вы займетесь моделированием реальных ситуаций, может возникнуть ситуация, в которой упорядоченный словарь окажется именно тем, что вам необходимо. А по мере изучения стандартной библиотеки вы узнаете о других полезных модулях, которые помогут вам в решении типичных задач. |