Создание классов в 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?

Классы имеют больше возможностей, нежели модули. Они создают объекты, которые наследуют часть их свойств. Например, если объекту не хватает какого-то поля, интерпретатор будет искать его в классе. Это значит, что если объект обозначает поле с тем же именем, как в классе, оно заменяет поле класса:

>>> l.n = 10
>>> l.n
10
>>> B.n
5

Здесь l.n и B.n – разные переменные: первая существует в пространстве имен объекта l, а вторая в пространстве класса B. Если бы у объекта не было поля n, интерпретатор бы обратился к ему у класса.

Методы также наследуются объектами от класса. В случае отсутствия у l метода adder, он ищется в классе B. Поскольку методы часто нужны для работы с объектами, метод должен принимать конкретный объект в качестве параметра.

Выражение l.adder(100) интерпретируется интерпретатором так:

  1. Ищу метод adder() у объекта l. Не нахожу.
  2. Воспользуюсь методом из класса B, который создал объект l.
  3. Вызову метод, передав в него объект и аргумент, указанный в скобках.

Таким образом, выражение l.adder(100) преобразуется в B.adder(l, 100).

Python попытается передать методам 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 пользуется значением из класса 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 – получить) среди программистов.

Практическая работа

Создайте программу по следующему описанию. Существует класс "Воин". От него создаются два объекта – юниты. Каждому из них устанавливается здоровье в 100 очков. В случайном порядке они атакуют друг друга. Атакующий не теряет здоровья, а у атакованного здоровье уменьшается на 20 очков от каждого удара. После каждой атаки выводите сообщение о том, кто атаковал, и сколько здоровья осталось у противника. Когда здоровье одного из них достигает нуля, программа заканчивается сообщением о победителе.

Вопросы для самопроверки:

  1. Как создать класс в Python?
  2. Как создать объект в Python и сохранить ссылку на него?
  3. Какой параметр обычно используется в Python для ссылки на объект внутри метода?
  4. Можно ли в Python добавлять новые атрибуты объектам, которые отсутствуют в их классе?
  5. Почему в программировании предпочтительнее использовать сеттеры и геттеры для работы с полями объекта?