Создание классов и объектов
В языке программирования Python классы создаются с помощью команды class, за которой следует произвольное имя класса, после которого ставится двоеточие, далее с новой строки и с отступом реализуется тело класса:
class ИмяКласса:
код_тела_класса
2
Если класс является дочерним, то родительские классы перечисляются в круглых скобках после имени класса.
Объект создается путем вызова класса по его имени. При этом после имени класса обязательно ставятся скобки:
NameOfClass()
То есть класс вызывается подобно функции. Однако при этом происходит не выполнение его тела, а создается объект. Поскольку в программном коде важно не потерять ссылку на только что созданный объект, то обычно его связывают с переменной. Поэтому создание объекта чаще всего выглядит так:
variable_name = AnyClass()
В последствии к объекту обращаются через связанную с ним переменную.
Пример «пустого» класса и двух созданных на его основе объектов:
class A:
pass
a = A()
b = A()
2
3
4
5
Оператор pass
не делает ничего. Он может использоваться когда синтаксически требуется присутствие оператора, но от программы не требуется действий.
Класс как модуль
В языке программирования Python класс можно представить подобным модулю. Также как в модуле в нем могут быть свои переменные со значениями и функции. Также как в модуле у класса есть собственное пространство имен, доступ к которым возможен через имя класса:
class B:
n = 5
def adder(v):
return v + B.n
print(B.n) # 5
print(B.adder(4)) # 9
2
3
4
5
6
7
Однако в случае классов используется немного иная терминология. Пусть имена, определенные в классе, называются атрибутами этого класса. В примере имена n и adder – это атрибуты класса B. Атрибуты-переменные часто называют полями или свойствами. Свойством является n. Атрибуты-функции называются методами. Методом в классе B является adder. Количество свойств и методов в классе может быть любым.
Класс как создатель объектов
Приведенный выше класс позволяет создавать объекты, но мы не можем применить к объекту класса метод adder():
l = B()
print(l.n) #5
print(l.adder(100))
2
3
Вывод в консоль:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: adder() takes 1 positional argument but 2 were given
2
3
В сообщении об ошибке говорится, что adder() принимает только один аргумент, а было передано два. Откуда взялся второй аргумент, и кто он такой, если в скобках было указано только одно число 100?
На самом деле классы – это далеко не модули. Они идут дальше модулей и обладают своими особенностями. Класс создает объекты, которые в определенном смысле являются его наследниками. Это значит, что если у объекта нет собственного поля n, то интерпретатор ищет его уровнем выше, то есть в классе. Таким образом, если мы присваиваем объекту поле с таким же именем как в классе, то оно перекрывает, т. е. переопределяет, поле класса:
l.n = 10
print(l.n) # 10
print(B.n) # 5
2
3
Здесь 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
2
3
4
Переменная self связывается с объектом, к которому был применен данный метод, и через эту переменную мы получаем доступ к атрибутам объекта. Когда этот же метод применяется к другому объекту, то self свяжется уже с этим другим объектом, и через эту переменную будут извлекаться только его свойства.
Протестируем обновленный метод:
l = B()
m = B()
l.n = 10
print(l.adder(3)) # 13
print(m.adder(4)) # 9
2
3
4
5
Здесь от класса B создаются два объекта – l и m. Для объекта l заводится собственное поле n. Объект m, за неимением собственного, наследует n от класса B. Можно в этом убедиться, проверив соответствие:
print(m.n is B.n) # True
print(l.n is B.n) # False
2
В методе adder() выражение self.n – это обращение к свойству n, переданного объекта, и не важно, на каком уровне наследования оно будет найдено. Операции is или is not проверяют, не являются ли два объекта на самом деле одним и тем же.
Если метод не принимает объект, к которому применяется, в качестве первого параметра, то такие методы в других языках программирования называются статическими. Они имеют особый синтаксис и могут вызываться как через класс, так и через объект этого класса. В Python все немного по-другому. Для имитации статических методов используется специальный декоратор, после чего метод можно вызывать не только через класс, но и через объект, не передавая сам объект.
Изменение полей объекта
В Python объекту можно не только переопределять поля и методы, унаследованные от класса, также можно добавить новые, которых нет в классе:
l.test = "hi"
print(B.test)
2
В консоль выведет ошибку: «объект В не имеет атрибута „test“»:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'B' has no attribute 'test'
2
3
Код:
print(l.test) # hi
Однако в программировании так делать не принято, потому что тогда объекты одного класса будут отличаться между собой по набору атрибутов. Это затруднит автоматизацию их обработки, внесет в программу хаос.
Поэтому принято присваивать полям, а также получать их значения, путем вызова методов:
class User:
def setName(self, n):
self.name = n
def getName(self):
try:
return self.name
except:
return "No name"
first = User()
second = User()
first.setName("Bob")
print(first.getName()) # Bob
print(second.getName()) # No name
2
3
4
5
6
7
8
9
10
11
12
13
14
Подобные методы в простонародье называют сеттерами геттерами (get – получить) и (set – установить).
Упражнения
Программу в которой класс «Животное» с полями тип и кличка:
class Animal: kind = "" name = "" cat = Animal() cat.kind = "cat" cat.name = "Murzik" print("I like my {} {}!".format(cat.kind, cat.name))
1
2
3
4
5
6
7
8Создайте ещё 3 экземпляра данного класса присвоив им тип и кличку. Выведите значения полей созданного объекта.
Добавим в класс «Животное» поля «Очки Жизней» и методы: «кушать», который увеличивает количество очков жизней на 10; «жизнь», который уменьшает количество очков жизней на переданное методу значение. Добавит метод возвращающий текущее значение жизней или над «хочу есть», если значение жизней меньше или равно 0:
class Animal: kind = "" name = "" hp = 0 def eat(self): self.hp +=10 def live(self, n): self.hp -= n def getHP(self): if self.hp > 0: return self.hp else: return "I am hungry..." cat = Animal() cat.kind = "cat" cat.name = "Murzik" print("I like my {} {}!".format(cat.kind, cat.name)) # I like my cat Murzik! print(cat.getHP()) # I am hungry... cat.eat() print(cat.getHP()) # 10 cat.live(7) print(cat.getHP()) # 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26Создайте еще пару объектов это класса и попробуйте применить к ним написанные методы и понять принцип работы кода.
Напишите программу в которой есть класс автомобиль с полями например: марка, модель, цвет. И создайте 3 экземпляра класса. И выведите значения полей в консоль.
Добавьте к классу автомобиль поле «топливо» и методы «заправить» и «ехать», которые будут принимать параметры пополнять или уменьшать объем топлива машины. Метод ехать должен проверять наличие топлива и если его осталось меньше нуля, то выводиться: «бензин кончился».
Напишите программу по следующему описанию. Есть класс «Воин». От него создаются два экземпляра-юнита. Каждому устанавливается здоровье в 100 очков. В случайном порядке они бьют друг друга. Тот, кто бьет, здоровья не теряет. У того, кого бьют, оно уменьшается на 20 очков от одного удара. После каждого удара надо выводить сообщение, какой юнит атаковал, и сколько у противника осталось здоровья. Как только у кого-то заканчивается ресурс здоровья, программа завершается сообщением о том, кто одержал победу.