Сразу начнем с примера простого класса, который содержит обычный метод и метод класса:
class MyClass: def method(self): return 'instance method called', self @classmethod def classmethod(cls): return 'class method called', cls
В Python, методы класса отмечаются декоратором @classmethod
, следовательно в приведенном выше примере, метод класса будет определен в функции classmethod()
.
Как можно заметить, метод класса вместо того, чтобы принимать аргумент self
, принимает аргумент cls
. При вызове метода этот аргумент указывает на сам класс, а не на экземпляр класса.
Поскольку метод класса имеет доступ только к аргументу cls
, он не может изменять состояние экземпляра объекта. Для этого потребуется доступ к аргументу self
. НО все же методы класса могут изменять состояние класса, которое применяется ко всем экземплярам класса.
MyClass()
настроен таким образом, что реализация каждого метода возвращает кортеж для отслеживания, что происходит, и к каким частям класса или объекта метод может получить доступ.
Вот что происходит, когда мы вызываем метод экземпляра:
>>> obj = MyClass() >>> obj.method() # ('instance method called', <__main__.MyClass object at 0x7f9748403f40>) # Можно передать объект `obj` экземпляра вручную, # для получения того же результата >>> MyClass.method(obj) # ('instance method called', <__main__.MyClass object at 0x7f9748403f40>)
Этот кусок кода подтвердил, что метод экземпляра класса имеет доступ к экземпляру объекта, напечатанному как <__main__.MyClass object>
через аргумент self
.
Кстати, методы экземпляра также могут получить доступ к самому классу через атрибут self.__class__
. Это делает методы экземпляра мощными с точки зрения ограничений доступа - они могут изменять состояние как экземпляра объекта, так и самого класса.
Попробуем вызвать метод класса:
>>> obj.classmethod() # ('class method called', <class '__main__.MyClass'>)
Вызов метода класса obj.classmethod()
показал, что он не имеет доступа к объекту <__main__.MyClass object>
, а только к объекту <class '__main__.MyClass'>
, представляющему сам класс (в Python все является объектом, даже сами классы).
Обратите внимание, как Python автоматически передает класс в качестве первого аргумента функции, когда вызывается MyClass.classmethod()
. Вызов метода в Python через точечную нотацию запускает это поведение. Параметр self
в методах экземпляра работает точно так же.
Теперь посмотрим, что происходит, когда попытаться вызвать эти методы в самом классе - без предварительного создания экземпляра объекта:
>>> MyClass.classmethod() # ('class method called', <class '__main__.MyClass'>) >>> MyClass.method() # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # TypeError: method() missing 1 required positional argument: 'self'
Из примера видно, что можно нормально вызвать метод класса MyClass.classmethod()
, но попытка вызвать метод экземпляра MyClass.method()
завершилась ошибкой TypeError
. Этого следовало ожидать, ведь экземпляр объекта не создан, что означает, что Python не может заполнить аргумент self
и следовательно, вызов не выполняется.
Следующие примеры кода должны сделать понимание метода класса более ясным. Далее рассмотрим пример класса, имеющего дело с информацией о дате (это будет шаблон):
class Date: def __init__(self, day=0, month=0, year=0): self.day = day self.month = month self.year = year def string_to_db(self): return f'{self.year}-{self.month}-{self.day}'
Этот класс, очевидно, можно использовать для хранения информации об определенных датах, без информации о часовом поясе (предположим, что все даты представлены в формате UTC).
Здесь есть конструктор __init__
, типичный инициализатор экземпляров классов Python, который получает аргументы как типичный метод экземпляра, имея первый необязательный аргумент self
, который содержит ссылку на вновь созданный экземпляр.
Например есть несколько задач, которые можно решить при помощи будущих методов этого класса, не только определенного для примера метода, банального перевода числовых значений в формат строки с датой для баз данных.
Предположим, что мы хотим создать много экземпляров класса Date()
с информацией о дате, поступающей из внешнего источника, в виде строки с форматом dd.mm.yyyy
. Предположим, нужно сделать это в разных местах исходного кода проекта.
Итак, что для этого необходимо сделать:
Date
, передав эти значения в конструктор класса при его создании.Это будет выглядеть так:
>>> string_date = '10.10.2020' >>> day, month, year = map(int, string_date.split('.')) >>> date = Date(day, month, year) >>> date.string_to_db() # '2020-10-10'
Для выполнения этой задачи, например C++ или Java может реализовать такую возможность с перегрузкой метода __init__
, но в Python перегрузка методов отсутствует. Вместо нее необходимо использовать метод класса (декоратор @classmethod).
Создадим еще один "конструктор".
class Date(object): def __init__(self, day=0, month=0, year=0): self.day = day self.month = month self.year = year @classmethod def from_string(cls, date_as_string): day, month, year = map(int, date_as_string.split('.')) date1 = cls(day, month, year) return date1 def string_to_db(self): return f'{self.year}-{self.month}-{self.day}'
Более подробно о перегрузке смотрите в материале "Перегрузка методов в Python".
Обратите внимание, как используется аргумент cls
в методе класса from_string()
вместо прямого вызова конструктора класса Date()
.
Теперь создать новый экземпляр Date
, так же, можно следующим образом:
>>> date1 = Date.from_string('30.12.2020') >>> date1.string_to_db() # '2020-12-30' >>> date2 = Date.from_string('01.01.2021') >>> date2.string_to_db() # '2021-1-1'
Рассмотрим приведенную выше реализацию чтобы понять, какие преимущества здесь есть:
cls
- это объект, который содержит сам класс, а не его экземпляр. Это довольно круто, потому что, если мы наследуем класс Date
, для всех дочерних элементов также будет определен метод класса from_string()
.Используя методы класса, можно добавить столько альтернативных конструкторов, сколько необходимо. Такое поведение может сделать интерфейс создаваемых классов самодокументированным (до определенной степени конечно) и упростить их использование.