В языке программирования 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 "<stdin>", line 1, in <module> 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.
Однако в случае с методами не так все просто, как с полями. В Python по умолчанию один и тот же метод, вызванный от имени класса (например, B.meth()) и от экземпляра этого класса (например, l.meth()), вызывается по-разному. В последнем случае l.meth() невидимо для нас преобразуется в B.meth(l).
Но если метод meth() определен как непринимающий никаких аргументов, а они в него передаются, это приводит к ошибке:
>>> class A: ... def meth(): ... print(1) ... >>> a = A() >>> a.meth() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: meth() takes 0 positional arguments but 1 was given >>> A.meth() 1
Зачем в методы передают экземпляры? Потому что методы обычно для этого и предназначены – для изменения свойств конкретных объектов. А все экземпляры даже одного класса – это разные объекты, со своими свойствами, и метод должен иметь ссылку на конкретный экземпляр, с которым ему придется работать.
Понятно, что передаваемый экземпляр, это объект, к которому применяется метод. Выражение 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 "<stdin>", line 1, in <module> 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 – получить).
Атрибут __dict__
В Python у объектов есть встроенные специальные атрибуты. Мы их не определяем, но они есть. Одним из таких атрибутов объекта является свойство __dict__. Его значением является словарь, в котором ключи – это имена свойств экземпляра, а значения – текущие значения свойств.
>>> class B:
... n = 5
... def adder(self, v):
... return v + self.n
...
>>> w = B()
>>> w.__dict__
{}
>>> w.n = 8
>>> w.__dict__
{'n': 8}
В примере у экземпляра класса B сначала нет собственных атрибутов. Свойство n и метод adder – это атрибуты объекта-класса, а не объекта-экземпляра, созданного от этого класса. Лишь когда мы выполняем присваивание новому полю n экземпляра, у него появляется собственное свойство, что мы наблюдаем через словарь __dict__.
В следующем уроке мы увидим, что свойства экземпляра обычно не назначаются за пределами класса. Это происходит в методах классах путем присваивание через self. Например, self.n = 10.
Атрибут __dict__ используется для просмотра всех текущих свойств объекта. С его помощью можно удалять, добавлять свойства, а также изменять их значения.
>>> w.__dict__['m'] = 100
>>> w.__dict__
{'n': 8, 'm': 100}
>>> w.m
100
Практическая работа
Напишите программу по следующему описанию. Есть класс «Воин». От него создаются два экземпляра-юнита. Каждому устанавливается здоровье в 100 очков. В случайном порядке они бьют друг друга. Тот, кто бьет, здоровья не теряет. У того, кого бьют, оно уменьшается на 20 очков от одного удара. После каждого удара надо выводить сообщение, какой юнит атаковал, и сколько у противника осталось здоровья. Как только у кого-то заканчивается ресурс здоровья, программа завершается сообщением о том, кто одержал победу.
Источник:https://younglinux.info/