Класс Rational является хорошим примером создания в Pythonодного класса. Давайте рассмотрим, как в языке реализуется создание семейства классов, связанных отношениями наследования.
В классическом варианте одиночного наследования потомок наследует все атрибуты родителя - данные и методы - за исключением конструктора, который должен задаваться для каждого класса. Конструктор потомка, прежде чем начать свою работу, вызывает конструктор родителя, который создает ту часть объекта, которая определена в родительском классе. После этого конструктор потомка завершает построение объекта, добавляя в объект ту часть, которая задана потомком и не наследуется от родителя.
Посмотрим, как эта концепция реализуется в Python. Создадим в нашем Решении новый проект с именем Inheritance. Соответственно в проекте появится модуль с этим именем. Добавим в проект два класса - Father и Son, связанных отношением наследования. Каждый класс будем записывать в отдельный модуль класса. Так что в проекте Inheritance появятся дополнительно два модуля Father и Son.
Содержательная сторона классов нас особо не интересует. У класса Father зададим три поля и два метода - конструктор и метод __str__, позволяющий выводить на печать информацию об объекте. Вот описание этого класса:
class Father(object):
"""SimpleTest of Inheritance
Father and Son
"""
#Fields of class Father
#first_name: string
#last_name: string
#town : string
def __init__(self, first_name, last_name, town):
self.first_name = first_name
self.last_name = last_name
self.town = town
def __str__(self):
return ('fam = ' + self.last_name + ' ' + self.first_name +
' town = ' + self.town)
В сравнении с ранее построенным классом Rational в этом описании ничего нового нет.
Построим теперь класс Son - потомок класса Father. Наш потомок ничего не создает, наследуя все от своего родителя. Заметьте, что в Python конструктор может наследоваться и его можно не задавать, если потомок не добавляет новых полей:
from Father import Father
class Son(Father):
"""
sub class of super class Father
"""
Заметьте, поскольку классы находятся в отдельных модулях, то определению класса Son должен предшествовать импорт родительского класса.
В нашем примере класс Son ничего не определяет, ничего не создает, все наследует от родителя - и конструктор и метод str.
Модуль Inheritance в нашем проекте будет играть роль модуля тестера, так что все тесты будем помещать в этот модуль. Приведу тест, в котором создаются объекты как родительского класса, так и класса потомка:
"""
tester class
"""
from Father import Father
from Son import Son
def test1():
"""
Create objects Father and Son
"""
f1 = Father('Vladimir', 'Petrov', 'Tver')
print('Father: ', f1)
s1 = Son('Ilya', 'Petrov', 'Moscow')
print('Son: ', s1)
Вот результаты работы этого теста
Потомок, который ничего не делает не интересен. Поэтому будем усложнять ситуацию. Начнем с того, что добавим в класс Son новое свойство - возраст объекта - атрибут age. Как следствие, у потомка появится конструктор и изменится метод str. Вот описание конструктора класса:
def __init__(self, first_name, last_name, town, age):
super().__init__(first_name, last_name, town)
self.age = age
Конструктор потомка вызывает конструктор родителя, передает ему необходимые параметры, а затем выполняет свою часть работы. Для обращения к атрибутам родительского класса, в частности, вызова его методов используется конструкция super().
Аналогично, модифицированный метод __str__ вызывает родительский метод, присоединяя к результату его работы свою часть информации:
def __str__(self):
return super().__str__() + ' age = ' + str(self.age)
В следующем тесте создадим новый объект класса Son, у которого уже будет дополнительный атрибут:
def test2():
s = Son('Mike', 'Petrov', 'Praga', 27 )
print('Son: ', s)
Вот результаты:
Давайте добавим в проект еще один класс - Daughter - потомка класса Son. В этом классе добавим новый атрибут и соответственно переопределим конструктор и метод __str__:
from Son import Son
class Daughter(Son):
"""
Потомок класса Father
Прямой потомок класса Son
"""
def __init__(self, first_name, last_name, town, age, sex):
super().__init__(first_name, last_name, town, age)
self.sex = sex
def __str__(self):
return super().__str__() + ' sex = ' + self.sex
Никакие новые особенности при расширении дерева наследования не возникают. Создадим объекты, определенные на разных уровнях иерархии наследования:
def test3():
f = Father('Vladimir', 'Petrov', 'Tver')
s1 = Son('Ilya', 'Petrov', 'Moscow', 31)
s2 = Son('Mike', 'Petrov', 'Praga', 27 )
gd = Daughter('Anna', 'Petrova', 'Moscow', 4, 'f')
print('Father: ', f)
print('Son: ', s1)
print('Son: ', s2)
print('Daughter: ', gd)
Вот результат работы этого теста:
Рассмотрим, что происходит с методами в результате построения семейства классов, связанных отношением наследования. Мы уже видели на примере построения иерархии Father <= Son <=Daugter, как классы потомки переопределяют встроенные методы __init__ и __str__. При этом сохраняется возможность доступа к соответствующим методам родительского класса.
Из общих принципов ООП, выполняемых в Python, следует, что потомок наследует все методы родительских классов и может добавлять собственные методы. Если имена методов, добавляемых потомком, не совпадают с именами методов родителей, то никаких проблем не возникает. Если родитель создал метод с именем f, а потомок - метод с именем g, то у потомка будут оба метода - f и g. Но потомок по ряду причин может захотеть переопределить метод родителя и задать собственный метод с именем f. В языках ООП, где допускается перегрузка методов, в классе может существовать множество методов с одним именем при условии, что все методы отличаются по сигнатуре. В Python, где нет типов аргументов при описании метода, перегрузка методов не допускается. Поэтому, если потомок создает метод с именем f, совпадающим с именем родительского метода, то метод потомка перекрывает метод родителя и у потомка будет только один метод с именем
f. Однако родительский метод остается доступным для вызова потомком. Проще всего, дать родительскому методу другое имя.
Продемонстрируем все эти детали, расширив нашу иерархию построенных классов.
Добавим объектам класса Father, а соответственно и всем его потомкам, три свойства и два метода. Новые свойства - place, position, hobby - будут задавать место работы, чем объект занимается на работе, и его хобби. Метод Work позволяет получить доступ на чтение свойств, характеризующих работу, а метод Hobby - позволяет узнать хобби объекта.
Вот как теперь выглядит конструктор этого класса:
def __init__(self, first_name, last_name, town,
place = 'университет', position = 'преподаватель',
hobby = 'прогулки на природе'):
self.first_name = first_name
self.last_name = last_name
self.town = town
self.hobby = hobby
self.place = place
self.position = position
Для новых атрибутов класса заданы значения по умолчанию.
Приведу теперь определения методов Work и Hobby:
def Work(self):
"""
работа
place - где работаешь
position - чем занимаешься
"""
print ('место работы - ', self.place)
print ('род занятия - ', self.position)
def Hobby(self):
""" Любимое занятие в свободное время """
print ('В свободное время люблю - ' + self.hobby )
Конечно, подобное определение нарушает все принципы качественного программирования, поскольку негоже методу класса заниматься выводом данных на консоль, - этим должен заниматься интерфейсный класс. Но позволим себе подобное нарушение, поскольку нас сейчас интересует выяснение деталей наследования методов потомками класса, а что делает метод в данной ситуации не суть важно.
Понятно, что новые свойства появятся и у потомков класса. Но, в связи с этим, придется в соответствующих классах изменить конструктор, чтобы была возможность задавать значения новых свойств. Приведу текст конструкторов классов Son и Daughter, учитывающий эти изменения:
def __init__(self, first_name, last_name, town, age,
place, position, hobby):
super().__init__(first_name, last_name, town, place, position, hobby)
self.age = age
def __init__(self, first_name, last_name, town, age, sex,
place, position, hobby):
super().__init__(first_name, last_name, town, age,
place, position, hobby)
self.sex = sex
Потомки могут принять методы класса. Тогда им делать ничего не нужно. Но давайте примем такое решение. Класс Son согласен с реализацией метода Work, но полагает нужным переопределить метод Hobby, и делает это следующим образом:
def Hobby(self, k):
"""
Переопределение и переименование метода родителя
"""
BestHobby = super().Hobby
if k == 1:
print ('хобби - чтение ')
elif k == 2:
print('хобби - шахматы')
elif k == 3:
print('хобби - работа на даче')
else:
BestHobby()
В данной реализации полагается, что хобби может быть не единственным, так что при вызове метода можно узнать одно из увлечений объекта. Родительский метод также доступен для вызова, для удобства работы ему дано новое имя - BestHobby.
Класс Daughter принимает методы своего родителя - класса Son, и потому не вводит никаких изменений в состав своих методов.
Построим теперь сложный тест, проверяющий создание и работу объектов построенных нами классов:
def test4():
f = Father('Vladimir', 'Petrov', 'Tver')
s1 = Son('Ilya', 'Petrov', 'Moscow', 31,
place = 'Яндекс', position = 'программист', hobby = 'ловить рыбу')
s2 = Son('Mike', 'Petrov', 'Praga', 27,
place = 'мастерская', position = 'художник', hobby = 'собирать грибы')
gd = Daughter('Anna', 'Petrova', 'Moscow', 4, 'f',
place = 'детский сад', position = 'игры', hobby = 'смотреть мультик')
print('Father: ', f.first_name )
f.Work(); f.Hobby()
print('Son: ', s1.first_name )
s1.Work(); s1.Hobby(2)
print('Son: ', s2.first_name )
s2.Work(); s2.Hobby(3)
print('Daughter: ', gd.first_name )
gd.Work(); gd.Hobby(0)
Приведу результаты работы этого теста:
Полиморфизм является одним из наиболее сложных понятий при изучении наследования. Вместе с тем полиморфизм является одним из наиболее важных инструментов ООП. В типизированных компилируемых языках ООП полиморфизм обычно построен на "трех китах". Во-первых, семейство классов, связанных наследованием, может иметь в каждом классе виртуальный метод M с одним и тем же именем и одной и той же сигнатурой, но с разной реализацией, так что метод имеет много форм, отсюда и название этого механизма - полиморфизм. Во-вторых, можно объявить объект f родительского класса и присвоить ему в момент выполнения программы объект потомка - g. В - третьих, для виртуальных методов выполняется позднее связывание. Если объект f вызывает метод M, то связывание объекта и метода выполняется не на этапе компиляции, когда доступен для связывания только родительский метод M, а откладывается до момента выполнения, когда известно, что реально метод
M вызывает объект g и связывание происходит с методом M, принадлежащим классу объекта g.
В Python, где нет этапа компиляции, нет типизированных имен, где все методы виртуальны, полиморфизм почти естественен, тем не менее интрига остается. Пусть у родительского класса есть метод P, который наследуют его потомки, не переопределяя этот метод. Пусть метод P в ходе работы вызывает метод M, который есть как у родителя, так и у каждого из потомков. Что происходит, когда потомок вызывает метод P, - метод, определенный родителем. Будет ли метод P вызывать метод M родителя или потомка, которого родитель и знать не знает? Результат зависит от того, в каком пространстве имен будет разыскиваться метод M, - в классе, где дается описание метода P, или в классе, связанном с потомком?
Давайте разберем эту ситуацию на нашем примере. Добавим в класс Father соответствующие методы P и M:
def M(self, s):
"""
Метод с этим именем и таким же набором параметров
определен в каждом классе иерархии наследования классов
Метод М - полиморфный метод
"""
return s + " Это метод M класса Father"
def P(self):
"""
Метод P определен только в классе Father
и наследуется всеми потомками.
Вызывает полиморфный метод М
"""
s = "Метод P класса Father вызывает метод M "
res = self.M(s)
return res
Переопределим полиморфный метод M в классе потомке Son:
def M(self, s):
"""
Метод с этим именем и таким же набором параметров
определен в каждом классе иерархии наследования классов
Метод М - полиморфный метод
"""д
return s + " Это метод M класса Son"
Переопределим полиморфный метод M в классе потомке Daughter:
def M(self, s):
"""
Метод с этим именем и таким же набором параметров
определен в каждом классе иерархии наследования классов
Метод М - полиморфный метод
"""
return s + " Это метод M класса Daughter"
А теперь создадим объекты этих классов и вызовем для каждого из них родительский метод P:
def test5():
f = Father('Vladimir', 'Petrov', 'Tver')
s1 = Son('Ilya', 'Petrov', 'Moscow', 31,
place = 'Яндекс', position = 'программист', hobby = 'ловить рыбу')
s2 = Son('Mike', 'Petrov', 'Praga', 27,
place = 'мастерская', position = 'художник', hobby = 'собирать грибы')
gd = Daughter('Anna', 'Petrova', 'Moscow', 4, 'f',
place = 'детский сад', position = 'игры', hobby = 'смотреть мультик')
print('ДЕМОНСТРАЦИЯ ПОЛИМОРФИЗМА')
print('Father: ', f.first_name )
print(f.P())
print('Son: ', s1.first_name )
print(s1.P())
print('Son: ', s2.first_name )
print(s2.P())
print('Daughter: ', gd.first_name )
print(gd.P())
Вот результаты:
Как видите, полиморфизм работает. Когда потомок вызывает метод родителя P, то метод P родителя вызывает метод M потомка. В этом главная мощь инструмента полиморфизма.
В классическом полиморфизме полиморфный метод применяется к объектам, принадлежащим иерархии классов, связанных отношением наследования. Язык Pythonдопускает экзотический вид полиморфизма, когда метод применяется к объектам разных классов, не связанных никаким отношением. Поскольку типы объектов не проверяются, то метод может быть применен к объектам разных классов, если выполняемые операции синтаксически допустимы.
Для демонстрации построю облегченные версии двух классов, задающих рациональные и комплексные числа, ограничившись одним методом (операцией) сложения чисел. Определения этих классов встрою непосредственно в тестирующий модуль Inheritance:
class C(object):
"""Упрощенная версия комплексных чисел """
def __init__ (self, m, n):
self.m = m; self.n = n
def __str__(self):
return str(self.m) + ' + ' + str(self.n) + 'j'
def __add__(self, c):
a = self.m + c.m
b = self.n + c.n
return C(a, b)
class R(object):
"""Упрощенная версия рациональных чисел """
def __init__ (self, m, n):
self.m = m; self.n = n
def __str__(self):
return str(self.m) + '/' + str(self.n)
def __add__(self, c):
a = self.m * c.n + self.n * c.m
b = self.n * c.n
return R(a, b)
Построим теперь тест, в котором создадим объекты этих типов и будем выполнять сложение чисел:
def test6():
c1 = C(2, 5); c2 = C(3, 7)
r1 = R(3, 6); r2 = R(4, 8)
cc = c1 + c2
rr = r1 + r2
cr = c1 + r1
rc = r2 + c2
print('c1 = ', c1)
print('c2 = ', c2)
print('r1 = ', r1)
print('r2 = ', r2)
print('cc = ', cc)
print('rr = ', rr)
print('cr = ', cr)
print('rc = ', rc)
Как видите в тесте выполняем операции сложения, где операнды принадлежат как к одному классу, так и к разным классам. В классических языках сложение операндов разного типа недопустимо и ошибка возникла бы еще на этапе компиляции. В Python, однако, никаких ошибок не возникает и действия выполняются. Вот результаты выполнения этого теста:
Операция сложения объектов спокойно выполняется, поскольку синтаксические проблемы не возникают: Оба объекта имеют поля m и n, поля одного типа, арифметические операции над ними определены. Это позволяет к комплексному числу прибавить рациональное число, интерпретируя число как комплексное. Результат при этом будет комплексным числом. Аналогично, если к рациональному числу прибавить комплексное, то результат будет рациональным числом.
В данном случае такое расширение понятия полиморфизма вряд ли является полезным. Но есть ситуации, когда такой полиморфизм полезен.
Класс Rational является хорошим примером создания в Pythonодного класса. Давайте рассмотрим, как в языке реализуется создание семейства классов, связанных отношениями наследования.
В классическом варианте одиночного наследования потомок наследует все атрибуты родителя - данные и методы - за исключением конструктора, который должен задаваться для каждого класса. Конструктор потомка, прежде чем начать свою работу, вызывает конструктор родителя, который создает ту часть объекта, которая определена в родительском классе. После этого конструктор потомка завершает построение объекта, добавляя в объект ту часть, которая задана потомком и не наследуется от родителя.
Посмотрим, как эта концепция реализуется в Python. Создадим в нашем Решении новый проект с именем Inheritance. Соответственно в проекте появится модуль с этим именем. Добавим в проект два класса - Father и Son, связанных отношением наследования. Каждый класс будем записывать в отдельный модуль класса. Так что в проекте Inheritance появятся дополнительно два модуля Father и Son.
Содержательная сторона классов нас особо не интересует. У класса Father зададим три поля и два метода - конструктор и метод __str__, позволяющий выводить на печать информацию об объекте. Вот описание этого класса:
class Father(object):
"""SimpleTest of Inheritance
Father and Son
"""
#Fields of class Father
#first_name: string
#last_name: string
#town : string
def __init__(self, first_name, last_name, town):
self.first_name = first_name
self.last_name = last_name
self.town = town
def __str__(self):
return ('fam = ' + self.last_name + ' ' + self.first_name +
' town = ' + self.town)
В сравнении с ранее построенным классом Rational в этом описании ничего нового нет.
Построим теперь класс Son - потомок класса Father. Наш потомок ничего не создает, наследуя все от своего родителя. Заметьте, что в Python конструктор может наследоваться и его можно не задавать, если потомок не добавляет новых полей:
from Father import Father
class Son(Father):
"""
sub class of super class Father
"""
Заметьте, поскольку классы находятся в отдельных модулях, то определению класса Son должен предшествовать импорт родительского класса.
В нашем примере класс Son ничего не определяет, ничего не создает, все наследует от родителя - и конструктор и метод str.
Модуль Inheritance в нашем проекте будет играть роль модуля тестера, так что все тесты будем помещать в этот модуль. Приведу тест, в котором создаются объекты как родительского класса, так и класса потомка:
"""
tester class
"""
from Father import Father
from Son import Son
def test1():
"""
Create objects Father and Son
"""
f1 = Father('Vladimir', 'Petrov', 'Tver')
print('Father: ', f1)
s1 = Son('Ilya', 'Petrov', 'Moscow')
print('Son: ', s1)
Вот результаты работы этого теста
Потомок, который ничего не делает не интересен. Поэтому будем усложнять ситуацию. Начнем с того, что добавим в класс Son новое свойство - возраст объекта - атрибут age. Как следствие, у потомка появится конструктор и изменится метод str. Вот описание конструктора класса:
def __init__(self, first_name, last_name, town, age):
super().__init__(first_name, last_name, town)
self.age = age
Конструктор потомка вызывает конструктор родителя, передает ему необходимые параметры, а затем выполняет свою часть работы. Для обращения к атрибутам родительского класса, в частности, вызова его методов используется конструкция super().
Аналогично, модифицированный метод __str__ вызывает родительский метод, присоединяя к результату его работы свою часть информации:
def __str__(self):
return super().__str__() + ' age = ' + str(self.age)
В следующем тесте создадим новый объект класса Son, у которого уже будет дополнительный атрибут:
def test2():
s = Son('Mike', 'Petrov', 'Praga', 27 )
print('Son: ', s)
Вот результаты:
Давайте добавим в проект еще один класс - Daughter - потомка класса Son. В этом классе добавим новый атрибут и соответственно переопределим конструктор и метод __str__:
from Son import Son
class Daughter(Son):
"""
Потомок класса Father
Прямой потомок класса Son
"""
def __init__(self, first_name, last_name, town, age, sex):
super().__init__(first_name, last_name, town, age)
self.sex = sex
def __str__(self):
return super().__str__() + ' sex = ' + self.sex
Никакие новые особенности при расширении дерева наследования не возникают. Создадим объекты, определенные на разных уровнях иерархии наследования:
def test3():
f = Father('Vladimir', 'Petrov', 'Tver')
s1 = Son('Ilya', 'Petrov', 'Moscow', 31)
s2 = Son('Mike', 'Petrov', 'Praga', 27 )
gd = Daughter('Anna', 'Petrova', 'Moscow', 4, 'f')
print('Father: ', f)
print('Son: ', s1)
print('Son: ', s2)
print('Daughter: ', gd)
Вот результат работы этого теста:
Рассмотрим, что происходит с методами в результате построения семейства классов, связанных отношением наследования. Мы уже видели на примере построения иерархии Father <= Son <=Daugter, как классы потомки переопределяют встроенные методы __init__ и __str__. При этом сохраняется возможность доступа к соответствующим методам родительского класса.
Из общих принципов ООП, выполняемых в Python, следует, что потомок наследует все методы родительских классов и может добавлять собственные методы. Если имена методов, добавляемых потомком, не совпадают с именами методов родителей, то никаких проблем не возникает. Если родитель создал метод с именем f, а потомок - метод с именем g, то у потомка будут оба метода - f и g. Но потомок по ряду причин может захотеть переопределить метод родителя и задать собственный метод с именем f. В языках ООП, где допускается перегрузка методов, в классе может существовать множество методов с одним именем при условии, что все методы отличаются по сигнатуре. В Python, где нет типов аргументов при описании метода, перегрузка методов не допускается. Поэтому, если потомок создает метод с именем f, совпадающим с именем родительского метода, то метод потомка перекрывает метод родителя и у потомка будет только один метод с именем
f. Однако родительский метод остается доступным для вызова потомком. Проще всего, дать родительскому методу другое имя.
Продемонстрируем все эти детали, расширив нашу иерархию построенных классов.
Добавим объектам класса Father, а соответственно и всем его потомкам, три свойства и два метода. Новые свойства - place, position, hobby - будут задавать место работы, чем объект занимается на работе, и его хобби. Метод Work позволяет получить доступ на чтение свойств, характеризующих работу, а метод Hobby - позволяет узнать хобби объекта.
Вот как теперь выглядит конструктор этого класса:
def __init__(self, first_name, last_name, town,
place = 'университет', position = 'преподаватель',
hobby = 'прогулки на природе'):
self.first_name = first_name
self.last_name = last_name
self.town = town
self.hobby = hobby
self.place = place
self.position = position
Для новых атрибутов класса заданы значения по умолчанию.
Приведу теперь определения методов Work и Hobby:
def Work(self):
"""
работа
place - где работаешь
position - чем занимаешься
"""
print ('место работы - ', self.place)
print ('род занятия - ', self.position)
def Hobby(self):
""" Любимое занятие в свободное время """
print ('В свободное время люблю - ' + self.hobby )
Конечно, подобное определение нарушает все принципы качественного программирования, поскольку негоже методу класса заниматься выводом данных на консоль, - этим должен заниматься интерфейсный класс. Но позволим себе подобное нарушение, поскольку нас сейчас интересует выяснение деталей наследования методов потомками класса, а что делает метод в данной ситуации не суть важно.
Понятно, что новые свойства появятся и у потомков класса. Но, в связи с этим, придется в соответствующих классах изменить конструктор, чтобы была возможность задавать значения новых свойств. Приведу текст конструкторов классов Son и Daughter, учитывающий эти изменения:
def __init__(self, first_name, last_name, town, age,
place, position, hobby):
super().__init__(first_name, last_name, town, place, position, hobby)
self.age = age
def __init__(self, first_name, last_name, town, age, sex,
place, position, hobby):
super().__init__(first_name, last_name, town, age,
place, position, hobby)
self.sex = sex
Потомки могут принять методы класса. Тогда им делать ничего не нужно. Но давайте примем такое решение. Класс Son согласен с реализацией метода Work, но полагает нужным переопределить метод Hobby, и делает это следующим образом:
def Hobby(self, k):
"""
Переопределение и переименование метода родителя
"""
BestHobby = super().Hobby
if k == 1:
print ('хобби - чтение ')
elif k == 2:
print('хобби - шахматы')
elif k == 3:
print('хобби - работа на даче')
else:
BestHobby()
В данной реализации полагается, что хобби может быть не единственным, так что при вызове метода можно узнать одно из увлечений объекта. Родительский метод также доступен для вызова, для удобства работы ему дано новое имя - BestHobby.
Класс Daughter принимает методы своего родителя - класса Son, и потому не вводит никаких изменений в состав своих методов.
Построим теперь сложный тест, проверяющий создание и работу объектов построенных нами классов:
def test4():
f = Father('Vladimir', 'Petrov', 'Tver')
s1 = Son('Ilya', 'Petrov', 'Moscow', 31,
place = 'Яндекс', position = 'программист', hobby = 'ловить рыбу')
s2 = Son('Mike', 'Petrov', 'Praga', 27,
place = 'мастерская', position = 'художник', hobby = 'собирать грибы')
gd = Daughter('Anna', 'Petrova', 'Moscow', 4, 'f',
place = 'детский сад', position = 'игры', hobby = 'смотреть мультик')
print('Father: ', f.first_name )
f.Work(); f.Hobby()
print('Son: ', s1.first_name )
s1.Work(); s1.Hobby(2)
print('Son: ', s2.first_name )
s2.Work(); s2.Hobby(3)
print('Daughter: ', gd.first_name )
gd.Work(); gd.Hobby(0)
Приведу результаты работы этого теста:
Полиморфизм является одним из наиболее сложных понятий при изучении наследования. Вместе с тем полиморфизм является одним из наиболее важных инструментов ООП. В типизированных компилируемых языках ООП полиморфизм обычно построен на "трех китах". Во-первых, семейство классов, связанных наследованием, может иметь в каждом классе виртуальный метод M с одним и тем же именем и одной и той же сигнатурой, но с разной реализацией, так что метод имеет много форм, отсюда и название этого механизма - полиморфизм. Во-вторых, можно объявить объект f родительского класса и присвоить ему в момент выполнения программы объект потомка - g. В - третьих, для виртуальных методов выполняется позднее связывание. Если объект f вызывает метод M, то связывание объекта и метода выполняется не на этапе компиляции, когда доступен для связывания только родительский метод M, а откладывается до момента выполнения, когда известно, что реально метод
M вызывает объект g и связывание происходит с методом M, принадлежащим классу объекта g.
В Python, где нет этапа компиляции, нет типизированных имен, где все методы виртуальны, полиморфизм почти естественен, тем не менее интрига остается. Пусть у родительского класса есть метод P, который наследуют его потомки, не переопределяя этот метод. Пусть метод P в ходе работы вызывает метод M, который есть как у родителя, так и у каждого из потомков. Что происходит, когда потомок вызывает метод P, - метод, определенный родителем. Будет ли метод P вызывать метод M родителя или потомка, которого родитель и знать не знает? Результат зависит от того, в каком пространстве имен будет разыскиваться метод M, - в классе, где дается описание метода P, или в классе, связанном с потомком?
Давайте разберем эту ситуацию на нашем примере. Добавим в класс Father соответствующие методы P и M:
def M(self, s):
"""
Метод с этим именем и таким же набором параметров
определен в каждом классе иерархии наследования классов
Метод М - полиморфный метод
"""
return s + " Это метод M класса Father"
def P(self):
"""
Метод P определен только в классе Father
и наследуется всеми потомками.
Вызывает полиморфный метод М
"""
s = "Метод P класса Father вызывает метод M "
res = self.M(s)
return res
Переопределим полиморфный метод M в классе потомке Son:
def M(self, s):
"""
Метод с этим именем и таким же набором параметров
определен в каждом классе иерархии наследования классов
Метод М - полиморфный метод
"""д
return s + " Это метод M класса Son"
Переопределим полиморфный метод M в классе потомке Daughter:
def M(self, s):
"""
Метод с этим именем и таким же набором параметров
определен в каждом классе иерархии наследования классов
Метод М - полиморфный метод
"""
return s + " Это метод M класса Daughter"
А теперь создадим объекты этих классов и вызовем для каждого из них родительский метод P:
def test5():
f = Father('Vladimir', 'Petrov', 'Tver')
s1 = Son('Ilya', 'Petrov', 'Moscow', 31,
place = 'Яндекс', position = 'программист', hobby = 'ловить рыбу')
s2 = Son('Mike', 'Petrov', 'Praga', 27,
place = 'мастерская', position = 'художник', hobby = 'собирать грибы')
gd = Daughter('Anna', 'Petrova', 'Moscow', 4, 'f',
place = 'детский сад', position = 'игры', hobby = 'смотреть мультик')
print('ДЕМОНСТРАЦИЯ ПОЛИМОРФИЗМА')
print('Father: ', f.first_name )
print(f.P())
print('Son: ', s1.first_name )
print(s1.P())
print('Son: ', s2.first_name )
print(s2.P())
print('Daughter: ', gd.first_name )
print(gd.P())
Вот результаты:
Как видите, полиморфизм работает. Когда потомок вызывает метод родителя P, то метод P родителя вызывает метод M потомка. В этом главная мощь инструмента полиморфизма.
В классическом полиморфизме полиморфный метод применяется к объектам, принадлежащим иерархии классов, связанных отношением наследования. Язык Pythonдопускает экзотический вид полиморфизма, когда метод применяется к объектам разных классов, не связанных никаким отношением. Поскольку типы объектов не проверяются, то метод может быть применен к объектам разных классов, если выполняемые операции синтаксически допустимы.
Для демонстрации построю облегченные версии двух классов, задающих рациональные и комплексные числа, ограничившись одним методом (операцией) сложения чисел. Определения этих классов встрою непосредственно в тестирующий модуль Inheritance:
class C(object):
"""Упрощенная версия комплексных чисел """
def __init__ (self, m, n):
self.m = m; self.n = n
def __str__(self):
return str(self.m) + ' + ' + str(self.n) + 'j'
def __add__(self, c):
a = self.m + c.m
b = self.n + c.n
return C(a, b)
class R(object):
"""Упрощенная версия рациональных чисел """
def __init__ (self, m, n):
self.m = m; self.n = n
def __str__(self):
return str(self.m) + '/' + str(self.n)
def __add__(self, c):
a = self.m * c.n + self.n * c.m
b = self.n * c.n
return R(a, b)
Построим теперь тест, в котором создадим объекты этих типов и будем выполнять сложение чисел:
def test6():
c1 = C(2, 5); c2 = C(3, 7)
r1 = R(3, 6); r2 = R(4, 8)
cc = c1 + c2
rr = r1 + r2
cr = c1 + r1
rc = r2 + c2
print('c1 = ', c1)
print('c2 = ', c2)
print('r1 = ', r1)
print('r2 = ', r2)
print('cc = ', cc)
print('rr = ', rr)
print('cr = ', cr)
print('rc = ', rc)
Как видите в тесте выполняем операции сложения, где операнды принадлежат как к одному классу, так и к разным классам. В классических языках сложение операндов разного типа недопустимо и ошибка возникла бы еще на этапе компиляции. В Python, однако, никаких ошибок не возникает и действия выполняются. Вот результаты выполнения этого теста:
Операция сложения объектов спокойно выполняется, поскольку синтаксические проблемы не возникают: Оба объекта имеют поля m и n, поля одного типа, арифметические операции над ними определены. Это позволяет к комплексному числу прибавить рациональное число, интерпретируя число как комплексное. Результат при этом будет комплексным числом. Аналогично, если к рациональному числу прибавить комплексное, то результат будет рациональным числом.
В данном случае такое расширение понятия полиморфизма вряд ли является полезным. Но есть ситуации, когда такой полиморфизм полезен.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.