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



Pdf көрінісі
бет92/238
Дата07.01.2022
өлшемі7,86 Mb.
#18670
түріКнига
1   ...   88   89   90   91   92   93   94   95   ...   238
Байланысты:
2 5343781172763690906

ПРИМЕЧАНИЕ

Подобные методы управляют обновлением внутренних значений экземпляров (таких, как показа-

ния одометра), однако любой пользователь, имеющий доступ к программному коду, сможет напря-

мую задать атрибуту любое значение . Эффективная схема безопасности должна уделять особое 

внимание таким подробностям, не ограничиваясь простейшими проверками .

УПРАЖНЕНИЯ

9-4 .  Посетители:  начните  с  программы  из  упражнения  9-1  (с .  165) .  Добавьте  атрибут 

number_served со значением по умолчанию 0; он представляет количество обслуженных 

посетителей . Создайте экземпляр с именем restaurant . Выведите значение number_served, 

потом измените и выведите снова .

Добавьте метод с именем set_number_served(), позволяющий задать количество обслужен-

ных посетителей . Вызовите метод с новым числом, снова выведите значение .



170    Глава 9  •  Классы

Добавьте  метод  с  именем  increment_number_served(),  который  увеличивает  количество 

обслуженных посетителей на заданную величину . Вызовите этот метод с любым числом

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

9-5 .  Попытки  входа:  добавьте  атрибут  login_attempts  в  класс  User  из  упражнения  9-3 

(с .  165) .  Напишите  метод  increment_login_attempts(),  увеличивающий  значение  login_

attempts на 1 . Напишите другой метод с именем reset_login_attempts(), обнуляющий значе-

ние login_attempts .

Создайте экземпляр класса User и вызовите increment_login_attempts() несколько раз . Вы-

ведите значение login_attempts, чтобы убедиться в том, что значение было изменено пра-

вильно, а затем вызовите reset_login_attempts() . Снова выведите login_attempts и убеди-

тесь в том, что значение обнулилось .

Наследование

Работа над новым классом не обязана начинаться с нуля. Если класс, который вы 

пишете, представляет собой специализированную версию ранее написанного клас-

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

автоматически получает все атрибуты и методы первого класса. Исходный класс 

называется родителем, а новый класс — потомком. Класс-потомок наследует 

атрибуты и методы родителя, но при этом также может определять собственные 

атрибуты и методы.

Метод __init__() класса-потомка

Первое, что делает Python при создании экземпляра класса-потомка, — присваивает 

значения всем атрибутам класса-родителя. Для этого методу 

__init__()

 класса-по-

томка необходима помощь со стороны родителя.

Например, попробуем построить модель электромобиля. Электромобиль пред-

ставляет собой специализированную разновидность автомобиля, поэтому новый 

класс 

ElectricCar



 можно создать на базе класса 

Car


, написанного ранее. Тогда 

нам останется добавить в него код атрибутов и поведения, относящегося только 

к электромобилям.

Начнем с создания простой версии класса 

ElectricCar

, который делает все, что 

делает класс 

Car


:

electric_car.py

class Car():



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

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

self.make = make

self.model = model

self.year = year

self.odometer_reading = 0

def get_descriptive_name(self):

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

return long_name.title()



Наследование    171

def read_odometer(self):

print("This car has " + str(self.odometer_reading) + " miles on it.")

def update_odometer(self, mileage):

if mileage >= self.odometer_reading:

self.odometer_reading = mileage

else:

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



def increment_odometer(self, miles):

self.odometer_reading += miles

class ElectricCar(Car):



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

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



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

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



        

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



print(my_tesla.get_descriptive_name())

В точке  строится экземпляр 

Car

. При создании класса-потомка класс-родитель 



должен быть частью текущего файла, а его определение должно предшествовать 

определению класса-потомка в файле. В точке  определяется класс-потомок 

ElectricCar

. В определении потомка имя класса-родителя заключается в круглые 

скобки. Метод 

__init__()

 в точке  получает информацию, необходимую для 

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

Car

.

Функция 



super()

 в строке  — специальная функция, которая помогает Python 

связать потомка с родителем. Эта строка приказывает Python вызвать метод 

__init__()

 класса, являющегося родителем 

ElectricCar

, в результате чего экзем-

пляр 


ElectricCar

 получает все атрибуты класса-родителя. Имя 

super

 происходит 



из распространенной терминологии: класс-родитель называется суперклассом

а класс-потомок — субклассом.

Чтобы проверить, правильно ли сработало наследование, попробуем создать 

электромобиль с такой же информацией, которая передается при создании 

обычного экземпляра 

Car


. В точке  мы создаем экземпляр класса 

ElectricCar

 

и сохраняем его в 



my_tesla

. Эта строка вызывает метод 

__init__()

, определен-

ный в 

ElectricCar



, который в свою очередь приказывает Python вызвать метод 

__init__()

, определенный в классе-родителе 

Car


. При вызове передаются аргу-

менты 


'tesla'

'model



 

s'

 и 



2016

.

Кроме 



__init__()

 класс еще не содержит никаких атрибутов или методов, специ-

фических для электромобилей. Пока мы просто убеждаемся в том, что класс 

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

2016 Tesla Model S

Экземпляр 

ElectricCar

 работает так же, как экземпляр 

Car

; можно переходить 



к определению атрибутов и методов, специфических для электромобилей.


172    Глава 9  •  Классы

Наследование в Python 2 .7

В Python 2.7 наследование реализовано немного иначе. Класс 

ElectricCar

 будет 

выглядеть примерно так:

class Car(object):

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

...

class ElectricCar(Car):



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

        super(ElectricCar, self).__init__(make, model, year)

...

Функция 


super()

 должна получать два аргумента: ссылку на класс-потомок и объ-

ект 

self


. Эти аргументы необходимы для того, чтобы Python мог правильно связать 

родителя с потомком. Если вы используете наследование в Python 2.7, убедитесь 

в том, что родитель также определяется с синтаксисом 

object


.

Определение атрибутов и методов класса-потомка

После создания класса-потомка, наследующего от класса-родителя, можно пере-

ходить к добавлению новых атрибутов и методов, необходимых для того, чтобы 

потомок отличался от родителя.

Добавим атрибут, специфический для электромобилей (например, мощность ак-

кумулятора), и метод для вывода информации об этом атрибуте:

class Car():

...

class ElectricCar(Car):



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

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

        """

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

        Затем инициализирует атрибуты, специфические для электромобиля.

        """

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

        self.battery_size = 70



        

    def describe_battery(self):



        """Выводит информацию о мощности аккумулятора."""

        print("This car has a " + str(self.battery_size) + "-kWh battery.")

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

print(my_tesla.get_descriptive_name())

my_tesla.describe_battery()

В точке  добавляется новый атрибут 

self.battery_size

, которому присваивается 

исходное значение — скажем, 70. Этот атрибут будет присутствовать во всех экзем-

плярах, созданных на основе класса 

ElectricCar

 (но не во всяком экземпляре 

Car

). 


Также добавляется метод с именем 

describe_battery()

, который выводит инфор-



Наследование    173

мацию об аккумуляторе в точке . При вызове этого метода выводится описание, 

которое явно относится только к электромобилям:

2016 Tesla Model S 

This car has a 70-kWh battery.

Возможности специализации класса 

ElectricCar

 беспредельны. Вы можете до-

бавить сколько угодно атрибутов и методов, чтобы моделировать электромобиль 

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

любой машине (а не только электромобилю), должны добавляться в класс 

Car


 вме-

сто 


ElectricCar

. Тогда эта информация будет доступна всем пользователям класса 

Car

, а класс 



ElectricCar

 будет содержать только код информации и поведения, 

специфических для электромобилей.

Переопределение методов класса-родителя

Любой метод родительского класса, который в моделируемой ситуации делает 

не то, что нужно, можно переопределить. Для этого в классе-потомке определяется 

метод с тем же именем, что и у метода класса-родителя. Python игнорирует метод 

родителя и обращает внимание только на метод, определенный в потомке.

Допустим, в классе 

Car


 имеется метод 

fill_gas_tank()

. Для электромобилей за-

правка бензином бессмысленна, поэтому этот метод логично переопределить. На-

пример, это можно сделать так:

def ElectricCar(Car):

...

    def fill_gas_tank():



        """У электромобилей нет бензобака."""

        print("This car doesn't need a gas tank!")

И если кто-то попытается вызвать метод 

fill_gas_tank()

 для электромобиля, 

Python игнорирует метод 

fill_gas_tank()

 класса 


Car

 и выполнит вместо него этот 

код. С применением наследования потомок сохраняет те аспекты родителя, которые 

вам нужны, и переопределяет все ненужное.

Экземпляры как атрибуты

При моделировании явлений реального мира в программах классы нередко до-

полняются все большим количеством подробностей. Списки атрибутов и мето-

дов растут, и через какое-то время файлы становятся длинными и громоздкими. 

В такой ситуации часть одного класса нередко можно записать в виде отдельного 

класса. Большой код разбивается на меньшие классы, которые работают во взаи-

модействии друг с другом.

Например, при дальнейшей доработке класса 

ElectricCar

 может оказаться, что 

в нем появилось слишком много атрибутов и методов, относящихся к аккумулято-

ру. В таком случае можно остановиться и переместить все эти атрибуты и методы 

в отдельный класс с именем 

Battery


. Затем экземпляр 

Battery


 становится атрибу-

том класса 

ElectricCar

:



174    Глава 9  •  Классы

class Car():

...



class Battery():



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

    def __init__(self, battery_size=70):



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

        self.battery_size = battery_size

    def describe_battery(self):



        """Выводит информацию о мощности аккумулятора."""

        print("This car has a " + str(self.battery_size) + "-kWh battery.")     

class ElectricCar(Car):

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

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

"""


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

Затем инициализирует атрибуты, специфические для электромобиля.

"""

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



        self.battery = Battery()

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

print(my_tesla.get_descriptive_name())

my_tesla.battery.describe_battery()

В точке  определяется новый класс с именем 

Battery

, который не наследует 

ни от одного из других классов. Метод 

__init__()

 в точке  получает один пара-

метр 


battery_size

, кроме 


self

. Если значение не предоставлено, этот необязатель-

ный параметр задает 

battery_size

 значение 70. Метод 

describe_battery()

 также 

перемещен в этот класс .

Затем в класс 

ElectricCar

 добавляется атрибут с именем 

self.battery

 . Эта стро-

ка приказывает Python создать новый экземпляр 

Battery

 (со значением 

battery_

size


 по умолчанию, равным 70, потому что значение не задано) и сохранить его 

в атрибуте 

self.battery

. Это будет происходить при каждом вызове 

__init__()

теперь любой экземпляр 



ElectricCar

 будет иметь автоматически создаваемый 

экземпляр 

Battery


.

Программа создает экземпляр электромобиля и сохраняет его в переменной 

my_

tesla


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

к атрибуту 

battery

:

my_tesla.battery.describe_battery()



Эта строка приказывает Python обратиться к экземпляру 

my_tesla


, найти его 

атрибут 


battery

 и вызвать метод 

describe_battery()

, связанный с экземпляром 

Battery

 из атрибута.

Результат выглядит так же, как и в предыдущей версии:

2016 Tesla Model S 

This car has a 70-kWh battery.



Наследование    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, а о том, как представить реальный мир в коде. И, до-

стигнув этой точки, вы поймете, что однозначно правильного или неправильного 

подхода к моделированию реальных ситуаций часто не существует. Некоторые 

методы эффективнее других, но для того, чтобы найти наиболее эффективную 

реализацию, необходим практический опыт. Если ваш код работает именно так, 

как вы хотели, — значит, у вас все получается! Не огорчайтесь, если окажется, что 

вы по несколько раз переписываете свои классы для разных решений. На пути 

к написанию точного, эффективного кода все программисты проходят через этот 

процесс.



Достарыңызбен бөлісу:
1   ...   88   89   90   91   92   93   94   95   ...   238




©emirsaba.org 2024
әкімшілігінің қараңыз

    Басты бет