Выбери формат для чтения
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Объектно-ориентированное программирование
на языке Python
Часть 1
Технология объектно-ориентированного программирования (ООП) возникла на
уровне идеи в 70-80 годы XX века, однако окончательно оформилась как реально работающая технология после 2000 года.
Зачем нужна ООП-технология, в чём её необходимость, почему в настоящее
время она стала основной технологией программирования ?
Прежде всего необходимость в ООП обосновывается всё возрастающей сложностью программного обеспечения, над разработкой которого трудятся иногда сотни и
тысячи программистов (пример – ОС Windows содержит более 1 млн. строк кода).
Благодаря ООП стало возможным разрабатывать программные комплексы практически неограниченной сложности.
Предыдущие методы, такие как структурное и модульное программирование,
относящиеся к процедурному программированию, такой возможности не давали.
Основная цель ООП – создание программ простой структуры за счёт разбиения (декомпозиции) программы на максимально обособленные небольшие части
(классы)
Достоинства ООП
1
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
1)
использование при программировании понятий, близких к предметной области; тем самым мы получаем фактически расширяемый язык
программирования, в который можем добавлять свои типы данных, свои
понятия предметной области (например, Contract, Customer, Users, Person, Order и т.д.)
2)
возможность успешно управлять большими объёмами исходного
кода благодаря инкапсуляции, то есть скрытию деталей реализации классов объектов и упрощению структуры программы;
3)
возможность многократного использования кода за счёт наследования;
4)
сравнительно простая возможность модификации программ;
5)
возможность создания и использования библиотек классов объектов;
Недостатки ООП
1)
некоторое снижение быстродействия программы;
2)
идеи ООП сложнее для понимания по сравнению с процедурными;
3)
неквалифицированное применение ООП может привести к значительному ухудшению характеристик разрабатываемой программы.
Основными понятиями ООП являются понятия :
Класс
Объект (экземпляр класса).
Класс – это структурированный тип данных, определяемый пользователем.
Класс = Данные + Код
2
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Класс ещё называют неким шаблоном (прототипом, проектом) для создания новых объектов.
Основные (но не все) составляющие класса:
1) Поля (переменные, члены) – это данные класса;
2) Методы (функции) – это код класса;
3) Свойства – это данные класса совместно с правилами их обработки.
Основными принципами ООП являются следующие:
абстракция;
инкапсуляция;
наследование;
полиморфизм.
Абстракция выполняет основную задачу ООП, позволяя программисту формировать объекты определённого типа с различными характеристиками и поведением, благодаря применению классов.
Инкапсуляция скрывает детали реализации конкретных объектов и защищает их
свойства от постороннего вмешательства.
Наследование позволяет расширять уже существующие классы за счёт передачи
их данных и методов к новым классам.
Полиморфизм используется для создания единого интерфейса, который имеет
множество различных реализаций, зависящих от класса применяемого объекта.
3
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Абстра́кция (лат. abstractio — отвлечение) — теоретическое обобщение как результат абстрагирования.
Абстрагирование — отвлечение в процессе познания от несущественных сторон, свойств, связей объекта (предмета или явления) с целью выделения их существенных, закономерных признаков.
Для того, чтобы описать предметную область в терминах ООП, нужно внимательно изучить предметную область, выявить в ней основные сущности или понятия,
их свойства, взаимосвязи между ними.
Преимущества ООП проявляются при создании программ большого объёма.
Для разработки небольших и средних программных проектов часто более эффективной остаётся традиционная технология процедурного программирования.
Для создания класса в Python используется ключевое слово class.
Пример создания простейшего класса и объекта на Python:
>>>class Test:
>>>
pass
# Создали новый класс Test
# Он пустой (возможно, пока)
>>>var_test = Test()
#
Создали объект класса Test
>>>type(var_test)
Результат:
Нужно понимать, что когда мы создаём объект класса, то при этом выполняется
некоторое физическое действие – в динамической памяти нашей программы под со4
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
здаваемый объект выделяется некоторый набор ячеек оперативной памяти (количество
которых определяется структурой данных класса).
Пример 11-1 (файл 11-1.py)
# Пример создания простейшего класса на Python
class Person:
name = 'Фамилия'
age = 0
p1 = Person()
print(p1.name,p1.age)
Результат:
Фамилия 0
Здесь name, age – это поля (данные) класса. Доступ к данным объекта мы получаем, используя точечную нотацию:
p1.name
# Поле name объекта p1
p1.age
# Поле age объекта p1
Вряд ли мы хотим создавать объекты с одними и теми же данными, разумеется,
данные у различных объектов класса должны быть различными. В ООП при описании
классов используется специальный метод (функция), который задаёт значения полей
(данных) объекта во время его создания. Он называется конструктор класса, а выполняемое им действие называется инициализация объекта.
Конструктор автоматически срабатывает каждый раз, когда программа создаёт новый объект класса.
5
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
В Python имя метода-конструктора класса фиксированное, он называется
__init__:
def __init__(self, параметры):
Здесь обязательный параметр self – это ссылка на создаваемый объект. Конкретно, это обозначает адрес создаваемого объекта в оперативной памяти.
В языках группы C (C, C++, C#) этот параметр называется this, а в PascalABC.NET – тоже self (но он там необязателен).
Пример 11-2 (файл 11-2.py)
# Пример создания простейшего класса на Python
class Person:
def __init__(self, nm, ag):
self.name = nm
self.age = ag
# Создаём объект p1
p1 = Person('Иванов Иван',20)
print(p1.name,p1.age)
# Создаём объект p2
p2 = Person('Петрова Мария',19)
print(p2.name,p2.age)
В нашем случае (как и чаще всего на практике) конструктор класса Person выполняет простейшее действие – он присваивает полям класса передаваемые в качестве
параметров значения (это действие называется инициализацией объекта).
p1 и p2 – это объекты класса Person. Также p1 и p2 называют экземплярами
класса Person.
6
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
В класс можно включать любой код, необходимый для обработки данных объекта. Например, в нашем случае было бы полезно включить в класс функцию
PrintPerson (метод класса Person), которая просто выводит в удобном виде данные
объекта.
Также добавим в класс (немного искусственный, для примера) метод NextYear,
который определяет возраст персоны в следующем году.
Пример 11-3 (файл 11-3.py)
# Пример создания простейшего класса на Python
class Person:
def __init__(self, nm, ag):
self.name = nm
self.age = ag
def PrintPerson(self):
print('Имя - ',self.name,' ,возраст - ',self.age)
def NextYear(self):
self.age +=1
# Создаём объект p1
p1 = Person('Иванов Иван',20)
p1.PrintPerson()
# Создаём объект p2
p2 = Person('Петрова Мария',19)
p2.PrintPerson()
# Какой возраст будет у объекта p1 в следующем году ?
p1.NextYear()
p1.PrintPerson()
Результат работы:
Имя Имя Имя -
Иванов Иван ,возраст - 20
Петрова Мария ,возраст - 19
Иванов Иван ,возраст - 21
7
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Мы видим, что структурно код нашего примера быстро увеличивается. Вот здесь
нам и пригодятся модули (предыдущая тема). Мы класс Person перебросим в модуль
PersonClass.py.
Пример 11-4 (файл 11-4.py)
# Код модуля PersonClass.py
class Person:
def __init__(self, nm, ag):
self.name = nm
self.age = ag
def PrintPerson(self):
print('Имя - ',self.name,' ,возраст - ',self.age)
def NextYear(self):
self.age +=1
Тогда основная программа (главный скрипт) будет выглядеть следующим образом:
# Код главного скрипта 11-4.py
from PersonClass import * # Поключение модуля с классом Person
# Создаём объект p1
p1 = Person('Иванов Иван',20)
p1.PrintPerson()
# Создаём объект p2
p2 = Person('Петрова Мария',19)
p2.PrintPerson()
# Какой возраст будет у объекта p1 в следующем году ?
p1.NextYear()
p1.PrintPerson()
Результат:
Имя -
Иванов Иван
,возраст 8
20
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Имя Имя -
Петрова Мария ,возраст - 19
Иванов Иван ,возраст - 21
Теперь структурно наша программа построена правильно – в отдельном модуле
PersonClass.py находится класс Person, в отдельном – использующий класс Person
скрипт.
Кроме конструктора в классе также может использоваться обратный ему специальный метод – деструктор класса. Если конструктор класса создаёт объект - размещает его в оперативной памяти программы, то деструктор выполняет обратное действие – удаляет объект из оперативной памяти (уничтожает объект).
В Python имя деструктора класса фиксированное, он называется:
__del__
Пример 11-5. Деструктор класса Person
class Person:
__del__(self):
print('Объект уничтожен')
p3 = Person('Некто',0)
del(p3)
Результат:
Объект уничтожен
Вообще-то объект автоматически уничтожается при окончании работы программы. Более того, в современных программных системах есть специальная функция,
которая называется «сборка мусора», это когда объекты автоматически уничтожаются,
если становятся ненужными. Однако бывают ситуации, когда «ручное» удаление объектов может быть полезным. Например, когда наша программа создаёт очень много
объектов и возникает угроза нехватки памяти.
9
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Наследование
Можно создавать новые классы на базе существующих классов, при этом класснаследник (называемый также производным классом, дочерним классом или классомпотомком) получает (наследует) все данные и методы базового класса-предка. Обязательно то, что класс-наследник должен представлять собой более частный случай
класса-родителя.
Рассмотрим данную концепцию на примере. Создадим на базе класса Person дочерний класс Student. Класс Student наследует данные класса Person (name, age), а
также его методы PrintPerson() и NextYear(). Это обозначает, что объект класса Student может использовать эти данные и методы базового класса Person.
В классе Student мы добавим следующие дополнительные данные (кроме name
и age, которые наследуются из Person):
ng – номер группы;
y_ent – год поступления в вуз;
y_fin – год окончания вуза.
Кроме того, добавим в него методы:
PrintStudent() – выводит все данные студента;
FinishYear() – вычисляет год окончания вуза по году поступления (для бакалавриата + 4 года).
Теперь наш проект будет состоять из 3-х файлов:
Модуль PersonClass.py – базовый класс Person;
Модуль StudentClass.py – дочерний класс Student (по отношению к Person);
Главный скрипт – 11-6.py.
Пример 11-6. Добавляем класс Student.
Модуль StudentClass.py
# Класс Student
10
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
from PersonClass import *
class Student(Person):
def __init__(self, name, age, ng, y_ent, y_fin):
self.name = name
self.age = age
self.ng = ng
self.y_ent = y_ent
self.y_fin = y_fin
def PrintStudent(self):
print('Имя студента - ',self.name)
print('Возраст студента - ',self.age)
print('Группа студента - ',self.ng)
print('Год поступления - ',self.y_ent)
print('Год окончания - ',self.y_fin)
def FinishYear(self):
self.y_fin = self.y_ent + 4
def __del__(self):
print('Объект класса Student уничтожен')
Чтобы класс Student правильно работал, он должен импортировать класс Person
из модуля PersonClass.py.
Главный скрипт 11-6.py будет выглядеть так:
# Пример создания простейшего класса на Python
from PersonClass import *
from StudentClass import *
# Создаём объект класса студент
s1 = Student('Смирнов Артём',19,'ЭБб-18-1',2018,0)
s1.PrintStudent()
# Вычисляем год окончания учёбы через год поступления
s1.FinishYear()
s1.PrintStudent()
Результат:
11
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Имя студента - Смирнов Артём
Возраст студента - 19
Группа студента - ЭБб-18-1
Год поступления - 2018
Год окончания - 0
Имя студента - Смирнов Артём
Возраст студента - 19
Группа студента - ЭБб-18-1
Год поступления - 2018
Год окончания - 2022
Для правильной работы скрипта нужно импортировать оба модуля с классами:
PersonClass.py и StudentClass.py.
Самостоятельно:
Разработать класс Diplomnik, дочерний по отношению к классу Student.
Должны быть добавлены следующие данные дипломника:
ruk – ФИО преподавателя – руководителя дипломного проекта;
tema – тема дипломного проекта;
Также в класс должен быть добавлен метод для вывода данных дипломника
PrintDiplomnik()
12
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Часть 2
Объектно-ориентированное программирование
на языке Python (продолжение)
Абстрактные методы и полиморфизм
Рассмотрим пример:
Пример 12-1.
# Пример 12-1. Абстрактные методы и полиморфизм
class Animal:
def __init__(self, name):
self.name = name
def makeNoise(self):
# Абстрактный метод, который ничего
не делает
pass
class Dog(Animal):
def makeNoise(self):
# Переопределяем его
print(self.name + " произносит: Гав-гав !")
class Cat(Animal):
def makeNoise(self):
# Переопределяем его снова
print(self.name + " произносит: Мая-мяу !")
# Главная программа
# Создаём собаку
dog = Dog("Мухтар")
# Создаём кошку
cat = Cat("Алиса")
# Один и тот же метод даёт разные результаты:
dog.makeNoise()
cat.makeNoise()
Результат:
13
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Мухтар произносит: Гав-гав !
Алиса произносит: Мая-мяу !
В этом примере в базовом классе Animal определяется абстрактный метод
makeNoise (шуметь). Далее, в классах Dog и Cat данный метод переопределяется. В
результате, после создания объектов этих классов, один и тот же метод, применённый
к объектам разных классов (Dog и Cat), даёт разный результат. Другими словами, он
имеет много форм (полиморфизм – много форм).
В этом заключается суть полиморфизма, позволяющего изменять ход работы
определённого метода исходя из нужд конкретного класса. При этом название у него
остаётся общим для всех наследников, что помогает избежать путаницы с именами.
Полиморфизм в ООП проявляется в том,
что мы используем одно имя метода для
задания общих для класса действий.
В нашем случае, метод называется makeNoise, а его действие – «шуметь».
Методы класса – это функции Python, имеющие одну особенность:
Метод класса в Python – это функция,
первым параметром которой всегда является параметр self.
При вызове метода класса параметр self писать уже не надо, т.е. пишем не
dog.makeNoise(self)
,а
dog.makeNoise()
14
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Проект «Компьютерная игра» (ComputerGame)
Рассмотрим проект «Компьютерная Игра» класса «Шутер» (shooter) – «Стрелялка». Конечно, это очень упрощённый вариант реальной игры, затрагивающий лишь
некоторые аспекты реальной игры для демонстрации идей ООП.
Суть игры: В игре участвуют боты (искусственные игроки). У каждого бота
имеется:
Имя (name);
Здоровье (health);
Патроны (ammo).
Боты описываются в классе Monster. Каждый бот играет за себя. Он стреляет в
других ботов, они стреляют в него.
Игра вариант ComuperGame1.
class Monster:
def __init__(self, name, health, ammo):
self.name = name
self.health = health
self.ammo = ammo
def Passport(self):
print("Monster ",self.name," health=",self.health," ammo=",self.ammo)
def __del__(self):
print("Monster ",self.name, " was killed")
# Computer Game Project 1
from MonsterClass1 import *
Masha=Monster("Masha",100, 100)
Masha.Passport()
Masha.health +=50
15
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Masha.ammo += 100
Masha.Passport()
Результат:
Monster
Monster
Masha
Masha
health= 100
health= 150
ammo= 100
ammo= 200
Описание. Здесь мы создаём бота с именем «Masha», здоровьем 100 и патронами
100. Выводим данные бота с помощью метода Passport(). Затем меняем значения полей
health и ammo и снова применяем метод Passport().
Игра вариант ComuperGame2.
Добавим в класс Monster метод Attack. Он моделирует ситуацию, когда игрок-бот стреляет в цель (в целевого бота) и все патроны в него попадают. При этом
количество патронов стреляющего уменьшается, также уменьшается здоровье целевого бота (на это же значение).
def Attack(self, aim, power):
# power - количество выпущенных патронов
# aim – целевой бот
# Каждый патрон попадает в цель и уменьшает Здоровье цели на 1
self.ammo -= power
# Патроны стреляющего уменьшаются на
Power
aim.health -= power # Здоровье цели уменьшается на Power
Основная программа будет выглядеть так:
# Computer Game Project 2
from MonsterClass2 import *
Masha = Monster("Masha",100, 100)
Zero = Monster("Zero",200,200)
Masha.Passport()
Zero.Passport()
Masha.Attack(Zero,50) # Masha выпускает в Zero 50 патронов
Masha.Passport()
16
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Zero.Passport()
Результат:
Monster
Monster
Monster
Monster
Masha health= 100 ammo= 100
Zero health= 200 ammo= 200
Masha health= 100 ammo= 50
Zero health= 150 ammo= 200
Здесь мы видим, что уменьшилось как количество патронов игрока Masha, так и
количество здоровья игрока Zero.
17
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Игра вариант ComuperGame3.
Здесь мы:
1) сгенерируем 5 игроков и;
2) устроим между ними игру-перестрелку.
# Computer Game Project 3
# Генерируем 5 ботов и устраиваем перестрелку между ними
import random
from MonsterClass2 import *
stado = []
for i in range(5):
new_name = "Crazy"+str(i+1) # Имя бота
rnd_health = random.randint(0,100) # Здоровье бота
rnd_ammo = random.randint(0,100)
# Патроны бота
newMonster = Monster(new_name,rnd_health,rnd_ammo)
stado.append(newMonster)
for mon in stado:
mon.Passport()
Результат:
Monster
Monster
Monster
Monster
Monster
Crazy1
Crazy2
Crazy3
Crazy4
Crazy5
health=
health=
health=
health=
health=
20
38
38
72
27
ammo=
ammo=
ammo=
ammo=
ammo=
47
91
45
72
8
Теперь смоделируем игру-перестрелку между ботами. Для этого будем случайным образом выбирать пару ботов, в которой первый бот атакует второго. Число атак
в перестрелке будет случайным от 1 до 10. После Перестрелки выведем Паспорта ботов.
Также в метод Attack класса Monster добавлена строка:
18
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
print(self.name," атакует ",aim.name," с силой ",power)
Игра вариант ComuperGame4.
# Computer Game Project 4
# Генерируем ботов и устраиваем перестрелку между ними
import random
from MonsterClass4 import *
stado = []
for i in range(5):
new_name = "Crazy"+str(i+1)
rnd_health = random.randint(0,100)
rnd_ammo = random.randint(0,100)
newMonster = Monster(new_name,rnd_health,rnd_ammo)
stado.append(newMonster)
for mon in stado:
mon.Passport()
# Перестрелка - выбираем случайным образом пары ботов
print("*"*20," Перестрелка ","*"*20)
NumBots = len(stado)
for i in range(random.randint(1,10)):
firstBot = random.randint(0,NumBots-1)
secondBot = random.randint(0,NumBots-1)
if firstBot == secondBot:
continue # Бот не должен атаковать сам себя
power = random.randint(0,100)
stado[firstBot].Attack(stado[secondBot],power)
# Итоги перестрелки
print("*"*20,"После перестрелки ","*"*20)
for mon in stado:
mon.Passport()
19
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Результат:
Monster Crazy1 health= 5 ammo= 19
Monster Crazy2 health= 35 ammo= 28
Monster Crazy3 health= 100 ammo= 87
Monster Crazy4 health= 97 ammo= 64
Monster Crazy5 health= 65 ammo= 93
******************** Перестрелка ********************
Crazy1 атакует Crazy2 с силой 63
Crazy5 атакует Crazy1 с силой 88
Crazy3 атакует Crazy2 с силой 45
Crazy5 атакует Crazy3 с силой 63
Crazy1 атакует Crazy5 с силой 27
******************** После перестрелки ********************
Monster Crazy1 health= -83 ammo= -71
Monster Crazy2 health= -73 ammo= 28
Monster Crazy3 health= 37 ammo= 42
Monster Crazy4 health= 97 ammo= 64
Monster Crazy5 health= 38 ammo= -58
Мы видим, что в результате перестрелки-игры у некоторых ботов получилось
отрицательное здоровье и вооружение. Это, разумеется, невозможно в реальности,
нужно с этим что-то делать.
Если немного углубиться в суть игры, то в процессе игры бот будет убит, если
его health получит значение <=0. Значение поля ammo по смыслу игры не может быть
<0 (отрицательное число патронов бессмысленно). Поэтому изменение значения поля
ammo правильно будет контролировать (иначе может получиться, что у игрока -10 патронов).
Возможные варианты развития игры: добавить
1) поле – броня (при наличии брони уменьшается броня, а не здоровье);
20
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
2) поле – коэффициент попадания (он определяет меткость стреляющего, а
именно процент попадающих в цель патронов;
3) метод SaveAllGame – сохранение текущих данных всех игроков.
Ограничение доступа к данным класса.
Свойства классов. Инкапсуляция
По умолчанию все данные классов открыты для доступа извне, благодаря чему
их можно в любой момент изменить по своему усмотрению при помощи оператора
точки. Такие открытые данные в ООП обычно обозначаются свойством public (публичные). Свободный доступ к данным класса – далеко не всегда хорошо, так как существует опасность потери информации либо введения неправильных данных, приводящих к сбоям в работе программы. Особенно это актуально, когда над проектом работает несколько программистов и не всегда очевидно, для чего нужно то или иное
поле данных.
Для ограничения доступа к полям данных в других ЯП их объявляют с модификатором private (частный, приватный). В Python, для того чтобы поле класса сделать
закрытым извне (из других классов и модулей), его следует писать с префиксом __(два
подчёркивания).
Пример 12-2.
# 12-2 Открытые поля класса
class Animal:
def __init__(self, name, type, meal):
self.name = name
self.type = type
self.meal = meal
def OutputData(self):
print(self.type,self.name,' любит ',self.meal)
# Главная программа
21
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
camel = Animal('Кеша','верблюд','колючки')
camel.OutputData()
print(camel.name) # Данные открыты и доступны
print(camel.type) # Данные открыты и доступны
print(camel.meal) # Данные открыты и доступны
Результат:
верблюд Кеша любит колючки
Кеша
верблюд
колючки
Здесь у нас данные полностью открыты и доступны отовсюду вне класса.
Теперь закроем поля type и meal для доступа извне.
Пример 12-3.
# 12-3 Закрытые поля класса
class Animal:
__type = "" # Закрытое поле класса
__meal = "" # Закрытое поле класса
def __init__(self, name, type, meal):
self.name = name
self.__type = type
self.__meal = meal
def OutputData(self):
print(self.__type,self.name,' любит ',self.__meal)
# Главная программа
camel = Animal('Кеша','верблюд','колючки')
print(camel.name)
#print(camel.type)
#print(camel.meal)
camel.OutputData()
Результат:
Кеша
верблюд Кеша
любит
колючки
22
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Теперь попробуем раскомментировать выделенные красным строки, пытаясь
получить доступ к закрытым полям класса Animal – type и meal. Получим такой результат:
Кеша
Traceback (most recent call last):
File "C:/2019/УЧЁБА ОСЕНЬ 2019/ОБЪЕКТНО-ОРИЕНТИРОВАННЫЙ АНАЛИЗ И ПРОГРАММИРОВАНИЕ (ЭБб-18-1)/ЛЕКЦИИ/Лекция - 12/12-3.py", line
15, in
print(camel.type)
AttributeError: 'Animal' object has no attribute 'type'
Мы получили исключение типа AttributeError при попытке доступа к полю
type объекта camel класса Animal.
Таким образом, доступ к полям type и meal класса Animal извне класса напрямую будет закрыт.
Кроме приватных данных класса (с двойным подчёркиванием) можно также использовать так называемые защищённые данные (protected) с одним подчёркиванием.
Доступ к защищённым переменным возможет только из того пакета, в котором хранится класс. Более правильно, в соответствии с философией Python, доступ к защищённым переменным извне нежелателен, но возможен, если очень нужно )).
attr
__attr
_attr
public
Доступ отовсюду
private
Доступ только изнутри класса
protected
Доступ из пакета, извне ограничен
(нежелателен)
Приватная переменная или приватный метод не видны за пределами методов
класса, в котором они определяются. Приватные переменные и методы полезны по
двум причинам: они повышают уровень безопасности и надёжности за счёт избирательного ограничения доступа к важным или критичным частям реализации объекта, а также предотвращают конфликты имён, которые могут возникнуть из-за применения наследования. Класс может определить приватную переменную и на23
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
следовать от класса, определяющего приватную переменную с тем же именем, но это
не создаёт проблем, так как приватность переменных гарантирует хранение их раздельных копий. Приватные переменные упрощают чтение кода, поскольку они явно
указывают, что должно использоваться только внутри класса. Все остальное относится
к интерфейсу класса.
Пример 12-4.
Здесь мы делаем поле meal защищённым. Из одного пакета доступ к полю открыт.
Пример 12-5.
Здесь мы выводим класс Animal в отдельный модуль AnimalClass.py. И пытаемся получить доступ к полю _meal извне.
Данный механизм реализует свойства ООП ИНКАПСУЛЯЦИЯ –
сокрытие деталей реализации
класса.
Другими словами, некоторые (закрытые) данные и (закрытые) методы класса мы
прячем от доступа извне класса (инкапсулируем эти данные).
Приватность данных вовсе не означает, что эти данные нельзя изменять нигде и
никогда. Просто требуется правильно организовать доступ к закрытым полям класса
через его Свойства (Property).
Свойства класса – это механизм
доступа к закрытым данным (полям) объектов класса извне класса.
24
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
Для управления данными объекта извне объекта, необходимо использовать специальные методы класса, именуемые getter (возвращает значение закрытого поля) и
setter (устанавливает значение закрытого поля).
Описать свойства класса в Python, можно используя различную стилистику. Мы
рассмотрим 2 основных варианта.
Способ 1. Через функцию property().
Через функцию property() можно создать идентификатор, через которое можно
1)получить, 2) изменить или 3)удалить значение закрытого поля класса. Функция
Property имеет следующую форму:
<Свойство> = property(<Чтение>[, <Запись>[, <Удаление>[,
<Строка документирования>]]])
В первых трёх параметрах указывается ссылка на соответствующий метод класса. При попытке получить значение будет вызван метод, указанный в первом параметре. При операции присваивания значения будет вызван метод, указанный во втором
параметре. Этот метод должен принимать один параметр. В случае удаления атрибута
вызывается метод, указанный в третьем параметре. Если в качестве какого-либо параметра задано значение None, то это означает, что соответствующий метод не поддерживается. Рассмотрим свойства класса на примере.
Пример 12-6.
# 12-6 Свойства класса - стиль 1
class Animal:
__type = "" # Закрытое поле класса
_meal = "" # Защищённое поле класса
def __init__(self, name, type, meal):
self.name = name
self.__type = type
self._meal = meal
def OutputData(self):
print(self.__type,self.name,' любит ',self._meal)
25
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
def getType(self):
# Чтение
return self.__type
def setType(self, value): # Запись
self.__type = value
def delType(self):
# Удаление
del self.__type
typ = property(getType, setType, delType, "Строка документирования")
# Главная программа
camel = Animal('Кеша','верблюд','колючки')
camel.OutputData()
camel.typ = 'Лошадь'
# Вызывается метод setType()
camel.OutputData()
print(camel.typ)
# Вызывается метод getType()
#del camel.typ
# Вызывается метод delType()
print(camel.typ)
Результат:
верблюд Кеша любит колючки
Лошадь Кеша любит колючки
Лошадь
Лошадь
Здесь для доступа к закрытому полю __type с помощью функции property мы создаём свойство typ. Как видим из примера, используя свойство typ, мы изменили тип
животного (был Верблюд, стала Лошадь).
Способ 2. Через декоратор @property.
Декоратор функции в Python – это обёртка функции. Чтобы создать свойство,
используем декоратор свойства с методом, имя которого соответствует имени свойства.
Пример 12-7.
# 12-7 Свойства класса - стиль 2
class Animal:
26
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
__type = "" # Закрытое поле класса
_meal = "" # Зищённое поле класса
def __init__(self, name, type, meal):
self.name = name
self.__type = type
self._meal = meal
def OutputData(self):
print(self.__type,self.name,' любит ',self._meal)
@property
def typ(self):
# Чтение
return self.__type
@typ.setter
def typ(self, value): # Запись
self.__type = value
# Главная программа
camel = Animal('Кеша','верблюд','колючки')
camel.OutputData()
camel.typ = 'Лошадь'
camel.OutputData()
print(camel.typ)
print(camel.typ)
Результат:
верблюд Кеша любит колючки
Лошадь Кеша любит колючки
Лошадь
Лошадь
Здесь мы также смогли поменять значение закрытого поля type с помощью свойства typ.
Методы getter и setter могут включать дополнительные действия. Например, они
могут при установке значения поля объекта проверять его на допустимость.
Пример 12-8.
@typ.setter
27
Объектно-ориентированный анализ
и программирование. Лекция 2020 – 2
.
def typ(self, value): # Запись
if value == 'лошадь' or value == 'верблюд' or value ==
'корова':
self.__type = value
else:
print(5*'*',value,' - это не парнокопытное
!',5*'*')
Здесь мы поменяли свойство setter таким образом, что допускает задание только
одного из 3-х типов парнокопытных животных, попытки установить другой тип будут
пресекаться.
Закрытыми можно делать не только поля (данные) класса, но и методы класса.
Механизм инкапсуляции (сокрытия деталей реализации) играет очень важную
роль в ООП-технологии.
28