В чем назначение и как работает декоратор @classmethod в Python

«В чем назначение и как работает декоратор @classmethod в Python» — вопрос из категории ООП, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Декоратор @classmethod в Python преобразует метод так, что он получает класс в качестве первого аргумента, а не экземпляр объекта. По соглашению, этот аргумент называется cls.

Это позволяет методу работать с атрибутами самого класса, а не с состоянием конкретного экземпляра. В отличие от обычных методов, которые требуют self, @classmethod можно вызывать напрямую от класса, не создавая его экземпляр.

Ключевые отличия от других типов методов:

  • Обычный метод: def my_method(self, ...) — получает экземпляр как первый аргумент.
  • Метод класса: def my_method(cls, ...) — получает класс как первый аргумент.
  • Статический метод (@staticmethod): def my_method(...) — не получает ни экземпляр, ни класс и ведет себя как обычная функция, находящаяся в пространстве имен класса.

Основное применение — фабричные методы, которые предоставляют альтернативные способы создания экземпляров класса.

Пример 1: Работа с атрибутами класса

class Product:
    # Атрибут класса, общий для всех экземпляров
    total_products = 0

    def __init__(self, name):
        self.name = name
        Product.increment_total()

    @classmethod
    def increment_total(cls):
        # cls ссылается на класс Product
        cls.total_products += 1

    @classmethod
    def get_total_products(cls):
        return cls.total_products

p1 = Product("Phone")
p2 = Product("Laptop")

# Вызов метода через класс
print(f"Всего продуктов: {Product.get_total_products()}") # Вывод: Всего продуктов: 2

Пример 2: Фабричный метод

import datetime

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def from_birth_year(cls, name, birth_year):
        """Создает экземпляр Person, вычисляя возраст по году рождения."""
        current_year = datetime.date.today().year
        age = current_year - birth_year
        # cls(name, age) эквивалентно Person(name, age)
        return cls(name, age)

# Создание объекта через стандартный конструктор
person1 = Person("Alice", 34)

# Создание объекта через фабричный метод
person2 = Person.from_birth_year("Bob", 1990)

print(f"{person2.name} is {person2.age} years old.")
# Вывод (в 2024 году): Bob is 34 years old.