Главная страница

питон. ООП_на_Python_Учебное пособие_var5a. Методическое пособие по дисциплине Введение в компьютерные технологии Москва Физический факультет мгу имени м в ломоносова 2022


Скачать 0.51 Mb.
НазваниеМетодическое пособие по дисциплине Введение в компьютерные технологии Москва Физический факультет мгу имени м в ломоносова 2022
Анкорпитон
Дата07.11.2022
Размер0.51 Mb.
Формат файлаpdf
Имя файлаООП_на_Python_Учебное пособие_var5a.pdf
ТипМетодическое пособие
#774857
страница5 из 6
1   2   3   4   5   6
19.
Статическиеметоды
Ранее было сказано, с определенным допущением классы можно рассматривать как модули, содержащие переменные со значениями и функции. Только здесь переменные называются полями или свойствами, а функции – методами. Вместе поля и методы называются атрибутами. Когда метод применяется к объекту, этот экземпляр передается в метод в качестве первого аргумента: class
A
: def meth(
self
): print(
'meth'
) a =
A
() a.meth()
# для объкта a (экземпляра класса A) вызываем метод meth(a)
A
.meth(a)
# вызываем метод принадлежащий классу A и передаем ему экземпляр a
Т.е. a.meth() на самом деле преобразуется к A.meth(a), то есть мы идем к "модулю A" и в его пространстве имен ищем атрибут meth. Там оказывается, что meth это функция, принимающая один обязательный аргумент. Тогда ничего не мешает сделать так: class
A
: def meth(
self
): print(
'meth'
)
A
.meth(10)
В таком "модульном формате" вызова методов передавать объект-экземпляр именно класса A совсем не обязательно. Что делать, если возникает необходимость в методе, который не принимал бы объект данного класса в качестве аргумента? Да, мы можем объявить метод вообще без параметров и вызывать его только через класс: class
A
: def meth(): print(
'meth'
) a =
A
()
A
.meth()
# вызываем метод без параметров принадлежащий классу A
a.meth()
# Ошибка, в метод без параметров передается аргумент self
Получается странная ситуация. Ведь meth() вызывается не только через класса, но и через порожденные от него объекты. Однако в последнем случае всегда будет возникать ошибка. Кроме того, может понадобиться метод с параметрами, но которому не надо передавать экземпляр данного класса.

31
Для таких ситуаций предназначены статические методы. Эти методы могут вызываться через объекты данного класса, но сам объект в качестве аргумента в них не передается. В Python острой необходимости в статических методах нет. Если нам нужна просто какая-нибудь функция, мы можем определить ее вне класса. Единственное достоинство в том, что функция оказывается в пространстве имен этого класса.
Статические методы в Python реализуются с помощью специального декоратора
@staticmethod: class
A
:
@
staticmethod def meth(): print(
'meth'
)
A
.meth()
# вызываем статический метод (без параметров) принадлежащий классу A
a =
A
() a.meth()
# вызываем статический метод без параметров через экземпляр класса A
Вообще, если в теле метода не используется self, то есть ссылка на конкретный объект, следует задуматься, чтобы сделать метод статическим.
Пусть у нас будет класс "Цилиндр". При создании объектов от этого класса у них заводятся поля высота и диаметр, а также площадь поверхности. Вычисление площади можно поместить в отдельную статическую функцию. Она вроде и относится к цилиндрам, но, с другой стороны, само вычисление объекта не требует и может быть использовано где угодно. from math import pi class
Cylinder
:
@
staticmethod def make_area(
d
, h
): circle = pi * d
**2 / 4 side = pi * d
* h
return circle*2 + side def
__init__(
self
, diameter
, high
): self
.dia = diameter self
.h = high self
.area = self
.make_area(
diameter
, high
) a =
Cylinder
(1, 2) print(a.area) print(a.make_area(2, 2))
В примере вызов make_area() за пределами класса возможен в том числе через экземпляр. При этом понятно, в данном случае свойство area самого объекта a не меняется.
Мы просто вызываем функцию, находящуюся в пространстве имен класса.
20.
Примерыобъектно-ориентированныхпрограммна Python
В ООП очень важно предварительное проектирование. В общей сложности можно выделить следующие этапы разработки объектно-ориентированной программы:
1. Формулирование задачи.
2. Определение объектов, участвующих в ее решении.
3. Проектирование классов, на основе которых будут создаваться объекты. В случае необходимости установление между классами наследственных связей.
4. Определение ключевых для данной задачи свойств и методов объектов.
5. Создание классов, определение их полей и методов.
6. Создание объектов.
7. Решение задачи путем организации взаимодействия объектов.

32
Далее приведены примеры классов в порядке возрастания сложности. Сначала простые классы. Далее – классы демонстриующие наследование, полиморфизм и композицию.

Класс рациональных дробей
Простой класс, представляющий рациональную дробь (num – числитель, den – знаменатель). Класс содержит конструктор и перегруженные методы умножения и деления
(дроби на дробь и дроби на целое число). Метод создания случайной дроби из заданного диапазона целых чисел объявлен как статический.
Следует отметить, что в языке имеется готовый тип Fraction в модуле fractions. И данный пример нужно рассматривать только как образец для создания собственных классов. from math import gcd from random import randint class
My_Fraction
: def
__init__(
self
, num
, den
): if num
!= 0 and den
!= 0: k = gcd(
num
, den
)
# находим НОД
self
.num = num
// k
# числитель self
.den = den
// k
# знасенатель else
: raise
ValueError
@
staticmethod def generate(
num_min
, num_max
, den_min
, den_max
): return
My_Fraction
(randint(
num_min
, num_max
), randint(
den_min
, den_max
)) def
__str__(
self
):
# Метод преобразования дроби в строку return f'{self.num}/{self.den}'
def
__mul__(
self
, other
):
# Умножение дробей if isinstance(
other
,
My_Fraction
):
# перегрузка умножения на дробь return
My_Fraction
(
self
.num * other
.num, self
.den * other
.den) if isinstance(
other
,
int
):
# перегрузка умножения на целое число return
My_Fraction
(
self
.num * other
, self
.den) return self
# для остальных типов возвращаем значение самого объекта def
__truediv__(
self
, other
):
# Деление дробей if isinstance(
other
,
My_Fraction
):
# перегрузка деления на дробь return
My_Fraction
(
self
.num * other
.den, self
.den * other
.num) if isinstance(
other
,
int
):
# перегрузка деления на целое число return
My_Fraction
(
self
.num, self
.den*
other
) raise
TypeError
# для остальных типов вызываем исключение
#----------------------------------------------------------------------
# Список из 5 случайных дробей:
a = [
My_Fraction
.generate(1, 9, 1, 9) for i
in range
(5)] for f in a: b =
My_Fraction
.generate(1, 9, 1, 9)
# дробь для правого операнда cm = f * b print(
f'{f} * {b} = {cm}'
)
# пример умножения на дробь cd = f / b print(
f'{f} / {b} = {cd}'
)
# пример деления на дробь n=randint(1, 9)
# число для правого операнда cm = f * n print(
f'{f} * {n} = {cm}'
)
# пример умножения на число cd = f / n print(
f'{f} / {n} = {cd}'
)
# пример деления на число

33

Класс «Студент»
Класс содержит имя студента full_name, номер группы group_number и список полученных оценок progress. В программе вводится список студентов. Далее список сортируется по имени, потом выводятся студенты, имеющие неудовлетворительные оценки. class
Student
: def
__init__(
self
,
full_name
=
""
, group_number
=0, progress
=[]):
# конструктор self
.full_name = full_name
# имя self
.group_number = group_number
# номер группы self
.progress = progress
# оценки def
__str__(
self
):
# печатаемое представление экземпляра класса txt =
'Студент: '
+ self
.full_name +
' Группа: '
+ self
.group_number txt +=
' Оценки:'
for x in self
.progress: txt +=
' '
+ str
(x)
# добавляем список оценок return txt
#----------------------------------------------------------------------------- def
SortParam(
st
):
# функция определяющая атрибут для сортировки return st
.full_name
#----------------------------------------------------------------------------- st_size = 5
# количество студенов students = []
# создание пустого списка for i in range
(st_size):
# цикл для ввода st_size студентов print(
"Введите полное имя студента: "
) full_name = input()
# ввод фамилии print(
"Введите номер группы: "
) group_number = input()
# ввод группы n=5 print(
'Введите '
,n,
' оценок в столбик: '
)
# у каждого студента n оценок progress = [] for i in range
(n): score = int
(input())
# ввод оценок progress.append(score)
# добавление оценок
# создание экзепляра класса Student:
st =
Student
(full_name, group_number, progress) students.append(st)
# добавление экземпляра в список print(
"Students list:"
) for st in students:
# вывод полного списка студентов print(st)
# сортировка по фамилии, ключ сортировки определяется функцией SortParam:
students = sorted(students, key=SortParam) print(
"Sorted students:"
) for st in students:
# вывод отсортированного списка print(st) print(
"bad students:"
) n=0
# счетчик количества неуспевающих for st in students:
# вывод неуспевающих for val in st.progress: if val<3 :
# есть плохая оценка print(st)
# выводим студента с плохой оцекой n += 1 break if n == 0: print(
"no matches were found."
)

34

Виртуальная модель процесса обучения
Пусть необходимо разработать виртуальную модель процесса обучения. В программе должны быть объекты-ученики, учитель, кладезь знаний.
Потребуется три класса – "учитель", "ученик", "данные". Учитель и ученик во многом похожи, оба – люди. Значит, их классы могут принадлежать одному надклассу "человек".
Однако в контексте данной задачи у учителя и ученика вряд ли найдутся общие атрибуты.
Определим, что должны уметь объекты для решения задачи "увеличить знания":
• Ученик должен уметь брать информацию и превращать ее в свои знания.
• Учитель должен уметь учить группу учеников.
• Данные могут представлять собой список знаний. Элементы будут извлекаться по индексу. class
Data
: def __init__(
self
, info
):
#конструктор self
.info = list
(
info
) def __getitem__(
self
, i
): # перегрузка [] для извлечения элемента из Data return self
.info[
i
] class
Teacher
: def __init__(
self
):
#конструктор self
.work = 0 # количество учеников def teach(
self
, info
, pupil
): # обучение данными из info учеников pupil for i in pupil
: i.take(
info
)
# учим ученика i self
.work += 1 # количество учеников увеличилось на 1 class
Pupil
: def __init__(
self
):
#конструктор self
.knowledge = [] # список полученных знаний def take(
self
, info
):
# получение знания self
.knowledge.append(
info
)
В класс Teacher добавлено свойство экземпляров work, чтобы подсчитывать количество проделанной учителем работы.
Теперь посмотрим, как объекты этих классов могут взаимодействовать между собой: lesson =
Data
(['class', 'object', 'inheritance', 'polymorphism', 'encapsulation']) marIvanna =
Teacher
() vasy =
Pupil
() pety =
Pupil
() marIvanna.teach(lesson[2], [vasy, pety])
# учить обоих знанию lesson[2]
marIvanna.teach(lesson[0], [pety])
# учить pety знанию lesson[0]
print(vasy.knowledge) # вывод: ['inheritance'] print(pety.knowledge) # вывод: ['inheritance', 'class']

Игра-стратегиия «Солдаты и герои»
В некой игре-стратегии есть солдаты и герои. У всех есть свойство, содержащее уникальный номер объекта, и свойство, в котором хранится принадлежность команде. У солдат есть метод "иду за героем", который в качестве аргумента принимает объект типа "герой". У героев есть метод увеличения собственного уровня.
В основной ветке программы создается по одному герою для каждой команды. В цикле генерируются объекты-солдаты. Их принадлежность команде определяется случайно.
Солдаты разных команд добавляются в разные списки. Измеряется длина списков солдат противоборствующих команд и выводится на экран. У героя, принадлежащего команде с более длинным списком, увеличивается уровень.

35
Отправляем одного из солдат следовать за первым героем и выводим их идентификационные номера. from random import randint class
Person
: count = 0 def
__init__(
self
, c
): self
.id =
Person
.count
Person
.count += 1 self
.command = c
class
Hero
(
Person
): def
__init__(
self
, c
):
Person
.__init__(
self
, c
) self
.level = 1 def up_level(
self
): self
.level += 1 class
Soldier
(
Person
): def
__init__(
self
, c
):
Person
.__init__(
self
, c
) self
.my_hero =
None def follow(
self
, hero
): self
.my_hero = hero
.id h1 =
Hero
(1)
# первый герой h2 =
Hero
(2)
# второй герой army1 = []
# первая армия army2 = []
# вторая армия for i in range
(20): n = randint(1, 2) if n == 1: army1.append(
Soldier
(n))
# добавление солдата в первую армию else
: army2.append(
Soldier
(n))
# добавление солдата во вторую армию print(len(army1), len(army2))
# численность армий if len(army1) > len(army2):
# повышение уровня героя для команды с большей численностью h1.up_level() elif len(army1) < len(army2): h2.up_level() army1[0].follow(h1)
# первому солдату следовать за 1 героем print(army1[0].id, h1.id) )
# номера первого солдата и первого героя

36

Класс «Битва»
В классе Battle реализована композиция: он включает два объекта типа Soldier. from random import randint class
Soldier
:
# класс описывающий одного солдата def
__init__(
self
,
name
=
'Noname'
,
health
= 100):
# конструктор self
.name = name
# задаем имя воина self
.health = health
# задаем начальное здоровье def set_name(
self
, name
): self
.name = name
# есть возможность поменять имя def make_kick(
self
, enemy
):
# метод моделирующий атаку на солдата enemy enemy
.health -= 20
# при атаке здоровье врага уменьшаем на 20
if enemy
.health<0 : enemy
.health = 0 self
.health += 10
# а собственное здоровье увеличиваем на 10
print(
self
.name,
"бьет"
, enemy
.name)
# выводим кто кого бьет print(
'%s = %d'
% (
enemy
.name, enemy
.health))
# выводим состояние врага
#----------------------------------------------------------------------------- class
Battle
: def
__init__(
self
,
u1
,
u2
):
# конструктор
# композиция: класс включает двух солдат u1 и u2
self
.u1 = u1
self
.u2 = u2
self
.result =
"Сражения не было"
# строка для хранения состояния сражения def battle(
self
):
# метод моделирующий сражение while self
.u1.health > 0 and self
.u2.health > 0: n = randint(1, 2)
# определяем, кто атакует if n == 1: self
.u1.make_kick(
self
.u2)
# если атакует первый else
: self
.u2.make_kick(
self
.u1)
# если атакует второй if self
.u1.health > self
.u2.health:
# определяем, кто победил self
.result = self
.u1.name +
" ПОБЕДИЛ"
elif self
.u2.health > self
.u1.health: self
.result = self
.u2.name +
" ПОБЕДИЛ"
def who_win(
self
):
# вывод результата print(
self
.result)
#----------------------------------------------------------------------------- first =
Soldier
(
'Mr. First'
,140)
# созаем 1 солдата с именем Mr. First и здоровьем
140
second =
Soldier
()
# созаем 2 солдата с паметрами по умолчанию second.set_name(
'Mr. Second'
)
# меняем имя 2 солдата b =
Battle
(first,second)
# создаем объект Battle b.battle()
# запускаем сражение b.who_win()
# выводим итог

37

Класс «Колода карт»
Класс DeckOfCards
содержит список карт. Конструктор класса Card инициализирует значения масти и номера из списков NumsList и MastList, которые объявлены как общие атрибуты класса. import time import random
; class
Card
():
# класс Карта
NumsList = [
"Джокер"
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
,
'10'
,
"Валет"
,
"Дама"
,
"Король"
,
"Туз"
]
MastList = [
"пик"
,
"крестей"
,
"бубей"
,
"червей"
] def
__init__(
self
, i
, j
):
# конструктор self
.Mastb = self
.MastList[
i
-1];
# карта self
.Num = self
.NumsList[
j
-1];
# масть
#---------------------------------------- class
DeckOfCards
():
# класс Колода карт def
__init__(
self
):
# конструктор self
.deck = [
None
] * 56;
# список из 56 карт k = 0; for i in range
(1, 4 + 1): for j in range
(1, 14 + 1): self
.deck[k] =
Card
(i, j);
# очередная карта k += 1; def shuffle(
self
):
# перемешивание карт random
.shuffle(
self
.deck); def get(
self
, i
):
# вытаскивание i-й карты из колоды if
0 <= i
<=55 : answer = self
.deck[
i
].Num; answer +=
" "
; answer += self
.deck[
i
].Mastb; else
: answer =
"В колоде только 56 карт"
return answer;
#---------------------------------------- deck =
DeckOfCards
();
# создали колоду deck.shuffle();
# перемешали print(
'Выберите карту из колоды в 56 карт:'
); n=
int
(input()) if n<=56 : card = deck.get(n-1); print(
'Вы взяли карту: '
, card, end=
'.\n'
); else
: print(
"В колоде только 56 карт"
)

38

Класс «Паспорт»
Класс ForeignPassport является производным от класса Passport. Метод PrintInfo
существует в обоих классах. PassportList представляет собой список, содержащий объекты обоих классов. Вызов метода PrintInfo для каждого элемента списка демонстрирует его полиморфное поведение. class
Passport
(): def
__init__(
self
, first_name
, last_name
, country
, date_of_birth
, numb_of_pasport
): self
.first_name = first_name self
.last_name = last_name self
.date_of_birth = date_of_birth self
.country = country self
.numb_of_pasport = numb_of_pasport def
PrintInfo(
self
): print(
"\nFullname: "
,
self
.first_name,
" "
,
self
.last_name) print(
"Date of birth: "
,
self
.date_of_birth) print(
"County: "
,
self
.country) print(
"Passport number: "
,
self
.numb_of_pasport) class
ForeignPassport
(
Passport
): def
__init__(
self
, first_name
, last_name
, country
, date_of_birth
, numb_of_pasport
,
visa
): super
().__init__(
first_name
, last_name
, country
, date_of_birth
, numb_of_pasport
) self
.visa = visa def
PrintInfo(
self
): super
().PrintInfo() print(
"Visa: "
,
self
.visa)
PassportList=[] request =
ForeignPassport
(
'Ivan'
,
'Ivanov'
,
'Russia'
,
'12.03.1967'
,
'123456789'
,
'USA'
)
PassportList.append(request) request =
Passport
(
'Иван'
,
'Иванов'
,
'Россия'
,
'12.03.1967'
,
'45001432'
)
PassportList.append(request) request =
ForeignPassport
(
'Peter'
,
'Smit'
,
'USA'
,
'01.03.1990'
,
'21435688'
,
'Germany'
)
PassportList.append(request) for emp in
PassportList: emp.PrintInfo()

39

Класс «Склад оргтехники»
Классы Printer, Scaner и Xerox являются производными от класса Equipment. Метод
str() перегружен только в классе Printer, для остальных используется метод из базового класса. Метод action() перегружен для всех производных классов. Вызов этих методов для каждого элемента списка демонстрирует их полиморфное поведение. class
Equipment
: def
__init__(
self
, name
, make
, year
): self
.name = name
# производитель self
.make = make
# модель self
.year = year
# год выпуска def action(
self
): return
'Не определено'
def
__str__(
self
): return f'{self.name} {self.make} {self.year}'
#------------------------------------------------ class
Printer
(
Equipment
): def
__init__(
self
, series
, name
, make
, year
): super
().__init__(
name
, make
, year
) self
.series = series
# серия def
__str__(
self
): return f'{self.name} {self.series} {self.make} {self.year}'
def action(
self
): return
'Печатает'
#------------------------------------------------ class
Scaner
(
Equipment
): def
__init__(
self
, name
, make
, year
): super
().__init__(
name
, make
, year
) def action(
self
): return
'Сканирует'
#------------------------------------------------ class
Xerox
(
Equipment
): def
__init__(
self
, name
, make
, year
): super
().__init__(
name
, make
, year
) def action(
self
): return
'Копирует'
#------------------------------------------------ sklad = []
# создаем объект сканер и добавляем scaner =
Scaner
(
'Mustek'
,
'BearPow 1200CU'
, 2010) sklad.append(scaner)
# создаем объект ксерокс и добавляем xerox =
Xerox
(
'Xerox'
,
'Phaser 3120'
, 2019) sklad.append(xerox)
# создаем объект принтер и добавляем printer =
Printer
(
"1200"
,
'hp'
,
'Laser Jet'
, 2018) sklad.append(printer)
# выводим склад print(
"На складе имеются:"
) for x in sklad: print(x,end=
' '
) print(x.action())
# забираем со склада все принтеры for x in sklad: if isinstance(x,
Printer
): sklad.remove(x)
# выводим склад print(
"\nНа складе осталось:"
) for x in sklad: print(x,end=
' '
) print(x.action())

40

Задача трёх тел
Задача трех тел — одна из задач небесной механики, состоящая в определении относительного движения трёх тел (материальных точек), взаимодействующих по закону тяготения Ньютона. В любой момент времени на каждое из тел действуют силы гравитационного притяжения остальных двух тел. Сила, с которой первое из тел притягивает второе, выражется формулой
ܨ
ଵଶ
ሬሬሬሬሬԦ = −ܩ ݉

݉

ݎ
ଵଶ
ሬሬሬሬሬԦ
ݎ
ଵଶ

, где
ܩ — гравитационная постоянаая, ݉

и
݉

— массы тел,
ݎ
ଵଶ
ሬሬሬሬሬԦ = ݎ

ሬሬሬԦ − ݎ

ሬሬሬԦ — вектор от первого к тела к второму, где положение тел в пространстве задается радиус векторами
ݎ

ሬሬሬԦ и
ݎ

ሬሬሬԦ. Остальные силы попарного взаимодействия можно получить заменной индексов.
Итого, движение всех трел задаётся системой динамических уравнений

݉

ܽ

ሬሬሬሬԦ = ܨ
ଶଵ
ሬሬሬሬሬሬԦ + ܨ
ଷଵ
ሬሬሬሬሬሬԦ
݉

ܽ

ሬሬሬሬԦ = ܨ
ଵଶ
ሬሬሬሬሬԦ + ܨ
ଷଶ
ሬሬሬሬሬሬԦ
݉

ܽ

ሬሬሬሬԦ = ܨ
ଵଷ
ሬሬሬሬሬԦ + ܨ
ଶଷ
ሬሬሬሬሬሬԦ
Такая система уравнения не имеет аналитического решения в общем случае, но можно решить её приближенно, введя малый шаг по времени

ݐ и используя следующую схему обновления положения и скорости тела в следующий момент времени
ݒ(ݐ + ∆ݐ)
ሬሬሬሬሬሬሬሬሬሬሬሬሬሬሬሬሬሬሬԦ = ݒ(ݐ)
ሬሬሬሬሬሬሬሬԦ + ܽ(ݐ)
ሬሬሬሬሬሬሬሬԦ∆ݐ,
ݎ(ݐ + ∆ݐ)
ሬሬሬሬሬሬሬሬሬሬሬሬሬሬሬሬሬሬሬԦ = ݎ(ݐ)
ሬሬሬሬሬሬሬሬԦ + ݒ(ݐ)
ሬሬሬሬሬሬሬሬԦ∆ݐ.
Рассмотрим решение задачи трех тел в объектно ориентированном стиле. Первое, что бросается в глаза — многие величины описываются векторами в трехмерном пространстве. Полезно в таком случае реализовать класс трехмерных векторов и определить для него операции сложения, вычитания, вычисления его нормы, домножения на скаляр и другие. В приведенных ниже примерах предполагается, что в программе определен класс `Vector3D` удовлетворяющий всем перечисленным свойствам, а реализация такого класса оставляется чителю в качестве упражнения (смотри задачи для самостоятельного решения).
Класс Body описывает небесное тело. В рамках нашей задачи тело задаётся его массой, положением пространства (радиус вектор) и вектором скорости. Метод update
рассчитывает изменение положения тела в пространстве и его вектоа скорости под действием заданной силы за время

ݐ по описанной выше схеме.
1   2   3   4   5   6


написать администратору сайта