Изучаем Python — страница 27 из 61


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

Импортирование одного класса


Начнем с создания модуля, содержащего только класс Car. При этом возникает неочевидный конфликт имен: в этой главе уже был создан файл с именем car.py, но этот модуль тоже должен называться car.py, потому что в нем содержится код класса Car. Мы решим эту проблему, сохранив класс Car в модуле с именем car.py, заменив им файл car.py, который использовался ранее. В дальнейшем любой программе, использующей этот модуль, придется присвоить более конкретное имя файла — например, my_car.py. Ниже приведен файл car.py с кодом класса Car:

car.py

(1) """Класс для представления автомобиля."""

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

В точке (1) включается строка документации уровня модуля с кратким описанием содержимого модуля. Пишите строки документации для каждого созданного вами модуля.

Теперь мы создадим отдельный файл с именем my_car.py. Этот файл импортирует класс Car и создает экземпляр этого класса:

my_car.py

(1) from car import 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()

Команда import в точке (1) приказывает 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

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

(1) from car import Car, ElectricCar


(2)my_beetle = Car('volkswagen', 'beetle', 2016)

print(my_beetle.get_descriptive_name())


(3)my_tesla = ElectricCar('tesla', 'roadster', 2016)

print(my_tesla.get_descriptive_name())

Чтобы импортировать несколько классов из модуля, разделите их имена запятыми (1) . После того как необходимые классы будут импортированы, вы можете создать столько экземпляров каждого класса, сколько потребуется.

В этом примере создается обычный автомобиль Volkswagen Beetle (2) и электромобиль Tesla Roadster (3):

2016 Volkswagen Beetle

2016 Tesla Roadster

Импортирование всего модуля


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

my_cars.py

(1) import car


(2)my_beetle = car.Car('volkswagen', 'beetle', 2016)

print(my_beetle.get_descriptive_name())


(3)my_tesla = car.ElectricCar('tesla', 'roadster', 2016)

print(my_tesla.get_descriptive_name())

В точке (1) импортируется весь модуль car, после чего программа обращается к нужным классам с использованием синтаксиса имя_модуля.имя_класса. В ­точке (2) снова создается экземпляр Volkswagen Beetle, а в точке (3) — экземпляр Tesla Roadster.

Импортирование всех классов из модуля


Для импортирования всех классов из модуля используется следующий синтаксис:

from имя_модуля import *

Использовать этот способ не рекомендуется по двум причинам. Прежде всего, бывает полезно прочитать команды import в начале файла и получить четкое представление о том, какие классы используются в программе, а при таком подходе неясно, какие классы из модуля нужны программе. Также возможны конфликты с именами в файле. Если вы случайно импортируете класс с именем, уже присутствующим в файле, в программе могут возникнуть коварные ошибки. Почему я привожу описание этого способа? Хотя использовать его не рекомендуется, скорее всего, вы встретите его в коде других разработчиков.

Итак, если вам нужно импортировать большое количество классов из модуля, лучше импортировать весь модуль и воспользоваться синтаксисом имя_модуля.имя_класса. Хотя вы не видите перечень всех используемых классов в начале файла, по крайней мере ясно видно, где модуль используется в программе. Также предотвращаются потенциальные конфликты имен, которые могут возникнуть при импортировании каждого класса в модуле.

Импортирование модуля в модуль


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

Допустим, класс Car хранится в одном модуле, а классы ElectricCar и Battery — в другом. Мы создадим новый модуль с именем electric_car.py (он заменит файл electric_car.py, созданный ранее) и скопируем в него только классы Battery и ElectricCar:

electric_car.py

"""Набор классов для представления электромобилей."""

(1) from car import Car


class Battery():

...


class ElectricCar(Car):

...

Классу ElectricCar необходим доступ к классу-родителю Car, поэтому класс Car импортируется прямо в модуль в точке (1) . Если вы забудете вставить эту команду, при попытке создания экземпляра ElectricCar произойдет ошибка. Также необходимо обновить модуль Car, чтобы он содержал только класс Car:

car.py

"""Простая модель автомобиля."""


class Car():

...

Теперь вы можете импортировать классы из каждого модуля по отдельности и ­создать ту разновидность машины, которая вам нужна:

my_cars.py

(1) 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())

В точке (1) класс Car импортируется из своего модуля, а класс ElectricCar — из своего. После этого создаются экземпляры обоих разновидностей. Вывод показывает, что экземпляры были созданы правильно:

2016 Volkswagen Beetle

2016 Tesla Roadster

Выработка рабочего процесса


Как видите, 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