Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программирование на языке Python. Учебное пособие.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
Как видно, здесь используется один и тот же интерфейс, а именно функция
print, для трех разных типов данных: строки, целого и вещественного числа. Иначе говоря, чтобы вывести эти разнородные типы данных, нет необходимо­сти определять и вызывать три отдельные функции персонально для каждого типа данных и, таким образом, перегружать программу лишним кодом. Вместо этого в программе используется функция print для представления единого интерфейса для вывода всех этих данных.
В следующем примере использована встроенная функция type, возвра-
щающая тип данных объекта.
s = 'Hello World!' print(type(s)) i = 10 print(type(i)) f = 15.2
print(type(f)) >> <class 'str'> >> <class 'int'> >> <class 'float'>
Посмотрим на другой пример. Допустим, хотим написать программу, ко-
торая может рисовать различные фигуры: треугольники, квадраты и круги. Каждая из этих фигур рисуется по-своему, поэтому методы их рисования будут иметь разные реализации. Для этого необходимо определить три различных класса Triangle, Square и Circle и метод draw для каждого из них. Тогда
Triangle.draw() нарисует треугольник, Square.draw() нарисует квад­рат, а Circle.draw() нарисует круг. Каждый из этих объектов имеет соб­ственный интерфейс рисования draw, который умеет рисовать самого себя. Ко­гда есть объект геометрической формы, можно вызвать функцию draw, чтобы нарисовать фигуру. Один и тот же интерфейс представлен для всех различных
типов данных.
Если бы Python не поддерживал полиморфизм, понадобилась бы функция
(метод) для создания треугольника, и еще одна функция, называемая
draw_triangle, для его рисования; функция для создания круга и функция draw_circle для его рисования и т.п. Поскольку Python обладает полимор-
91
физмом, любую фигуру можно просто нарисовать с помощью своего метода рисования draw. Это делает объекты формы намного проще в использовании и объяснении, например: есть три функции, представляющие три разные фигу­ры, и еще три функции, которые рисуют каждую из них; можно сказать тому, кто использует код: имеются три фигуры – если необходимо нарисовать одну, вызывается ее метод draw.
Заметим, что если бы был список упомянутых объектов и требовалось бы нарисовать каждый из них, то сначала пришлось бы узнавать тип каждого объ­екта, затем вызывать подходящий для этого типа метод, увеличивая программу, делая ее более сложной для чтения и написания, а также менее надежной. И это усложнило бы процесс совершенствования программы, поскольку всякий раз при добавлении новой фигуры пришлось бы отслеживать каждое место в коде, где рисуются фигуры, проверять их тип данных (чтобы выяснить, какой метод использовать) и вызывать новую функцию рисования. Ниже приведен пример рисования фигур с полиморфизмом и без него.
# Рисуем фигуры без использования полиморфизма shapes = [tr1, sql, cr1] for a_shape in shapes: if type(a_shape) == "Triangle": a_shape.draw_triangle() if type(a_shape) == "Square": a_shape.draw_square() if type(a_shape) == "___": a_shape.draw_circle() # Рисуем фигуры c использованием полиморфизма shapes = [tr1,sql,cr1] for a_shape in shapes: a_shape.draw()
Заметим, что если бы возникла необходимость добавить новую фигуру в список shapes без использования понятия полиморфизма, то нужно было бы изменить код в цикле for для идентификации типа a_shape, а затем вызвать соответствующий ему метод draw. При использовании же полиморфизма мож­но добавлять сколько угодно классов фигур в список shapes и фигура будет рисовать себя без какого-либо дополнительного кода.
92

Абстракция

Абстракция – это концепция объектно-ориентированного программиро-
вания, которая «показывает» только существенные (релевантные) атрибуты и «скрывает» ненужную информацию (иррелевантную). Иначе говоря, абстрак­ция обычно определяется как извлечение релевантной информации из большо­го набора данных, где использование абстракции позволяет инженерам и дру­гим специалистам упростить базовый код. Таким образом, суть абстракции со­стоит в том, чтобы сохранить информацию, уместную в данном контексте (ре­левантную информацию), и забыть информацию, не относящуюся к этому кон­тексту (иррелевантную). Очевидно, что в процессе абстрагирования происходит процесс изъятия или удаления (элиминации) характеристик чего-либо, чтобы свести его к набору существенных (релевантных) характеристик.
При использовании абстракции в объектно-ориентированном программи­ровании создается класс и определяются его методы. Допустим, создается класс для представления человека. Когда определяется класс Person и связанные с ним методы, то создается абстракция. Определение человека может включать цвет глаз, волос, рост и этническую принадлежность, а также способность чи­тать, писать и рисовать. К примеру, человек ростом 160 см с голубыми глазами, светлыми волосами, не умеющий читать, писать и рисовать, и человек ростом 195 см с карими глазами, каштановыми волосами, умеющий читать, писать и рисовать, попадают в категорию созданной абстракции человека.
Когда разрабатываются объектно-ориентированные программы, то созда­ются абстракции различных концепций, работающие вместе, формируя про­грамму. Например, можно создать абстракцию человека и абстракцию прави­тельства и смоделировать, сколько людей живет при каждом правительстве в мире. Абстракция позволяет моделировать объекты с четкими границами и заставлять их взаимодействовать друг с другом.

Инкапсуляция

В объектно-ориентированном программировании инкапсуляция скрывает внутренние данные нашего кода. Когда код инкапсулирован, это означает, что при его вызове вызывающая сторона не может получить доступ к внутренним данным кода.
93
Рассмотрим метод get_data:
class Data: def get_data(self, index, n):
data = [1, 2, 3, 4, 5] data.append(n)
Метод имеет переменную с именем data. Когда вызывается get_data, нет возможности получить доступ к этой переменной из-за инкапсуляции. Если бы не было инкапсуляции, можно было бы получить доступ к переменной data – и добавить к ней n:
# предупреждение: этот код не работает Data.get_data.data.append(6)
Если бы это было разрешено, любой мог бы получить доступ к переменной data в методе get_data. Вместо того чтобы полагаться на реализацию мето­да get_data, можно было бы добавлять n к данным. Это не проблема до тех пор, пока не изменится реализация get_data. При принятии решения о типе переменной data, является ли она кортежем или списком, и внесении измене­ний в код, это нарушает его, т.к. у кортежа нет метода append. Но из-за ин­капсуляции этот сценарий невозможен (вот почему код не работает), и можно быть уверенным, что изменения во внутренней реализации кода не нарушат код клиента (клиент – это код вне класса, который использует объект).
Итак, инкапсуляция позволяет переменные и методы группировать в еди­ное целое – объект.
class Rectangle(): def __init__(self, w, l):
self.width = w self.len = l
def area(self): return self.width * self.len
Как видим, переменные len и width хранят состояние объекта, которое сгруппировано в том же блоке (объекте), что и метод area. Метод использует состояние объекта, чтобы вернуть площадь прямоугольника.
94
class Data: def __init__(self): self.nums = [1, 2, 3, 4, 5] def change_data(self, index, n): self.nums[index] = n
Как уже было сказано выше, инкапсуляция позволяет скрыть конкретную
информацию и контролировать доступ к внутреннему состоянию объекта.
class Data: def __init__(self): self.nums = [1, 2, 3, 4, 5] def change_data(self, index, n): self.nums[index] = n
В этом фрагменте кода класс Data имеет переменную nums экземпляра
класса, содержащую список целых чисел. Существуют два способа изменить элементы в nums – при помощи метода change_data или, напрямую получая доступ к переменной nums экземпляра с помощью объекта Data.
class Data: def __init__(self):
self.nums = [1, 2, 3, 4, 5] def change_data(self, index, n): self.nums[index] = n data_one = Data() data_one.nums[0] = 100 print(data_one.nums) data_two = Data() data_two.change_data(0, 100) print(data_two.nums)
>> [100, 2, 3, 4, 5] >> [100, 2, 3, 4, 5]
Оба способа изменения элементов nums вполне рабочие, но если попы-
таться переменную nums сделать кортежем вместо списка, то код клиента
nums[0] = 100 не сработает, так как кортежи неизменяемы.
95
Во многих языках эту проблему решают, определяя так называемые за­крытые переменные и закрытые методы – переменные и методы, к которым
могут обращаться объекты в коде, реализующем различные методы, но клиент не может. Кроме закрытых переменных и методов существуют открытые пе- ременные, к которым клиент может получить доступ.
В Python все переменные – открытые. Вместо механизма закрытых пере­менных и методов используются конвенции (соглашения) имен. Если в про­грамме есть переменная или метод, к которым вызывающий их не должен по­лучить доступ, то перед их именами необходимо добавить нижнее подчеркива­ние, что сигнализирует о том, что их нельзя использовать.
class PublicPrivateExample: def __init__(self):
self.public = "безопасно" self._unsafe = "опасно" def public_method(self): # клиенты могут это использовать
pass def _unsafe_method(self):
# клиенты не должны это использовать
pass
self.public = "безопасно" self._unsafe = "опасно"
В этом фрагменте кода self.public использовать безопасно, а пере­менную self._unsafe использовать нельзя. То же самое касается возможно­стей использования методов public_method и _unsafe_method.

Композиция

Несмотря на то, что композиция не является одним из четырех столпов объектно-ориентированного программирования, это важная концепция, связан­ная со всеми остальными. Композиция используется для представления отно-
96
шений «имеет». Это происходит, когда один объект хранит другой объект в ка­честве переменной. Например, предположим, что нужно представить отноше­ния между собакой и ее владельцем – это отношение «имеет» – у собаки есть хозяин. Для этого необходимо в первую очередь определить классы собак и людей:
class Dog(): def __init__ (self, name, breed, owner):
self.name = name self.breed = breed self.owner = owner
class Person(): def __init__ (self, name):
self.name = name
Когда создается объект Dog, то передается объект Person в качестве па-
раметра owner:
mick = Person("Mick Jagger") dog = Dog("Stanley", "French Bulldog", mick) print(dog.owner)
>> Mick Jagger
Теперь у собаки "Stanley" (объект stan) есть владелец – объект Per-
son по имени "Mick Jagger" (экземпляр класса owner), на который можно
легко ссылаться.

11. Дополнительные понятия ООП

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

Как работают переменные

Переменная «указывает» на объект:
number = 100
В данном примере переменная number указывает на целочисленный объ-
ект со значением 100.
Если присваивается number другое значение:
number = 101,
то оно указывает на новый целочисленный объект со значением 101, а старый со значением 100 отбрасывается, поскольку он больше не используется. Две переменные могут указывать на один и тот же объект:
x = 100 y = x
Переменная x указывает на целочисленный объект со значением 100. Ко-
гда присваивается переменной y значение x, то переменная y теперь указывает на тот же целочисленный объект, на который указывает x: обе переменные ука­зывают на целочисленный объект со значением 100.
Посмотрим, что напечатает следующая программа.
x = 10 y = x x += 1 print(x) print(y)
Ответ 11 и 10. Переменная x указывает на целочисленный объект со зна­чением 10, и когда создаете переменную y, она указывает на тот же целочис­ленный объект. Когда увеличиваете x, то она указывает на новый целочислен­ный объект со значением 11, но целочисленный объект со значением 10 не от­брасывается, потому что он используется: y по-прежнему указывает на цело­численный объект со значением 10. Итак, при печати x печатается 11, потому что присвоили x новому целочисленному объекту – 11, но при печати y печата­ется 10, потому что изменение значения x, которое указывает на целочислен-
98
ный объект 11, не влияет на значение y. Вот еще один пример, иллюстрирую­щий это положение. Посмотрим, что будет напечатано на выходе этого кода.
x = [1, 2, 3] y = x y[2] = 100 print(x) print(y)
Вывод будет [1, 2, 100] дважды. Причина в том, что и x, и y указывают на один и тот же объект списка. В строке 3 при внесении изменений в этот един­ственный объект списка и при печати обеих переменных печатается объект списка, на который они обе указывают, с изменениями, сделанными в строке 3.

Ключевое слово is

Ключевое слово is возвращает значение True, если два объекта являются одним и тем же объектом (они хранятся в одном и том же месте в памяти), и значение False – в противном случае.
class Person: def __init__(self):
self.name = 'Bob' bob = Person() the_same_bob = bob print(bob is the_same_bob) another_bob = Person() print(bob is another_bob)
>> True >> False
При использовании ключевого слова is в выражении с объектами bob и the_same_bob результатом будет True, поскольку обе переменные указы­вают на один и тот же объект Person. При создании нового объекта Person и сравнении его с исходным объектом bob результатом будет False, посколь­ку переменные указывают на разные объекты Person.
99

Константа None

Встроенная константа None используется для обозначения отсутствия
значения:
x= None x
>> None
Можно проверить, присвоено ли переменной значение None, используя
условные операторы, а именно ключевое слово is.
x = 10 if x is None: print("x is None") else: print("x is not None:(") x = None if x is None: print("x is None") else: print("x is not None:(")
>> x is not None:( >> x is None
Так как Python является объектно-ориентированным языком, поэтому None –
тоже объект и имеет свой тип.
type(None)
>> <class 'NoneType'>

Классы – это объекты

В языке Python классы – это объекты. Эта идея восходит к первому универ­сальному объектно-ориентированному языку программирования SmallTalk. Это означает, что при запуске программы, в которой определяется класс, Python пре­вращает его в объект, который затем можно использовать в своей программе:
class Pterodactyl: pass
100
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]