Полиморфизм – это множество форм. Однако в понятиях ООП имеется в виду скорее обратное. Объекты разных классов, с разной внутренней реализацией, то есть программным кодом, могут иметь одинаковые интерфейсы. Например, для чисел есть операция сложения, обозначаемая знаком +. Однако мы можем определить класс, объекты которого также будут поддерживать операцию, обозначаемую этим знаком. Но это вовсе не значит, что объекты должны быть числами, и будет получаться какая-то сумма. Операция + для объектов нашего класса может значить что-то иное. Но интерфейс, в данном случае это знак +, у чисел и нашего класса будет одинаков. Полиморфность же проявляется во внутренней реализации и результате операции.
Вы уже сталкивались с полиморфизмом операции +. Для чисел она обозначает сложение, а для строк – конкатенацию. Внутренняя реализация кода для этой операции у чисел отличается от реализации таковой для строк.
Создание классов и объектов
В языке программирования Python классы создаются с помощью инструкции class, за которой следует произвольное имя класса, после которого ставится двоеточие, далее с новой строки и с отступом реализуется тело класса:
class ИмяКласса:
код_тела_класса
Если класс является дочерним, то родительские классы перечисляются в круглых скобках после имени класса.
Объект создается путем вызова класса по его имени. При этом после имени класса обязательно ставятся скобки:
ИмяКласса()
То есть класс вызывается подобно функции. Однако при этом происходит не выполнение его тела, а создается объект. Поскольку в программном коде важно не потерять ссылку на только что созданный объект, то обычно его связывают с переменной. Поэтому создание объекта чаще всего выглядит так:
имя_переменной = ИмяКласса()
В последствии к объекту обращаются через связанную с ним переменную.
Пример "пустого" класса и двух созданных на его основе объектов:
>>> class A:
... pass
...
>>> a = A()
>>> b = A()
Класс как модуль
В языке программирования Python класс можно представить подобным модулю. Также как в модуле в нем могут быть свои переменные со значениями и функции. Также как в модуле у класса есть собственное пространство имен, доступ к которым возможен через имя класса:
>>> class B:
... n = 5
... def adder(v):
... return v + B.n
...
>>> B.n
5
>>> B.adder(4)
9
Однако в случае классов используется немного иная терминология. Пусть имена, определенные в классе, называются атрибутами этого класса. В примере имена n и adder – это атрибуты класса B. Атрибуты-переменные часто называют полями или свойствами. Свойством является n. Атрибуты-функции называются методами. Методом в классе B является adder. Количество свойств и методов в классе может быть любым.
Класс как создатель объектов
Приведенный выше класс позволяет создавать объекты, но мы не можем применить к объекту метод adder():
>>> l = B()
>>> l.n
5
>>> l.adder(100)
Traceback (most recent call last):
File "", line 1, in
TypeError: adder() takes 1 positional argument but 2 were given
В сообщении об ошибке говорится, что adder() принимает только один аргумент, а было передано два. Откуда взялся второй аргумент, и кто он такой, если в скобках было указано только одно число 100?
На самом деле классы – это далеко не модули. Они идут дальше модулей и обладают своими особенностями. Класс создает объекты, которые в определенном смысле являются его наследниками. Это значит, что если у объекта нет собственного поля n, то интерпретатор ищет его уровнем выше, то есть в классе. Таким образом, если мы присваиваем объекту поле с таким же именем как в классе, то оно перекрывает, т. е. переопределяет, поле класса:
>>> l.n = 10
>>> l.n
10
>>> B.n
5
Здесь l.n и B.n – это разные переменные. Первая находится в пространстве имен объекта l. Вторая – в пространстве класса B. Если бы мы не добавили поле n к объекту l, то интерпретатор бы поднялся выше по дереву наследования и пришел бы в класс, где бы и нашел это поле.
Что касается методов, то они также наследуются объектами класса. В данном случае у объекта l нет своего собственного метода adder, значит, он ищется в классе B. Однако от класса B может быть порождено множество объектов. Методы же чаще всего предназначаются для обработки объектов. Таким образом, когда вызывается метод, в него надо передать конкретный объект, который он будет обрабатывать.
Понятно, что передаваемый экземпляр, это объект, к которому применяется метод. Выражение l.adder(100) выполняется интерпретатором следующим образом:
Ищу атрибут adder() у объекта l. Не нахожу.
Тогда иду искать в класс B, так как он создал объект l.
Здесь нахожу искомый метод. Передаю ему объект, к которому этот метод надо применить, и аргумент, указанный в скобках.
Другими словами, выражение l.adder(100) преобразуется в выражение B.adder(l, 100).
Таким образом, интерпретатор попытался передать в метод adder() класса B два параметра – объект l и число 100. Но мы запрограммировали метод adder() так, что он принимает только один параметр. В Python, да и многих других языках, определения методов не предполагают принятие объекта как само собой подразумеваемое. Принимаемый объект надо указывать явно.
По соглашению в Python для ссылки на объект используется имя self. Вот так должен выглядеть метод adder(), если мы планируем вызывать его через объекты:
>>> class B:
... n = 5
... def adder(self, v):
... return v + self.n
...
Переменная self связывается с объектом, к которому был применен данный метод, и через эту переменную мы получаем доступ к атрибутам объекта. Когда этот же метод применяется к другому объекту, то self свяжется уже с этим другим объектом, и через эту переменную будут извлекаться только его свойства.
Протестируем обновленный метод:
>>> l = B()
>>> m = B()
>>> l.n = 10
>>> l.adder(3)
13
>>> m.adder(4)
9
Здесь от класса B создаются два объекта – l и m. Для объекта l заводится собственное поле n. Объект m, за неимением собственного, наследует n от класса B. Можно в этом убедиться, проверив соответствие:
>>> m.n is B.n
True
>>> l.n is B.n
False
В методе adder() выражение self.n – это обращение к свойству n, переданного объекта, и не важно, на каком уровне наследования оно будет найдено.
Если метод не принимает объект, к которому применяется, в качестве первого параметра, то такие методы в других языках программирования называются статическими. Они имеют особый синтаксис и могут вызываться как через класс, так и через объект этого класса. В Python все немного по-другому. Для имитации статических методов используется специальный декоратор, после чего метод можно вызывать не только через класс, но и через объект, не передавая сам объект.
Изменение полей объекта
В Python объекту можно не только переопределять поля и методы, унаследованные от класса, также можно добавить новые, которых нет в классе:
>>> l.test = "hi"
>>> B.test
Traceback (most recent call last):
File "", line 1, in
AttributeError: type object 'B' has no attribute 'test'
>>> l.test
'hi'
Однако в программировании так делать не принято, потому что тогда объекты одного класса будут отличаться между собой по набору атрибутов. Это затруднит автоматизацию их обработки, внесет в программу хаос.
Поэтому принято присваивать полям, а также получать их значения, путем вызова методов:
>>> class User:
... def setName(self, n):
... self.name = n
... def getName(self):
... try:
... return self.name
... except:
... print("No name")
...
>>> first = User()
>>> second = User()
>>> first.setName("Bob")
>>> first.getName()
'Bob'
>>> second.getName()
No name
Подобные методы в простонародье называют сеттерами (set – установить) и геттерами (get – получить).
Лекция №14. Текстовый и графический режимы. Графичесике координаты. Переход между текстовыми и графическими режимами. Принципы программирования графики. Инициализация графического режима
Достарыңызбен бөлісу: |