питон. ООП_на_Python_Учебное пособие_var5a. Методическое пособие по дисциплине Введение в компьютерные технологии Москва Физический факультет мгу имени м в ломоносова 2022
Скачать 0.51 Mb.
|
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 рассчитывает изменение положения тела в пространстве и его вектоа скорости под действием заданной силы за время ∆ ݐ по описанной выше схеме. 0>3> |