При работе с классами в Python вы наткнетесь на декоратор @classmethod. Он имеет четкую цель, отличающую его от @staticmethod или обычных методов экземпляра (Instance Method).
В этой статье мы узнаем, что такое @classmethod, а также в каких ситуациях и как его следует использовать по сравнению с @staticmethod и методами экземпляра.
1. Три типа методов: Instance vs. Class vs. Static
В Python классы по умолчанию имеют три типа методов. Главным отличием является то, что метод принимает в качестве первого аргумента.
class MyClass:
# 1. Метод экземпляра (Instance Method)
def instance_method(self, arg1):
# 'self' получает экземпляр класса.
# Можно обращаться к свойствам экземпляра (self.x) и изменять их.
print(f"Метод экземпляра вызван с {self} и {arg1}")
# 2. Метод класса (Class Method)
@classmethod
def class_method(cls, arg1):
# 'cls' получает сам класс (MyClass).
# Чаще используется для доступа к свойствам класса или
# выполнения роли 'фабрики', создающей экземпляры.
print(f"Метод класса вызван с {cls} и {arg1}")
# 3. Статический метод (Static Method)
@staticmethod
def static_method(arg1):
# Не принимает 'self' или 'cls'.
# Является утилитной функцией, не зависящей от состояния класса или экземпляра.
# Эта функция просто 'прикреплена' к классу.
print(f"Статический метод вызван с {arg1}")
# Пример вызова методов
obj = MyClass()
# Метод экземпляра (вызывается через экземпляр)
obj.instance_method("Hello")
# Метод класса (можно вызывать как через класс, так и через экземпляр)
MyClass.class_method("World")
obj.class_method("World") # это тоже возможно, но более понятно вызывать через класс.
# Статический метод (можно вызывать как через класс, так и через экземпляр)
MyClass.static_method("Python")
obj.static_method("Python")
Сравнение в таблице
| Категория | Первый аргумент | Объект доступа | Основное использование |
|---|---|---|---|
| Метод экземпляра | self (экземпляр) |
Свойства экземпляра (self.name) |
Манипуляция состоянием объекта |
| Метод класса | cls (класс) |
Свойства класса (cls.count) |
Альтернативный конструктор (фабрика) |
| Статический метод | Нет | Нет | Утилитная функция, логически связанная с классом |
2. Ключевое использование @classmethod: Фабричный метод
Наиболее важный и распространенный случай использования @classmethod — это определение "Альтернативного конструктора" или "Фабричного метода".
Обычный конструктор __init__ обычно принимает явные аргументы для инициализации объекта. Однако иногда возникают ситуации, когда необходимо создать объект иным способом (например, разбор строки, использование словаря, определенных значений расчетов).
Используя @classmethod, можно создать другую 'точку входа', которая возвращает экземпляр класса, помимо __init__.

Пример: Создание объекта Person из строки даты
Предположим, класс Person принимает name и age. Если вы хотите создать объект также из строки формата 'Имя-год рождения', что делать?
import datetime
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
print(f"Здравствуйте, меня зовут {self.name} и мне {self.age} лет.")
@classmethod
def from_birth_year(cls, name, birth_year):
"""
Принимает год рождения и рассчитывает возраст, создавая экземпляр Person.
"""
current_year = datetime.datetime.now().year
age = current_year - birth_year
# 'cls' указывает на сам класс Person.
# Это аналогично return Person(name, age), но,
# использование 'cls' дает преимущества при наследовании. (об этом ниже)
return cls(name, age)
# Использование обычного конструктора
person1 = Person("Alice", 30)
person1.greet()
# Использование альтернативного конструктора, созданного с помощью @classmethod
person2 = Person.from_birth_year("MadHatter", 1995)
person2.greet()
Результат:
Здравствуйте, меня зовут Alice и мне 30 лет.
Здравствуйте, меня зовут MadHatter и мне 30 лет. (по состоянию на 2025 год)
В примере выше метод from_birth_year обращается к самому классу Person через аргумент cls. Затем он использует рассчитанное значение age, чтобы вызвать cls(name, age), что аналогично вызову Person(name, age) и возвращает новый экземпляр Person.
3. @classmethod и наследование
"Почему бы просто не вызвать Person(...), зачем использовать cls(...)?"
Истинная мощь @classmethod проявляется в наследовании. cls указывает на _тот класс_, в котором был вызван метод.
Предположим, мы создаем класс Student, который наследует от Person.
class Student(Person):
def __init__(self, name, age, major):
super().__init__(name, age) # Вызов __init__ родительского класса
self.major = major
def greet(self):
# Переопределение метода
print(f"Здравствуйте, я {self.major} студент {self.name} и мне {self.age} лет.")
# Вызываем @classmethod родителя из класса Student.
student1 = Student.from_birth_year("Jhon", 2005, "компьютерные науки")
# Ой, возникает ошибка TypeError!
# При вызове Student.from_birth_year
# запускается Person.from_birth_year, в то время как 'cls' возвращает 'Student'.
# Таким образом, return cls(name, age) превращается в return Student(name, age).
# Однако __init__ класса Student требует дополнительный аргумент 'major'.
Код выше вызывает ошибку, потому что __init__ класса Student требует major. Однако, основной момент в том, что cls указывает на Student, а не на Person.
Если бы from_birth_year был @staticmethod или создан жестко с Person(name, age), вызов Student.from_birth_year вернул бы экземпляр Person, а не Student.
@classmethod гарантирует, что через cls создается правильный экземпляр дочернего класса, даже если метод вызывается из дочернего класса. (Конечно, если сигнатура __init__ дочернего класса изменится, from_birth_year тоже придется переопределить.)
4. Резюме
Итак, подводя итог, можно сказать следующее о @classmethod.
-
@classmethodпринимаетcls(сам класс) в качестве первого аргумента. -
Основное использование - это 'Альтернативный конструктор (Фабричный метод)'. Он используется для предоставления различных способов создания объекта помимо
__init__. -
Он мощен в сочетании с наследованием. При вызове из дочернего класса
clsуказывает на правильный дочерний класс, обеспечивая создание соответствующего экземпляра.
Запомните, что @staticmethod — это утилитная функция, для которой состояние класса/экземпляра вообще не нужно, @classmethod — это фабричный метод, который требует доступа к самому классу, а обычные методы экземпляра используются для работы с состоянием объекта (self).
Комментариев нет.