В чем разница между classmethod и обычным методом экземпляра в Python?

Ответ

Основное различие заключается в том, к чему привязан метод и какой первый аргумент он принимает.

  • Обычный метод (instance method): Привязан к экземпляру класса. Первым аргументом неявно принимает сам экземпляр, который по соглашению называется self.
  • Метод класса (@classmethod): Привязан к классу. Первым аргументом неявно принимает сам класс, который по соглашению называется cls.

Сравнение в коде:

class User:
    # Атрибут класса
    total_users = 0

    def __init__(self, name):
        # Атрибут экземпляра
        self.name = name
        User.total_users += 1

    # Обычный метод, работает с данными экземпляра (self.name)
    def say_hello(self):
        print(f"Hello, my name is {self.name}")

    # Метод класса, работает с данными класса (cls.total_users)
    @classmethod
    def get_total_users(cls):
        print(f"Total users: {cls.total_users}")

# Создаем экземпляры
user1 = User("Alice")
user2 = User("Bob")

# Вызов обычного метода через экземпляр
user1.say_hello()  # Output: Hello, my name is Alice

# Вызов метода класса можно делать как от класса, так и от экземпляра
User.get_total_users()  # Output: Total users: 2
user2.get_total_users() # Output: Total users: 2

Когда использовать @classmethod?

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

Пример фабричного метода:

from datetime import date

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

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

# Создание объекта стандартным способом
person1 = Person("John", 30)

# Создание объекта с помощью фабричного метода
person2 = Person.from_birth_year("Jane", 1995)

print(person2.age) # Выведет возраст, рассчитанный на основе года рождения