Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебное пособие 1606.pdf
Скачиваний:
18
Добавлен:
30.04.2022
Размер:
1.48 Mб
Скачать

divide(2, 0) division by zero!

executing finally clause

В реальных приложениях инструкцию finally удобно применять для освобождения ресурсов (таких как соединения по сети), не смотря на то, было ли успешным их использование или нет!

4.2. Итераторы и генераторы

Итераторы

Когда вы создаёте список, вы можете считывать его элементы один за другим – это называется итерацией:

>>>mylist = [1, 2, 3]

>>>for i in mylist :

... print(i) 1 2

3

Mylist является итерируемым объектом.

Всё, к чему можно применить конструкцию «for… in...», является итерируемым объектом: списки, строки, файлы… Это удобно, потому что можно считывать из них значения сколько потребуется – однако все значения хранятся в памяти, а это не всегда желательно, если у вас много значений.

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

Когда данных не остается – вызывается исключение StopIteration. Итератор также должен иметь метод __iter__(), возвращающий сам итератор.

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

Контейнеру необходимо определить один метод, чтобы обеспечить поддержку итерации:

container.__iter__() - Возвращает итерируемый объект. Объект должен поддерживать протокол итератора, описанный ниже.

Сами итерируемые объекты должны следующие 2 метода, составляющие протокол итератора:!

iterator.__iter__() – Возвращает сам объект итератора.

38

iterator.__next__() – Возвращает следующий элемент из контейнера. Если больше нет элементов – вызывается исключение StopIteration.

Генераторы

Генераторы это тоже итерируемые объекты, но прочитать их можно лишь один раз. Это связано с тем, что они не хранят значения в памяти, а генерируют их на лету:

>>>mygenerator = (x*x for x in range(3))

>>>for i in mygenerator :

... print(i)

0

1

4

Генератор - Функция, которая возвращает итератор. Она выглядит как обычная функция, за исключением того, что она содержит оператор yield, возвращающий серию значений, используемых в цикле или через функцию next(). Каждый оператор yield временно замораживает процесс, запоминая позицию выполнения (включая внутренние переменные).

# генератор чисел Фибоначчи def fibonacci(max):

a, b = 1, 2 while a < max: yield a

a, b = b, a+b

4.3.Практические задания

1.Напишите программу, которая обрабатывает исключение деления числа на 0.

2.Напишите программу с использованием генератора.

3.Напишите программу с использованием итератора.

Вопросы для самопроверки

1.Как использовать инструкция try/finally в Python?

2.Как использовать итераторы и генераторы в Python?

3.Как использовать инструкция try/else в Python?

4.Как использовать декораторы в Python?

5.Как использовать инструкцию try/finally в Python?

39

5. ОСНОВЫ ОБЪЕКТНО-ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ В ЯЗЫКЕ PYTHON

Главная идея ООП состоит в том, чтобы объединить в одно целое данные

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

– совершенно разные вещи (или сущности). Допустим, нам нужно собрать автомобиль. Завод у нас есть, и мы можем произвести любую деталь. Но этого мало. Нам нужен некий план или чертеж, который бы давал нам четкое и однозначное представление, какие детали в автомобиле, как и куда крепятся.

Понятно, что на самом деле там очень много чертежей для разных блоков

имеханизмов. Но в данном случае это не важно. Мы можем думать, что чертеж один. Вот этот чертеж, на основе которого собирается автомобиль – это аналог класса. А автомобиль, который собирается в соответствии с чертежом – аналог объекта, созданного на основе класса.

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

То есть ситуация следующая:

Есть чертеж автомобиля. Это аналог класса.

На основе чертежа можно собирать автомобили. Автомобиль, собранный на основе чертежа – аналог объекта, который создан на основе класса.

Вязыке Python – объектом является не только то, что мы создаем на основе класса, но и сам класс является объектом. Точно так же, как бумажный лист с чертежом является объектом, отличным от автомобиля, объект, которым является класс, отличается от объекта, который создается на основе класса. И здесь мы подходим к очень важному месту: к терминологии. Тот объект, который создается на основе класса, мы будем называть экземпляром касса. Тот объект, через который реализуется класс, будем называть объектом класса.

Создать класс можно следующим образом:

сlass имя_класса:

# блок тела класса

Все начинается с ключевого слова class, потом имя класса и двоеточие. Затем тело класса. При описании блок тела (инструкций) необходимо делать

40

отступы – четыре пробела. В теле класса, как правило, описывают методы. Метод – это та же функция, только вызываться она будет из экземпляра класса.

Формально метод в теле класса описывается как обычная функция. Но методы, как мы уже знаем, должны вызываться из экземпляра класса. При описании метода экземпляр класса, из которого будет вызываться метод, должен быть явно указан как первый аргумент метода. При вызове метода этот аргумент методу явно не передается. Причина в том, что экземпляр класса, из которого вызывается метод, указывается явно (перед именем метод через точку). Получается такое своеобразное правило, которое условно можно назвать "минус один аргумент": при вызове метода из экземпляра класса у него на один аргумент меньше, чем это было при описании метода (это если нет аргументов со значением по умолчанию)

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

После того, как класс создан, возникает следующий вопрос: как на основе класса создать экземпляр класса? Делается это очень просто с помощью команды вида переменная=класс (). Другими словами, после имени класса указываем круглые скобки (пока что пустые) и всю эту конструкцию присваиваем в качестве значения некоторой переменной. Как результат будет создан экземпляр класса, а ссылка на этот экземпляр записана в переменную.

Метод, описанный в классе как метод экземпляр класса, вызывается из экземпляра класса с помощью "точечного синтаксиса": после экземпляра класса через точку указывается имя вызываемого метода. При этом методу в круглых скобках передаются нужные аргументы. Если аргументы методу передавать не нужно, пустые круглые скобки после имени метода все равно указываем.

#создаем класс с названием MyClass Class MyClass :

#метод экземпляра класса

#единственный аргумент метод self – ссылка

# на экземпляр класса, из которого вызывается метод

def say_hello(self) :

#Методом отображается сообщение. Аргумент

#метода (ссылка self) явно не используется

print(‘Вас приветствует экземпляр класса !’)

#Создаем экземпляр класса obj = MyClass()

#Вызываем метод экземпляра класса

#При вызове аргументы методу не передаются obj.say_hello()

41

Класс и экземпляр класса

В результате выполнения данного программного кода в окне вывода появляются такое сообщение:

Вас приветствует экземпляр класса!

Мы создали класс с названием MyClass. В теле класса описан всего один метод, который называется say_hello(), и у этого метода, как несложно заметить, один аргумент, который называется self. В теле метода имеется одна команда print("Вас приветствует экземпляр класса!"), в результате выполнения которой, как мы не без оснований ожидаем, в окне вывода появится текстовое сообщение. Экземпляр класса создается командой obj=MyClass(). В данном случае экземпляр класса создается непосредственно при выполнении инструкции MyClass() , а ссылка на этот экземпляр записывается в переменную obj.

Хотя ничего неправильного или некорректного в этом нет, такой подход граничит с дурным тоном. Почему? Как минимум потому, что если нас устраивает ситуация, когда все экземпляры класса "ведут" себя одинаково, то возникают сомнения в необходимости использования ООП для решения соответствующей задачи. Проще говоря, здесь имеет место не совсем адекватное использование объектно-ориентированного подхода. Поэтому обычно методы, которые описываются как методы экземпляра класса, в том или ином виде используют ссылку на экземпляр класса, из которого вызывается метод (первый аргумент с рекомендованным названием self). Прежде, чем приступить к рассмотрению примера, в котором нам удастся избежать описанной выше досадной неприятности, расширим познания относительно содержимого экземпляров класса. Новость очень простая: у экземпляров класса могут быть не только методы, но и переменные, которые мы будем называть полями экземпляра касса. А все вместе, – поля и методы экземпляра класса, – будем называть атрибутами экземпляра касса. То есть атрибуты – это поля и методы.

Фактически поле экземпляра класса – это некоторая переменная, которая "приписана" (или "прикреплена") к этому экземпляру класса. Проблема здесь вот в чем: переменные, как мы знаем, появляются тогда, когда им присваивается значение. Если мы говорим о переменной в контексте экземпляра класса, то естественным образом возникает вопрос: где, в каком месте, переменной (полю) можно присвоить значение? Логично предположить, что при выполнении метода экземпляра класса.

#Создаем класс

Class MyClass:

#Метод для присваивания значения

#полю экземпляра класса

42

def set(self, n) :

рrint ( " Внимание ! Присваив ается значение! " )

# Полю присваивается значение self.nume r=n

#Метод для считывания значения

#поля э кземпляра класса

def get(self) :

# Отображаем значение поля

рrint ( " Значение пoля : " , s e l f . number )

#Создаем экземпляр класса obj =MyClass( )

#Вызывается метод экземпляра класса и

#полю экземпляра класса присваивается значение obj.set(100)

#Вызывается метод экземпляра класса и

#отображается значение поля экземпляра класса obj.get( )

Поле экземпляра класса

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

Внимание! Присваивается значение! Значение поля: 100

Мы, как и в предыдущем примере, создали класс с названием MyClass. Но теперь в этом классе описаны два метода экземпляра класса: метод set() предназначен для присваивания значения полю экземпляра класса. Поле называется number, и о его существовании мы узнаем исключительно из программного кода метода set() : в том месте, где командой self.number=n полю number экземпляра класса, на который ссылается переменная self, присваивается значение переменной n. И переменная self (первый аргумент), и переменная n (второй аргумент) объявлены как аргументы метода set(). Переменная self представляет собой ссылку на экземпляр класса, из которого вызывается метод, а переменная n – это непосредственно тот аргумент, который передается методу при вызове. В последствии, когда командой obj = MyClass() будет создан экземпляр obj класса MyClass и затем командой obj.set(100) из этого экземпляра вызван метод set() с аргументом 100, данное значение будет присвоено полю number экземпляра obj.

Значение полю экземпляра класса можно присвоить через прямое обращение к этому полю. Рассмотрим небольшой пример в листинге:

# Создаем класс без методов class MyClass:

pass

43

#Создаем экземпляр класса obj = MyClass()

#Присваивается значение полю number

#экземпляра obj

obj.number = 100

#От ображается значение поля number

#экземпляра obj

рrint ("Значение пoля: ", obj.number)

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

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

Значение поля: 100

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

Экземпляр класса, как и во всех предыдущих случаях, создаем командой obj = MyClass(). Затем командой obj.number = 100 экземпляр obj получает поле number, и этому полю присваивается значение 1 0 0. После этого значение поля nuшber экземпляра obj считывается и отображается в окне вывода командой print ("Значение поля:" obj.number).

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

5.1. Конструкторы и декораторы

При создании класса можно описать специальный метод, который называется конструктором экземпляра класса (иногда также называют методом инициализации). Этот метод автоматически вызывается при создании экземпляра класса. Рецепт создания конструктора очень простой: необходимо создать метод с названием __init__() (два символа подчеркивания в начале и два символа подчеркивания в конце). Аргументов у конструктора может быть сколько угодно – но не меньше одного (ссылка на экземпляр, при создании которого вызывается конструктор)

44

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

# Создаем класс class MyClass:

# Конструктор def __init__(self):

# Присваивается значение полю self.number= 0

# Отображается сообщение

print ("Coздaн экземпляр класса!")

#Создается э кземпляр класса obj = MyClass ()

#Проверяем значение поля экземпляра класса print("Значение пoля :" , obj.number)

Конструктор экземпляра класса

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

Создан экземпляр класса! Значение поля: 0

Вклассе MyClass описан конструктор __init__() с одним аргументом self (в случае конструктора это ссылка на создаваемый экземпляр класса). В теле конструктора командой self.number=0 полю number экземпляра класса присваивается нулевое значение, а затем командой print (''Создан экземпляр класса !'') в окне вывода отображается сообщение.

Эти два действия запрограммированы в конструкторе, который, напомним, автоматически вызывается при создании экземпляра класса. Поэтому каждый раз, когда создается экземпляр, этому экземпляру автоматически будет добавляться поле number с нулевым значением, и после этого, опять же автоматически, будет появляться сообщение в окне вывода. Поэтому при создании экземпляра obj командой obj =MyClass() у этого экземпляра появляется поле number, и этому полю присваивается значение 0. Также окне вывода появится сообщение Создан экземпляр класса ! . С помощью команды print("Значение поля : " , obj.number) мы проверяем, справедливы ли все те утверждения относительно поля number экземпляра obj , которые были сделаны выше. Результат выполнения этой команды не дает поводов для сомнений. У конструктора может быть несколько аргументов (во всяком случае, больше одного).

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

45

# Создаем класс class MyClass:

# Метод для присваивания значения полю def set(self , n ) :

# Полю number присваивается значение self.num =n

# Метод для отображения значения поля def get (self) :

# Отображается значение поля number

рrint ( " Значение пoля : " , s e l f . num )

#Конструктор с двумя аргументами

#у второго аргумента есть значение

#по умолчанию

def __init__(self, n=0) :

#Вызывается метод set() для присваивания

#значения полю number

self.set( n)

# Отображается сообщение print ("Coздaн экземпляр класса.")

#Вызывается метод get() для отображения

#значения поля number

self.get(n)

#Создается экземпляр класса a=MyClass()

#Создается еще один экземпляр класса b=MyClass(100)

Аргументы конструктора

Здесь мы создаем класс МуСlass, в теле которого описаны методы экземпляра класса set() и get(). Первый метод предназначен для присваивания значения полю number экземпляра класса, а второй метод нужен для отображения значения этого поля. У конструктора теперь два аргумента. Первый, как всегда, является ссылкой на экземпляр класса, а второй аргумент по нашей задумке, определяет значение поля number экземпляра класса. Причем этот второй аргумент имеет нулевое значение по умолчанию: то есть если при создании экземпляра аргумент указать, то это будет значение поля number, а если значение не указать, то у поля number будет нулевое значение.

Результат выполнения кода такой:

Создан экземпляр класса. Значение поля: 0 Создан экземпляр класса.

Значение поля: 100

46

Возникает вопрос: а как передать аргумент конструктору? Очень просто: в команде создания экземпляра класса после имени класса в круглых скобках (которые до этого у нас всегда были пустыми) передаются аргументы конструктору. Другими словами, шаблон команды создания экземпляра класса с передачей аргументов конструктору такой: переменная=класс(аргументы). Что касается данного конкретного случая, то поскольку второй аргумент имеет значение по умолчанию, мы можем как передавать аргумент конструктору, так и не передавать.

Примером первой ситуации является команда b=MyClass(100) (экземпляр b создается со значением 100 для поля number), а второй – команда a=MyClass() (экземпляр а создается со значением 0 для поля number).

Метод с названием __del__() автоматически вызывается при удалении экземпляра класса из памяти. Этот метод принято называть деструктором. У деструктора один и только один аргумент (ссылка на экземпляр класса self). Небольшой пример с использованием деструктора приведен в листинге:

# Класс с конструктором и деструктором class MyClass:

# Конструктор def __init__(self):

print ( "Bceм привет !" )

# Деструктор def __del__(self) :

print ( "Bceм пока !" )

print("Проверяем работу деструктора.")

#Создаем экземпляр класса obj =MyClass()

print("Экземпляр класса создан. Удаляем его")

#Удаляем экземпляр класса

del(obj)

print("Выполнение программы завершено.")

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

Проверяем работу деструктора. Bceм привет !

Экземпляр класса создан. Удаляем его.

Bceм пока !

Выполнение программы завершено.

Хотя вся эта схема с использованием деструктора выглядит довольно элегантно, у нее есть существенный недостаток, причем касается он не только рассмотренного примера, но деструкторов вообще. Дело в том, что очистка памяти в Python выполняется автоматически. Удаляются объекты, на которые в программе нет ссылок. Но сказать, когда конкретно они будут удалены практически невозможно. Поэтому если некоторый объект должен быть удален, то он рано или поздно будет удален. Но когда именно – вопрос сложный.

47