Проект для лекции Lecture8.rar.
В классическом объектно-ориентированном языке программа состоит из совокупности классов, связанных соотношениями, главными из которых являются отношения наследования и клиент-поставщик. Классы могут играть две роли - роль модуля и роль типа данных. Класс как модуль является архитектурной единицей в построении программного проекта. Достоинство класса в том, что он может независимо транслироваться, может быть включен в библиотеку классов, присоединяемую к любому программному проекту. Программирование в классах обеспечивает такое важное свойство разработки программных проектов как повторное использование кода без чего немыслимо современное программирование, создающее большие программные проекты.
Не менее важна и вторая роль класса. Будучи типом данных, класс описывает некоторое множество элементов, задавая их свойства и поведение. Элементы этого множества принято называть объектами. Тип данных, определяемый классом, может быть моделью реального типа данных некоторой проблемной области, для решения задач которой и создается программный проект. Но класс может задавать некоторый абстрактный тип данных, проектируемый программистом в интересах эффективной реализации алгоритмов проекта. Важно, что такой класс обладает определенной семантикой. Обычно имена классов дают подсказку о семантике класса: Person, Student, Car, Engine.
В процессе выполнения программы на основе описания класса можно создавать экземпляры класса - объекты этого класса. Выполнение программы можно рассматривать как создание некоторого мира взаимодействующих объектов, реагирующих на события, происходящие в этом мире, инициаторами которых могут быть как сами объекты, так и пользователь, взаимодействующий с объектами через интерфейс программного проекта.
Некоторые классы могут играть единственную роль - роль модуля. Класс, будучи только модулем и не являясь типом данных, не может создавать экземпляры класса, он существует в единственном экземпляре как модуль. Модуль предоставляет свои сервисы объектам других классов.
Язык Python, поддерживая наряду с другими стилями программирования и стиль ООП, позволяет создавать классы и модули, придавая этим понятиям специфику, характерную для этого языка.
При программировании на Pythonмодули, в отличие от классов, являются обязательными элементами. Программа на Python- это совокупность взаимодействующих модулей. Модуль Pythonможет определять класс. Всякий класс Pythonявляется модулем, но не всякий модуль является классом. В чем разница? Когда следует модуль Pythonопределять как класс?
Мы знаем, - что модуль может содержать данные, методы и выполняемый код, представляющий по сути метод main, с которого начинается работа проекта, если модуль назначается главным модулем проекта. На данные и методы формально никакие ограничения не накладываются. В этом случае модуль играет одну роль - архитектурную роль, являясь той строительной единицей из сочетания которых строится проект. Фактически, модуль всегда играет и вторую роль - семантическую. Модуль math предоставляет математические сервисы, модуль os - сервисы, связанные с операционной системой, random - дает возможность генерировать случайные числа. Даже для самых простых проектов рекомендуется создавать как минимум два модуля: один отвечает за интерфейс с пользователем, другой - предоставляет сервисы бизнес-логики, позволяя решать поставленную задачу. Так что в реальном хорошем программировании модули всегда играют не только архитектурную, но и семантическую роль. Но семантика семантике рознь. Вышеупомянутые
модули: math, os, random - это сервисные модули. Они предоставляют сервисы, доступные при импортировании другим модулям. Однако зачастую требуется семантика другого рода - модуль должен описывать некоторое множество объектов - их свойства и поведение. Модули, которые реализуют такую семантику, являются классами. Класс - это модуль, задающий тип данных. Экземпляры этого типа называются объектами. Встроенные типы данных - числа, строки, списки, множества - задаются модулями, представляющими классы. Но, конечно же, моделируя конкретную проблемную область, программист создает собственные классы, определяющие типы данных, характерные для этой проблемной области.
Программирование на Python, когда создается проект, модули которого являются классами, работа которого связана с созданием объектов разных типов, их взаимодействием, является объектно-ориентированным программированием. Сервисные модули и модули, задающие классы, прекрасно сочетаются в одном проекте.
В соответствие с терминологией, принятой в ООП, данные модуля, определенные на глобальном уровне, называются полями класса, когда модуль задает класс. Методы этого модуля называются методами класса. Поля класса определяют свойства объектов, методы - их поведение.
Класс Pythonявляется объектом таким же, как и другие объекты, например, функции. Объявление класса появляется в модуле. Как правило, такой модуль содержит единственный объект - класс.
Язык позволяет дать описание класса в традиционном стиле - с полями, методами, конструктором, деструктором, методами - свойствами, заданием операторов. Продемонстрируем такой стиль работы на примере простого и понятного класса, задающего рациональные числа. Напомню, в стандартной библиотеке есть модуль fractions, содержащий класс Fractions, который задает тип данных - рациональные числа. Построим аналог этого класса.
Как обычно, для новой лекции я создал в Visual Studio Решение Lecture8, содержащее новый проект с именем Rational. По умолчанию проект содержит модуль (файл) с именем Rational.py. В этот проект я добавил новый элемент - класс Pythonс именем classRational. В результате в проект добавился второй модуль, содержащий заготовку класса:
class classRational(object):
"""description of class"""
Имя модуля, содержащего класс, совпадает с именем класса. Синтаксически, как видите, объявление класса начинается с ключевого слова "class", затем следует <имя класса>, в круглых скобках задается список родителей класса. В Pythonвсе классы имеют в качестве родителя класс "object". Этот класс указывается в заголовке для классов, непосредственно наследуемых от класса object. Если же класс имеет родителей, отличных от object, то повторно он не задается, поскольку наследуется в соответствии с правилами наследования в языке Python, о которых предстоит подробное обсуждение.№ Заканчивается заголовок класса, как обычно двоеточием, после чего следует задавать заголовочный комментарий, описывающий суть класса.
Добавим в наш класс содержательный комментарий и введем поля класса:
"""
Класс Rational.
/// определяет новый тип данных - рациональные числа и основные
/// операции над ними - сложение, умножение, вычитание и деление.
/// Рациональное число задается парой целых чисел (m,n) и изображается,
/// обычно в виде дроби m/n. Число m называется числителем, а
/// n - знаменателем. Для каждого рационального числа существует
/// множество его представлений, например 1/2, 2/4, 3/6, 6/12 -
/// задают одно и тоже рациональное число. Среди всех представлений
/// можно выделить то, в котором числитель и знаменатель взаимно
/// несократимы. Такой представитель будет храниться в полях класса.
/// Операции над рациональными числами определяются естественным
/// для математиков образом.
"""
#поля класса
# int m - числитель
# int n - знаменатель
Заметьте, приведенные выше строки - это не определение полей, а комментарий. В традиционных объектно-ориентированных языках свойства объектов класса определяются в полях класса, задаваемых на уровне класса. В языке Pythonэто не так. На уровне класса задаются свойства, присущие классу, общие для всех объектов. Свойства же, присущие объектам класса, определяются в конструкторе класса.
Зададим конструктор класса. В традиционном объектно-ориентированном стиле каждый класс должен иметь специальный метод, называемый конструктором. Задача этого метода - создание объекта данного класса. При создании каждый объект получает полный набор свойств, определяемых полями класса. Значения этих свойств определяются параметрами, передаваемыми конструктору в момент его вызова. Все объекты, создаваемые конструктором, получают один и тот же набор свойств, но значения этих свойств различаются.
В языке Pythonподход к определению конструктора класса отличается от традиционного. Подробнее об этих особенностях поговорим чуть позже, а сейчас же отметим, что для задания конструктора класса необходимо переопределить наследуемый метод, имя которого фиксировано: __init__.
Рациональные числа - это простые объекты, у них всего два свойства - числитель и знаменатель рационального числа. Однако конструктор этого класса не прост, поскольку значения свойств могут не совпадать со значениями, заданными в момент вызова. Конструктор строит рациональное число, эквивалентное заданному числу, но с взаимно несократимыми значениями числителя и знаменателя, так что число (2, 4) будет храниться как эквивалентное число (1, 2).
Данные, переданные конструктору, приводятся к несократимой дроби, прежде чем они станут полями класса. Вот текст конструктора, сопровождаемый заголовочным комментарием:
def __init__(self, m, n):
"""
Конструктор класса. Создает рациональное число
m/n со взаимно несократимыми
числителем и знаменателем.
Параметры m, n - целые числа.
Значения по умолчанию задают
рациональный ноль.
"""
#приведение знака
if n < 0:
m, n = -m, - n
#приведение к несократимой дроби
a, b = m, n
if a < 0:
a = -a
if a < b:
a, b = b, a
while b != 0:
a, b = b, a % b
d = a
self.m = m // d
self.n = n // d
Важное замечание. В конструкторе класса нельзя вызывать функции, объявленные в классе, поскольку эти функции недоступны на момент создания экземпляра класса. Поэтому вычисление наибольшего общего делителя m и n непосредственно встроено в текст конструктора.
Кроме конструктора объектов класса в любом классе следует переопределить наследуемую функцию __str__, возвращающая строку, описывающую объект. Функция print в этом случае будет выдавать актуальную информацию, характеризующую объект. В данном случае определение, а точнее переопределение этой функции, наследуемой от класса object, выглядит достаточно просто:
def __str__(self):
"""
Переопределение встроенной функции __str__
"""
return str(self.m) + "/" + str(self.n)
На данном этапе уже можно создавать объекты класса Rational и выводить их на печать.
Модуль Rational будет главным модулем нашего проекта. Он будет играть роль тестера - модуля, содержащего наборы тестов, проверяющих корректность работы создаваемых методов класса. Структура модуля тестера достаточно проста. Выполняемый код (метод main) содержит вызовы тестов. Каждый метод модуля представляет отдельный тест.
Метод main модуля Rational начинается с импорта класса и вызова первого теста:
from classRational import classRational test1()
Определим первый тест для работы с объектами класса Rational:
def test1():
"""
Создание 4-х глобальных объектов класса Rational
Печать объектов
"""
global r1, r2, r3, r4
r1 = classRational(3, 5)
print("r1 = ", r1)
r2 = classRational(6, 10)
print("r2 = ", r2)
r3 = classRational(3, -5)
print("r3 = ", r3)
r4 = classRational(0, 1)
print("r4 = ", r4)
Вот результаты работы:
Заметьте, переменные r1 - r4 объявлены как глобальные для того, чтобы они могли использоваться в других тестах. Построим простой тест, в котором выполняются некоторые действия над уже созданными переменными:
def test2():
r1 = r3
print("r1 = ", r1 )
Добавим вызов test2() в main и запустим тестер на выполнение двух тестов. Вот результаты этой работы:
Рассмотрим некоторые особенности Python при работе с полями класса и конструктором.
Одним из важных механизмов ООП является перегрузка методов класса. Методы класса называются перегруженными, если они имеют одно и тоже имя. Чтобы перегрузка работала корректно, перегруженные методы должны иметь различную сигнатуру. Под сигнатурой понимается список типов формальных параметров метода. Это означает, что либо перегруженные методы имеют различное число параметров, либо при одинаковом числе параметров их типы должны отличаться. Перегрузка - это весьма полезный механизм.
В Python перегрузка методов класса не допускается. Причина понятна. Формальные параметры метода не имеют типов, поэтому понятие сигнатуры в Python лишено смысла. Какие же есть приемы в PytPythonhon, позволяющие моделировать механизм перегрузки. Вначале рассмотрим ситуацию с конструкторами, а о других методах поговорим чуть позже. В ООП хорошим стилем при определении класса является задание в классе нескольких конструкторов, в том числе задание конструктора без параметров. Считается, что клиент, создающий объекты класса, может обладать разной информацией о свойствах того или иного объекта, поэтому необходимы разные конструкторы, в которых передается только часть параметров, задающие те свойства объекта, которые известны на момент создания. Остальные свойства соответствующий конструктор задает значениями по умолчанию. Если ничего о свойствах объекта не известно в момент создания, то вызывается конструктор без параметров, который создает некоторый объект со свойствами, заданными по умолчанию.
В Pythonконструктор только один. Но есть возможность для части параметров или для всех параметров задать значения по умолчанию при определении конструктора. Этот прием, хотя и не в полной мере, позволяет решить проблему, связанную с отсутствием перегрузки конструкторов. Давайте заменим наш конструктор класса конструктором со следующим заголовком:
def __init__(self, m = 0, n = 1):
Заметьте, тело конструктора при этой замене не меняется. Построим тест для этого конструктора, в котором при создании объектов, конструктору передается разное число параметров:
def test3():
r1 = classRational()
print("r1 = ", r1 )
r2 = classRational(m = 7)
print("r2 = ", r2 )
r3 = classRational(m = 6, n = 4)
print("r3 = ", r3 )
r4 = classRational(m = 1, n = 1)
print("r4 = ", r4 )
Вот результаты работы теста с этим конструктором класса:
Объект r1- рациональный нуль, можно считать, создан конструктором без параметров. Объект r2 - рациональное целое, можно считать, создан конструктором с одним параметром. Объекты r3 и r4 созданы конструктором, имеющим полный набор параметров. Как видите, один конструктор с параметрами по умолчанию, позволяет моделировать работу нескольких конструкторов.
Возможно, единственное неудобство, это необходимость в момент вызова задавать имена параметров. Но можно это считать и достоинством конструктора. Тем не менее, в дальнейших экспериментах я вернусь к первой форме конструктора.
Поговорим теперь об особенностях задания полей класса. В классических языках ООП мы явно задаем поля класса, указывая их типы, с возможной инициализацией значений. В Python, естественно, типы полей не задаются, поэтому инициализация необходима, чтобы создать объекты, представляющие поля, на глобальном уровне класса. Но при программировании на Pythonполя, задающие свойства объектов не объявляются на глобальном уровне, а создаются на локальном уровне в процессе вызова конструктора класса. При рассмотрении модульного программирования мы подробно обсуждали возможность создания глобальных объектов на локальном уровне.
Разумным вариантом является задание полей в закомментированном виде с указанием типа полей, как некоторый аналог аннотаций формальных параметров метода. Вот пример такого объявления:
#поля класса # int m - числитель # int n - знаменатель
Я закомментировал подобным образом объявление полей класса и построил тест, демонстрирующий, что объявление полей класса не обязательно, поскольку они создаются конструктором:
def test4():
r1 = classRational(5, 15)
print('r1 = ', r1)
print ('r1.m = ', r1.m)
print ('r1.n = ', r1.n)
r1.m = 6
print('r1 = ', r1)
Тест работает, вот его результаты:
В Pythonнельзя объявить поля класса с модификатором private, закрывая клиентам класса доступ к полям. К полям класса возможен доступ как на чтение, так и на запись. Это нарушает один из важных принципов ООП - клиент не должен иметь прямой доступ к полям класса, доступ полагается реализовывать через методы класса.
Для клиента нашего класса рациональное число должно выглядеть как неделимое целое. Наш пример показывает, что клиент может изменить одно из полей, нарушив тем самым принцип работы нашего класса, предполагающий, что рациональное число задано своим представителем с взаимно несократимыми числителем и знаменателем.
Еще одна рекомендация к проектированию класса. Следует создавать методы, их иногда называют методами - свойствами, которые реализуют ту или иную стратегию доступа к полям, - "только для чтения", "только для записи", "запись при первом обращении" и другие возможные варианты. В заголовочный комментарий класса следует включать обращение к клиентам класса с предупреждением не пользоваться прямым доступом к полям класса.
Проект для лекции Lecture8.rar.
В классическом объектно-ориентированном языке программа состоит из совокупности классов, связанных соотношениями, главными из которых являются отношения наследования и клиент-поставщик. Классы могут играть две роли - роль модуля и роль типа данных. Класс как модуль является архитектурной единицей в построении программного проекта. Достоинство класса в том, что он может независимо транслироваться, может быть включен в библиотеку классов, присоединяемую к любому программному проекту. Программирование в классах обеспечивает такое важное свойство разработки программных проектов как повторное использование кода без чего немыслимо современное программирование, создающее большие программные проекты.
Не менее важна и вторая роль класса. Будучи типом данных, класс описывает некоторое множество элементов, задавая их свойства и поведение. Элементы этого множества принято называть объектами. Тип данных, определяемый классом, может быть моделью реального типа данных некоторой проблемной области, для решения задач которой и создается программный проект. Но класс может задавать некоторый абстрактный тип данных, проектируемый программистом в интересах эффективной реализации алгоритмов проекта. Важно, что такой класс обладает определенной семантикой. Обычно имена классов дают подсказку о семантике класса: Person, Student, Car, Engine.
В процессе выполнения программы на основе описания класса можно создавать экземпляры класса - объекты этого класса. Выполнение программы можно рассматривать как создание некоторого мира взаимодействующих объектов, реагирующих на события, происходящие в этом мире, инициаторами которых могут быть как сами объекты, так и пользователь, взаимодействующий с объектами через интерфейс программного проекта.
Некоторые классы могут играть единственную роль - роль модуля. Класс, будучи только модулем и не являясь типом данных, не может создавать экземпляры класса, он существует в единственном экземпляре как модуль. Модуль предоставляет свои сервисы объектам других классов.
Язык Python, поддерживая наряду с другими стилями программирования и стиль ООП, позволяет создавать классы и модули, придавая этим понятиям специфику, характерную для этого языка.
При программировании на Pythonмодули, в отличие от классов, являются обязательными элементами. Программа на Python- это совокупность взаимодействующих модулей. Модуль Pythonможет определять класс. Всякий класс Pythonявляется модулем, но не всякий модуль является классом. В чем разница? Когда следует модуль Pythonопределять как класс?
Мы знаем, - что модуль может содержать данные, методы и выполняемый код, представляющий по сути метод main, с которого начинается работа проекта, если модуль назначается главным модулем проекта. На данные и методы формально никакие ограничения не накладываются. В этом случае модуль играет одну роль - архитектурную роль, являясь той строительной единицей из сочетания которых строится проект. Фактически, модуль всегда играет и вторую роль - семантическую. Модуль math предоставляет математические сервисы, модуль os - сервисы, связанные с операционной системой, random - дает возможность генерировать случайные числа. Даже для самых простых проектов рекомендуется создавать как минимум два модуля: один отвечает за интерфейс с пользователем, другой - предоставляет сервисы бизнес-логики, позволяя решать поставленную задачу. Так что в реальном хорошем программировании модули всегда играют не только архитектурную, но и семантическую роль. Но семантика семантике рознь. Вышеупомянутые
модули: math, os, random - это сервисные модули. Они предоставляют сервисы, доступные при импортировании другим модулям. Однако зачастую требуется семантика другого рода - модуль должен описывать некоторое множество объектов - их свойства и поведение. Модули, которые реализуют такую семантику, являются классами. Класс - это модуль, задающий тип данных. Экземпляры этого типа называются объектами. Встроенные типы данных - числа, строки, списки, множества - задаются модулями, представляющими классы. Но, конечно же, моделируя конкретную проблемную область, программист создает собственные классы, определяющие типы данных, характерные для этой проблемной области.
Программирование на Python, когда создается проект, модули которого являются классами, работа которого связана с созданием объектов разных типов, их взаимодействием, является объектно-ориентированным программированием. Сервисные модули и модули, задающие классы, прекрасно сочетаются в одном проекте.
В соответствие с терминологией, принятой в ООП, данные модуля, определенные на глобальном уровне, называются полями класса, когда модуль задает класс. Методы этого модуля называются методами класса. Поля класса определяют свойства объектов, методы - их поведение.
Класс Pythonявляется объектом таким же, как и другие объекты, например, функции. Объявление класса появляется в модуле. Как правило, такой модуль содержит единственный объект - класс.
Язык позволяет дать описание класса в традиционном стиле - с полями, методами, конструктором, деструктором, методами - свойствами, заданием операторов. Продемонстрируем такой стиль работы на примере простого и понятного класса, задающего рациональные числа. Напомню, в стандартной библиотеке есть модуль fractions, содержащий класс Fractions, который задает тип данных - рациональные числа. Построим аналог этого класса.
Как обычно, для новой лекции я создал в Visual Studio Решение Lecture8, содержащее новый проект с именем Rational. По умолчанию проект содержит модуль (файл) с именем Rational.py. В этот проект я добавил новый элемент - класс Pythonс именем classRational. В результате в проект добавился второй модуль, содержащий заготовку класса:
class classRational(object):
"""description of class"""
Имя модуля, содержащего класс, совпадает с именем класса. Синтаксически, как видите, объявление класса начинается с ключевого слова "class", затем следует <имя класса>, в круглых скобках задается список родителей класса. В Pythonвсе классы имеют в качестве родителя класс "object". Этот класс указывается в заголовке для классов, непосредственно наследуемых от класса object. Если же класс имеет родителей, отличных от object, то повторно он не задается, поскольку наследуется в соответствии с правилами наследования в языке Python, о которых предстоит подробное обсуждение.№ Заканчивается заголовок класса, как обычно двоеточием, после чего следует задавать заголовочный комментарий, описывающий суть класса.
Добавим в наш класс содержательный комментарий и введем поля класса:
"""
Класс Rational.
/// определяет новый тип данных - рациональные числа и основные
/// операции над ними - сложение, умножение, вычитание и деление.
/// Рациональное число задается парой целых чисел (m,n) и изображается,
/// обычно в виде дроби m/n. Число m называется числителем, а
/// n - знаменателем. Для каждого рационального числа существует
/// множество его представлений, например 1/2, 2/4, 3/6, 6/12 -
/// задают одно и тоже рациональное число. Среди всех представлений
/// можно выделить то, в котором числитель и знаменатель взаимно
/// несократимы. Такой представитель будет храниться в полях класса.
/// Операции над рациональными числами определяются естественным
/// для математиков образом.
"""
#поля класса
# int m - числитель
# int n - знаменатель
Заметьте, приведенные выше строки - это не определение полей, а комментарий. В традиционных объектно-ориентированных языках свойства объектов класса определяются в полях класса, задаваемых на уровне класса. В языке Pythonэто не так. На уровне класса задаются свойства, присущие классу, общие для всех объектов. Свойства же, присущие объектам класса, определяются в конструкторе класса.
Зададим конструктор класса. В традиционном объектно-ориентированном стиле каждый класс должен иметь специальный метод, называемый конструктором. Задача этого метода - создание объекта данного класса. При создании каждый объект получает полный набор свойств, определяемых полями класса. Значения этих свойств определяются параметрами, передаваемыми конструктору в момент его вызова. Все объекты, создаваемые конструктором, получают один и тот же набор свойств, но значения этих свойств различаются.
В языке Pythonподход к определению конструктора класса отличается от традиционного. Подробнее об этих особенностях поговорим чуть позже, а сейчас же отметим, что для задания конструктора класса необходимо переопределить наследуемый метод, имя которого фиксировано: __init__.
Рациональные числа - это простые объекты, у них всего два свойства - числитель и знаменатель рационального числа. Однако конструктор этого класса не прост, поскольку значения свойств могут не совпадать со значениями, заданными в момент вызова. Конструктор строит рациональное число, эквивалентное заданному числу, но с взаимно несократимыми значениями числителя и знаменателя, так что число (2, 4) будет храниться как эквивалентное число (1, 2).
Данные, переданные конструктору, приводятся к несократимой дроби, прежде чем они станут полями класса. Вот текст конструктора, сопровождаемый заголовочным комментарием:
def __init__(self, m, n):
"""
Конструктор класса. Создает рациональное число
m/n со взаимно несократимыми
числителем и знаменателем.
Параметры m, n - целые числа.
Значения по умолчанию задают
рациональный ноль.
"""
#приведение знака
if n < 0:
m, n = -m, - n
#приведение к несократимой дроби
a, b = m, n
if a < 0:
a = -a
if a < b:
a, b = b, a
while b != 0:
a, b = b, a % b
d = a
self.m = m // d
self.n = n // d
Важное замечание. В конструкторе класса нельзя вызывать функции, объявленные в классе, поскольку эти функции недоступны на момент создания экземпляра класса. Поэтому вычисление наибольшего общего делителя m и n непосредственно встроено в текст конструктора.
Кроме конструктора объектов класса в любом классе следует переопределить наследуемую функцию __str__, возвращающая строку, описывающую объект. Функция print в этом случае будет выдавать актуальную информацию, характеризующую объект. В данном случае определение, а точнее переопределение этой функции, наследуемой от класса object, выглядит достаточно просто:
def __str__(self):
"""
Переопределение встроенной функции __str__
"""
return str(self.m) + "/" + str(self.n)
На данном этапе уже можно создавать объекты класса Rational и выводить их на печать.
Модуль Rational будет главным модулем нашего проекта. Он будет играть роль тестера - модуля, содержащего наборы тестов, проверяющих корректность работы создаваемых методов класса. Структура модуля тестера достаточно проста. Выполняемый код (метод main) содержит вызовы тестов. Каждый метод модуля представляет отдельный тест.
Метод main модуля Rational начинается с импорта класса и вызова первого теста:
from classRational import classRational test1()
Определим первый тест для работы с объектами класса Rational:
def test1():
"""
Создание 4-х глобальных объектов класса Rational
Печать объектов
"""
global r1, r2, r3, r4
r1 = classRational(3, 5)
print("r1 = ", r1)
r2 = classRational(6, 10)
print("r2 = ", r2)
r3 = classRational(3, -5)
print("r3 = ", r3)
r4 = classRational(0, 1)
print("r4 = ", r4)
Вот результаты работы:
Заметьте, переменные r1 - r4 объявлены как глобальные для того, чтобы они могли использоваться в других тестах. Построим простой тест, в котором выполняются некоторые действия над уже созданными переменными:
def test2():
r1 = r3
print("r1 = ", r1 )
Добавим вызов test2() в main и запустим тестер на выполнение двух тестов. Вот результаты этой работы:
Рассмотрим некоторые особенности Python при работе с полями класса и конструктором.
Одним из важных механизмов ООП является перегрузка методов класса. Методы класса называются перегруженными, если они имеют одно и тоже имя. Чтобы перегрузка работала корректно, перегруженные методы должны иметь различную сигнатуру. Под сигнатурой понимается список типов формальных параметров метода. Это означает, что либо перегруженные методы имеют различное число параметров, либо при одинаковом числе параметров их типы должны отличаться. Перегрузка - это весьма полезный механизм.
В Python перегрузка методов класса не допускается. Причина понятна. Формальные параметры метода не имеют типов, поэтому понятие сигнатуры в Python лишено смысла. Какие же есть приемы в PytPythonhon, позволяющие моделировать механизм перегрузки. Вначале рассмотрим ситуацию с конструкторами, а о других методах поговорим чуть позже. В ООП хорошим стилем при определении класса является задание в классе нескольких конструкторов, в том числе задание конструктора без параметров. Считается, что клиент, создающий объекты класса, может обладать разной информацией о свойствах того или иного объекта, поэтому необходимы разные конструкторы, в которых передается только часть параметров, задающие те свойства объекта, которые известны на момент создания. Остальные свойства соответствующий конструктор задает значениями по умолчанию. Если ничего о свойствах объекта не известно в момент создания, то вызывается конструктор без параметров, который создает некоторый объект со свойствами, заданными по умолчанию.
В Pythonконструктор только один. Но есть возможность для части параметров или для всех параметров задать значения по умолчанию при определении конструктора. Этот прием, хотя и не в полной мере, позволяет решить проблему, связанную с отсутствием перегрузки конструкторов. Давайте заменим наш конструктор класса конструктором со следующим заголовком:
def __init__(self, m = 0, n = 1):
Заметьте, тело конструктора при этой замене не меняется. Построим тест для этого конструктора, в котором при создании объектов, конструктору передается разное число параметров:
def test3():
r1 = classRational()
print("r1 = ", r1 )
r2 = classRational(m = 7)
print("r2 = ", r2 )
r3 = classRational(m = 6, n = 4)
print("r3 = ", r3 )
r4 = classRational(m = 1, n = 1)
print("r4 = ", r4 )
Вот результаты работы теста с этим конструктором класса:
Объект r1- рациональный нуль, можно считать, создан конструктором без параметров. Объект r2 - рациональное целое, можно считать, создан конструктором с одним параметром. Объекты r3 и r4 созданы конструктором, имеющим полный набор параметров. Как видите, один конструктор с параметрами по умолчанию, позволяет моделировать работу нескольких конструкторов.
Возможно, единственное неудобство, это необходимость в момент вызова задавать имена параметров. Но можно это считать и достоинством конструктора. Тем не менее, в дальнейших экспериментах я вернусь к первой форме конструктора.
Поговорим теперь об особенностях задания полей класса. В классических языках ООП мы явно задаем поля класса, указывая их типы, с возможной инициализацией значений. В Python, естественно, типы полей не задаются, поэтому инициализация необходима, чтобы создать объекты, представляющие поля, на глобальном уровне класса. Но при программировании на Pythonполя, задающие свойства объектов не объявляются на глобальном уровне, а создаются на локальном уровне в процессе вызова конструктора класса. При рассмотрении модульного программирования мы подробно обсуждали возможность создания глобальных объектов на локальном уровне.
Разумным вариантом является задание полей в закомментированном виде с указанием типа полей, как некоторый аналог аннотаций формальных параметров метода. Вот пример такого объявления:
#поля класса # int m - числитель # int n - знаменатель
Я закомментировал подобным образом объявление полей класса и построил тест, демонстрирующий, что объявление полей класса не обязательно, поскольку они создаются конструктором:
def test4():
r1 = classRational(5, 15)
print('r1 = ', r1)
print ('r1.m = ', r1.m)
print ('r1.n = ', r1.n)
r1.m = 6
print('r1 = ', r1)
Тест работает, вот его результаты:
В Pythonнельзя объявить поля класса с модификатором private, закрывая клиентам класса доступ к полям. К полям класса возможен доступ как на чтение, так и на запись. Это нарушает один из важных принципов ООП - клиент не должен иметь прямой доступ к полям класса, доступ полагается реализовывать через методы класса.
Для клиента нашего класса рациональное число должно выглядеть как неделимое целое. Наш пример показывает, что клиент может изменить одно из полей, нарушив тем самым принцип работы нашего класса, предполагающий, что рациональное число задано своим представителем с взаимно несократимыми числителем и знаменателем.
Еще одна рекомендация к проектированию класса. Следует создавать методы, их иногда называют методами - свойствами, которые реализуют ту или иную стратегию доступа к полям, - "только для чтения", "только для записи", "запись при первом обращении" и другие возможные варианты. В заголовочный комментарий класса следует включать обращение к клиентам класса с предупреждением не пользоваться прямым доступом к полям класса.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.