Изучаем Python. Программирование игр, визуализация данных, веб-приложения

Мэтиз Эрик

9. Классы

 

Объектно-ориентированное программирование по праву считается одной из самых эффективных методологий создания программных продуктов. В объектно-ориентированном программировании вы пишете классы, описывающие реально существующие предметы и ситуации, а затем создаете объекты на основе этих описаний. При написании класса определяется общее поведение для целой категории объектов.

Когда вы создаете конкретные объекты на базе этих классов, каждый объект автоматически наделяется общим поведением; после этого вы можете наделить каждый объект уникальными особенностями на свой выбор. Просто невероятно, насколько хорошо реальные ситуации моделируются в объектно-ориентированном программировании.

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

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

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

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

 

Создание и использование класса

Классы позволяют моделировать практически все что угодно. Начнем с написания простого класса Dog, представляющего собаку — не какую-то конкретную, а собаку вообще. Что мы знаем о собаках? У них есть кличка и возраст. Также известно, что большинство собак умеют садиться и перекатываться по команде. Эти два вида информации (кличка и возраст) и два вида поведения (сидеть и перекатываться) будут включены в класс Dog, потому что они являются общими для большинства собак. Класс сообщает Python, как создать объект, представляющий собаку. После того как класс будет написан, мы используем его для создания экземпляров, каждый из которых представляет одну конкретную собаку.

 

Создание класса Dog

В каждом экземпляре, созданном на основе класса Dog, будет храниться кличка и возраст; кроме того, в нем будут присутствовать методы sit() и roll_over():

dog.py

(1) class Dog():

(2) . ."""Простая модель собаки."""

(3) . .def __init__(self, name, age):

. . . ."""Инициализирует атрибуты name и age."""

(4) . . . .self.name = name

. . . .self.age = age

. . . .

(5) . .def sit(self):

. . . ."""Собака садится по команде."""

. . . .print(self.name.title() + " is now sitting.")

. .def roll_over(self):

. . . ."""Собака перекатывается по команде."""

. . . .print(self.name.title() + " rolled over!")

В этом коде есть много мест, заслуживающих вашего внимания, но не беспокойтесь. Эта структура неоднократно встретится вам в этой главе, и вы еще успеете к ней привыкнуть. В точке (1) определяется класс с именем Dog. По общепринятым соглашениям имена, начинающиеся с символа верхнего регистра, в Python обозначают классы. Круглые скобки в определении класса пусты, потому что класс создается с нуля. В точке (2) приведена строка документации с кратким описанием класса.

 

Метод __init__()

Функция, являющаяся частью класса, называется методом. Все, что вы узнали ранее о функциях, также относится и к методам; единственное практическое различие — способ вызова методов. Метод __init__() в точке (3) — специальный метод, который автоматически выполняется при создании каждого нового экземпляра на базе класса Dog. Имя метода начинается и заканчивается двумя символами подчеркивания; эта схема предотвращает конфликты имен стандартных методов Python и методов ваших классов.

Метод __init__() определяется с тремя параметрами: self, name и age. Параметр self обязателен в определении метода; он должен предшествовать всем остальным параметрам. Он должен быть включен в определение для того, чтобы при ­будущем вызове метода __init__() (для создания экземпляра Dog) автоматически передавался аргумент self. При каждом вызове метода, связанного с классом, автоматически передается self — ссылка на экземпляр; она предоставляет конкретному экземпляру доступ к атрибутам и методам класса. Когда вы создаете экземпляр Dog, Python вызывает метод __init__() из класса Dog. Мы передаем Dog() кличку и возраст в аргументах; значение self передается автоматически, так что его передавать не нужно. Каждый раз, когда вы захотите создать экземпляр на основе класса Dog, необходимо предоставить значения только двух последних аргументов name и age.

Каждая из двух переменных, определяемых в точке (4), имеет префикс self. Любая переменная с префиксом self доступна для каждого метода в классе, и вы также сможете обращаться к этим переменным в каждом экземпляре, созданном на основе класса. Конструкция self.name = name берет значение, хранящееся в параметре name, и сохраняет его в переменной name, которая затем связывается с создаваемым экземпляром. Процесс также повторяется с self.age = age. Переменные, к которым вы обращаетесь через экземпляры, тоже называются атрибутами.

В классе Dog также определяются два метода: sit() и roll_over() (5). Так как этим методам не нужна дополнительная информация (кличка или возраст), они определяются с единственным параметром self. Экземпляры, которые будут созданы позднее, смогут вызывать эти методы. Пока методы sit() и roll_over() ограничиваются простым выводом сообщения о том, что собака садится или перекатывается. Тем не менее концепцию легко расширить для практического применения: если бы этот класс был частью компьютерной игры, то эти методы вполне могли бы содержать код для создания анимации садящейся или перекатывающейся собаки. А если бы класс был написан для управления роботом, то методы могли бы управлять механизмами, заставляющими робота-собаку выполнить соответствующую команду.

 

Создание классов в Python 2.7

При создании классов в Python 2.7 необходимо внести одно незначительное изменение — заключить в круглые скобки ключевое слово object:

class ClassName(object):

. ....

В этом случае поведение классов Python 2.7 будет приближено к поведению классов Python 3, что упрощает вашу работу в целом.

Скажем, в Python 2.7 класс Dog будет определяться следующим образом:

class Dog(object):

. ....

 

Создание экземпляра класса

Считайте, что класс — это своего рода инструкция по созданию экземпляров. Соответственно, класс Dog — инструкция по созданию экземпляров, представляющих конкретных собак.

Создадим экземпляр, представляющий конкретную собаку:

class Dog():

. ....

(1) my_dog = Dog('willie', 6)

(2)print("My dog's name is " + my_dog.name.title() + ".")

(3)print("My dog is " + str(my_dog.age) + " years old.")

Использованный в данном случае класс Dog был написан в предыдущем примере. В точке (1) мы приказываем Python создать экземпляр собаки с кличкой 'willie' и возрастом 6. В процессе обработки этой строки Python вызывает метод __init__() класса Dog с аргументами 'willie' и 6. Метод __init__() создает ­экземпляр, представляющий конкретную собаку, и присваивает его атрибутам name и age переданные значения. Метод __init__() не содержит явной команды return, но Python автоматически возвращает экземпляр, представляющий собаку. Этот экземпляр сохраняется в переменной my_dog. Здесь нелишне вспомнить соглашения по записи имен: обычно считается, что имя, начинающееся с символа верхнего регистра (например, Dog), обозначает класс, а имя, записанное в нижнем регистре (например, my_dog), обозначает отдельный экземпляр, созданный на базе класса.

 

Обращение к атрибутам

Для обращения к атрибутам экземпляра используется «точечная» запись. В строке (2) мы обращаемся к значению атрибута name экземпляра my_dog:

my_dog.name

Точечная запись часто используется в Python. Этот синтаксис показывает, как Python ищет значения атрибутов. В данном случае Python обращается к экземпляру my_dog и ищет атрибут name, связанный с экземпляром my_dog. Это тот же атрибут, который обозначался self.name в классе Dog. В точке (3) тот же прием используется для работы с атрибутом age. В первой команде print вызов my_dog.name.title() записывает 'willie' (значение атрибута name экземпляра my_dog) с символа верхнего регистра. Во второй команде print вызов str(my_dog.age) преобразует 6, значение атрибута age экземпляра my_dog, в строку.

Пример выводит сводку известных фактов о 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()

Чтобы вызвать метод, укажите экземпляр (в данном случае 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("My dog's name is " + my_dog.name.title() + ".")

print("My dog is " + str(my_dog.age) + " years old.")

my_dog.sit()

print("\nYour dog's name is " + your_dog.name.title() + ".")

print("Your dog is " + str(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. Вы можете создать сколько угодно ­экземпляров одного класса при условии, что эти экземпляры хранятся в переменных с разными именами или занимают разные позиции в списке или словаре:

Упражнения

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

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

(1) . .def __init__(self, make, model, year):

. . . ."""Инициализирует атрибуты описания автомобиля."""

. . . .self.make = make

. . . .self.model = model

. . . .self.year = year

. . . .

(2) . .def get_descriptive_name(self):

. . . ."""Возвращает аккуратно отформатированное описание."""

. . . .long_name = str(self.year) + ' ' + self.make + ' ' + self.model

. . . .return long_name.title()

. .

(3)my_new_car = Car('audi', 'a4', 2016)

print(my_new_car.get_descriptive_name())

В точке (1) в классе Car определяется метод __init__(); его список параметров начинается с self, как и в классе Dog. За ним следуют еще три параметра: make, model и year. Метод __init__() получает эти параметры и сохраняет их в атрибутах, которые будут связаны с экземплярами, созданными на основе класса. При создании нового экземпляра Car необходимо указать фирму-производителя, модель и год выпуска для данного экземпляра.

В точке (2) определяется метод get_descriptive_name(), который объединяет год выпуска, фирму-производителя и модель в одну строку с описанием. Это избавит вас от необходимости выводить значение каждого атрибута по отдельности. Для работы со значениями атрибутов в этом методе используется синтаксис self.make, self.model и self.year.

В точке (3) создается экземпляр класса 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

(1) . . . .self.odometer_reading = 0

. . . .

def get_descriptive_name(self):

...

. .

(2) . .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()

Когда Python вызывает метод __init__() для создания нового экземпляра, этот метод сохраняет фирму-производителя, модель и год выпуска в атрибутах, как и в предыдущем случае. Затем Python создает новый атрибут с именем odometer_reading и присваивает ему исходное значение 0 (1) . Также в класс добавляется новый метод read_odometer() (2), который упрощает чтение пробега машины в милях.

Сразу же после создания машины ее пробег равен 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())

(1) my_new_car.odometer_reading = 23

my_new_car.read_odometer()

В точке (1) точечная запись используется для обращения к атрибуту odometer_reading экземпляра и прямого присваивания его значения. Эта строка приказывает Python взять экземпляр my_new_car, найти связанный с ним атрибут odometer_reading и задать значение атрибута равным 23:

2016 Audi A4

This car has 23 miles on it.

Иногда подобные прямые обращения к атрибутам допустимы, но чаще разработчик пишет вспомогательный метод, который изменяет значение за него.

 

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

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

В следующем примере в класс включается метод update_odometer() для изменения показаний одометра:

class Car():

. ....

. . . .

(1) . .def update_odometer(self, mileage):

. . . ."""Устанавливает заданное значение на одометре."""

. . . .self.odometer_reading = mileage

. .

my_new_car = Car('audi', 'a4', 2016)

print(my_new_car.get_descriptive_name())

(2)my_new_car.update_odometer(23)

my_new_car.read_odometer()

Класс Car почти не изменился, в нем только добавился метод update_odometer()(1) . Этот метод получает пробег в милях и сохраняет его в self.odometer_reading. В точке (2) мы вызываем метод 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):

. . . ."""

. . . .Устанавливает на одометре заданное значение.

. . . .При попытке обратной подкрутки изменение отклоняется.

. . . ."""

(1) . . . .if mileage >= self.odometer_reading:

. . . . . .self.odometer_reading = mileage

. . . .else:

(2) . . . . . .print("You can't roll back an odometer!")

Теперь update_odometer() проверяет новое значение перед изменением атрибута. Если новое значение mileage больше или равно текущего, self.odometer_reading, показания одометра можно обновить новым значением (1) . Если же новое значение меньше текущего, вы получите предупреждение о недопустимости обратной подкрутки (2).

 

Изменение значения атрибута с приращением

Иногда значение атрибута требуется изменить с заданным приращением (вместо того чтобы присваивать атрибуту произвольное новое значение). Допустим, вы купили подержанную машину и проехали на ней 100 миль. Следующий метод получает величину приращения и прибавляет ее к текущим показаниям одометра:

class Car():

...

def update_odometer(self, mileage):

--snip--

. .

(1) . .def increment_odometer(self, miles):

. . . ."""Увеличивает показания одометра с заданным приращением."""

. . . .self.odometer_reading += miles

. .

(2)my_used_car = Car('subaru', 'outback', 2013)

print(my_used_car.get_descriptive_name())

(3)my_used_car.update_odometer(23500)

my_used_car.read_odometer()

(4)my_used_car.increment_odometer(100)

my_used_car.read_odometer()

Новый метод increment_odometer() в точке (1) получает расстояние в милях и прибавляет его к self.odometer_reading. В точке (2) создается экземпляр my_used_car. Мы инициализируем показания его одометра значением 23 500; для этого вызывается метод update_odometer(), которому передается значение 23500 (3). В точке (4) вызывается метод 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(), позволяющий задать количество обслуженных посетителей. Вызовите метод с новым числом, снова выведите значение.

Добавьте метод с именем 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

(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

(2)class ElectricCar(Car):

. ."""Представляет аспекты машины, специфические для электромобилей."""

(3) . .def __init__(self, make, model, year):

. . . ."""Инициализирует атрибуты класса-родителя."""

(4) . . . .super().__init__(make, model, year)

. . . .

(5)my_tesla = ElectricCar('tesla', 'model s', 2016)

print(my_tesla.get_descriptive_name())

В точке (1) строится экземпляр Car. При создании класса-потомка класс-родитель должен быть частью текущего файла, а его определение должно предшествовать определению класса-потомка в файле. В точке (2) определяется класс-потомок ElectricCar. В определении потомка имя класса-родителя заключается в круглые скобки. Метод __init__() в точке (3) получает информацию, необходимую для создания экземпляра Car.

Функция super() в строке (4) — специальная функция, которая помогает Python связать потомка с родителем. Эта строка приказывает Python вызвать метод __init__() класса, являющегося родителем ElectricCar, в результате чего экземпляр ElectricCar получает все атрибуты класса-родителя. Имя super происходит из распространенной терминологии: класс-родитель называется суперклассом, а класс-потомок — субклассом.

Чтобы проверить, правильно ли сработало наследование, попробуем создать электромобиль с такой же информацией, которая передается при создании обычного экземпляра Car. В точке (5) мы создаем экземпляр класса ElectricCar и сохраняем его в my_tesla. Эта строка вызывает метод __init__(), определенный в ElectricCar, который в свою очередь приказывает Python вызвать метод __init__(), определенный в классе-родителе Car. При вызове передаются аргументы 'tesla', 'model s' и 2016.

Кроме __init__() класс еще не содержит никаких атрибутов или методов, специ­фических для электромобилей. Пока мы просто убеждаемся в том, что класс электромобиля содержит все поведение, присущее классу автомобиля:

2016 Tesla Model S

Экземпляр ElectricCar работает так же, как экземпляр Car; можно переходить к определению атрибутов и методов, специфических для электромобилей.

 

Наследование в 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)

(1) . . . .self.battery_size = 70

. . . .

(2) . .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()

В точке (1) добавляется новый атрибут self.battery_size, которому присваивается исходное значение — скажем, 70. Этот атрибут будет присутствовать во всех экземплярах, созданных на основе класса ElectricCar (но не во всяком экземпляре Car). Также добавляется метод с именем describe_battery(), который выводит информацию об аккумуляторе в точке (2). При вызове этого метода выводится описание, которое явно относится только к электромобилям:

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:

class Car():

...

(1) class Battery():

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

(2) . .def __init__(self, battery_size=70):

. . . ."""Инициализирует атрибуты аккумулятора."""

. . . .self.battery_size = battery_size

(3) . .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)

(4) . . . .self.battery = Battery()

my_tesla = ElectricCar('tesla', 'model s', 2016)

print(my_tesla.get_descriptive_name())

my_tesla.battery.describe_battery()

В точке (1) определяется новый класс с именем Battery, который не наследует ни от одного из других классов. Метод __init__() в точке (2) получает один параметр battery_size, кроме self. Если значение не предоставлено, этот необязательный параметр задает battery_size значение 70. Метод describe_battery() также перемещен в этот класс (3).

Затем в класс ElectricCar добавляется атрибут с именем self.battery (4). Эта строка приказывает 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.

Казалось бы, новый вариант требует большой дополнительной работы, но теперь аккумулятор можно моделировать с любой степенью детализации без загромождения класса ElectricCar. Добавим в Battery еще один метод, который выводит запас хода на основании мощности аккумулятора:

class Car():

...

class Battery():

...

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

(2)my_tesla.battery.get_range()

Новый метод get_range() в точке (1) проводит простой анализ. Если мощность равна 70, то get_range() устанавливает запас хода 240 миль, а при мощности 85 kWh запас хода равен 270 милям. Затем программа выводит это значение. Когда вы захотите использовать этот метод, его придется вызывать через атрибут battery в точке (2).

Результат сообщает запас хода машины в зависимости от мощности аккумулятора:

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() по-прежнему будет проверять мощность аккумулятора перед определением запаса хода, но он будет сообщать запас хода для той машины, с которой он связан. Также возможно связать метод 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 позволяет хранить классы в модулях и импортировать нужные классы в основную программу.

 

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

Начнем с создания модуля, содержащего только класс 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

Стандартная библиотека Python представляет собой набор модулей, включаемых в каждую установленную копию Python. Сейчас вы уже примерно понимаете, как работают классы, и можете начать использовать модули, написанные другими программистами. Чтобы использовать любую функцию или класс из стандартной библиотеки, достаточно включить простую команду import в начало файла. Для примера рассмотрим класс OrderedDict из модуля collections.

Как вы уже знаете, словари позволяют связывать информационные фрагменты, но они не отслеживают порядок добавления пар «ключ—значение». Если вы хотите создать словарь, но при этом сохранить порядок добавления пар «ключ—значение», воспользуйтесь классом OrderedDict из модуля collections. Экземпляры класса OrderedDict ведут себя практически так же, как и словари, если не считать того, что они отслеживают порядок добавления пар «ключ—значение».

Вернемся к примеру favorite_languages.py из главы 6. На этот раз программа будет отслеживать порядок, в котором участники отвечают на опрос:

favorite_languages.py

(1) from collections import OrderedDict

(2)favorite_languages = OrderedDict()

(3)favorite_languages['jen'] = 'python'

favorite_languages['sarah'] = 'c'

favorite_languages['edward'] = 'ruby'

favorite_languages['phil'] = 'python'

(4)for name, language in favorite_languages.items():

. .print(name.title() + "'s favorite language is " +

. . . .language.title() + ".")

Сначала программа импортирует класс OrderedDict из модуля collections в точке (1) . В точке (2) создается экземпляр класса OrderedDict, который сохраняется в favorite_languages. Обратите внимание на отсутствие фигурных скобок; вызов OrderedDict() создает пустой упорядоченный словарь и сохраняет его в favorite_languages. Затем пары из имени и языка последовательно добавляются в словарь (3). Теперь при переборе favorite_languages в точке (4) данные всегда будут выдаваться в порядке их добавления:

Jen's favorite language is Python.

Sarah's favorite language is C.

Edward's favorite language is Ruby.

Phil's favorite language is Python.

Это чрезвычайно полезный класс, объединяющий основное преимущество списков (сохранение исходного порядка) с главной особенностью словарей (связывание двух видов информации). Когда вы займетесь моделированием реальных ситуаций, может возникнуть ситуация, в которой упорядоченный словарь окажется именно тем, что вам необходимо. А по мере изучения стандартной библиотеки вы узнаете о других полезных модулях, которые помогут вам в решении типичных задач.

примечание

Модули также можно загружать из внешних источников. Соответствующие примеры встретятся вам в части II, в которой для завершения работы над проектами мы будем использовать внешние модули.

Упражнения

9-13. Переработка с OrderedDict Rewrite: начните с упражнения 6-4 (с. 113), в котором стандартный словарь используется для представления глоссария. Перепишите программу с использованием класса OrderedDict и убедитесь в том, что порядок вывода совпадает с порядком добавления пар «ключ—значение» в словарь.

9-14. Кубики: модуль random содержит функции для генерирования случайных чисел разными способами. Функция randint() возвращает целое число в заданном диапазоне. Следующий код возвращает число от 1 до 6:

from random import randint

x = randint(1, 6)

Создайте класс Die с одним атрибутом с именем sides, который содержит значение по умолчанию 6. Напишите метод roll_die() для вывода случайного числа от 1 до количества сторон кубика. Создайте экземпляр, моделирующий 6-гранный кубик, и имитируйте 10 бросков.

Создайте модели 10- и 20-гранного кубика. Имитируйте 10 бросков каждого кубика.

9-15. Модуль недели: для знакомства со стандартной библиотекой Python отлично подойдет сайт Python Module of the Week. Откройте сайт http://pymotw.com/ и просмотрите оглавление. Найдите модуль, который покажется вам интересным, и прочитайте про него или изучите документацию по модулям collections и random.

 

Оформление классов

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

Имена классов должны записываться в «верблюжьей» схеме: первая буква каждого слова записывается в верхнем регистре, слова не разделяются пробелами. Имена экземпляров и модулей записываются в нижнем регистре с разделением слов символами подчеркивания.

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

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

Если вам потребуется импортировать модуль из стандартной библиотеки и модуль из библиотеки, написанной вами, начните с команды import для модуля стандартной библиотеки. Затем добавьте пустую строку и команду import для модуля, написанного вами. В программах с несколькими командами import выполнение этого соглашения поможет понять, откуда берутся разные модули, использованные в программе.

 

Итоги

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

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

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