Classmethod: примеры (PYTHON)

Использование classmethod в Python с подробными примерами
Раздел: Декораторы, Методы классов
classmethod: classmethod

Основные сведения о classmethod

Декоратор @classmethod в Python предназначен для создания методов класса. Такой метод принимает первым аргументом ссылку на сам класс (обычно именуемую cls), а не на экземпляр объекта (как в обычном методе, где первый аргумент - self). Это позволяет обращаться к атрибутам класса и создавать его экземпляры, не имея конкретного объекта.

Основное применение classmethod: реализация альтернативных конструкторов (фабричные методы), методов, которые должны работать с состоянием класса, а не экземпляра, и для создания методов, которые наследуются и могут быть переопределены в подклассах с сохранением контекста класса.

Синтаксис функции classmethod() как встроенной функции практически не используется напрямую. Обычно применяют декоратор @classmethod.

Аргументы:

  • function - функция, которую нужно преобразовать в метод класса. Это единственный позиционный аргумент.

Возвращаемое значение:

  • Метод класса, который можно вызвать как от имени класса, так и от имени его экземпляра.

Базовые примеры использования

Простейший пример создания и вызова метода класса:

class MyClass:
    class_attr = "Атрибут класса"

    @classmethod
    def class_method(cls):
        return f"Вызван метод класса. cls={cls.__name__}, class_attr='{cls.class_attr}'"

# Вызов от имени класса
print(MyClass.class_method())

# Вызов от имени экземпляра
obj = MyClass()
print(obj.class_method())
Вызван метод класса. cls=MyClass, class_attr='Атрибут класса'
Вызван метод класса. cls=MyClass, class_attr='Атрибут класса'

Альтернативный конструктор (фабричный метод) для создания объекта из строки:

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

    @classmethod
    def from_string(cls, string):
        name, age_str = string.split(',')
        age = int(age_str.strip())
        return cls(name, age)  # Создаем экземпляр того класса, от которого вызван

person = Person.from_string("Иван, 30")
print(f"Имя: {person.name}, Возраст: {person.age}")
Имя: Иван, Возраст: 30

Похожие механизмы в Python

Помимо @classmethod существуют другие типы методов.

@staticmethod: не получает ни ссылки на класс (cls), ни ссылки на экземпляр (self). Это обычная функция, помещенная в пространство имен класса для логической группировки. Используют, когда методу не нужен доступ к атрибутам класса или экземпляра.

class Calculator:
    @staticmethod
    def add(a, b):
        return a + b

print(Calculator.add(5, 3))
8

Обычные методы экземпляра: первый параметр - self. Используют для работы с данными конкретного объекта.

@property: превращает метод в атрибут, доступный только для чтения (или с использованием сеттеров и делитеров). Используют для вычисляемых атрибутов или валидации.

Выбор зависит от задачи. @classmethod предпочтительнее, когда логика связана с классом в целом (создание объектов, модификация атрибутов класса). @staticmethod подходит для служебных функций, не зависящих от состояния класса.

Распространенные ошибки

Использование self вместо cls: это приводит к путанице и потенциальным ошибкам, особенно при наследовании.

class MyClass:
    value = 100

    @classmethod
    def wrong_method(self):  # Ошибка в именовании, но код работает
        return self.value  # Здесь self - на самом деле ссылка на класс

print(MyClass.wrong_method())  # 100
100

Хотя код работает, использование self вводит в заблуждение. Правильно использовать cls.

Попытка доступа к атрибутам экземпляра из classmethod: атрибуты экземпляра создаются в __init__ и недоступны без конкретного объекта.

class Example:
    def __init__(self):
        self.instance_attr = "данные"

    @classmethod
    def bad_method(cls):
        # print(self.instance_attr)  # NameError, если бы self был определен
        # print(cls.instance_attr)   # AttributeError, т.к. атрибут класса отсутствует
        pass

Example.bad_method()

Забытый декоратор @classmethod: метод ожидает первым аргументом экземпляр, но вызывается от класса.

class Example:
    def method(self):
        return "Вызван"

# Example.method()  # TypeError: method() missing 1 required positional argument: 'self'

Изменения в последних версиях Python

Поведение декоратора @classmethod остается стабильным на протяжении многих версий. В Python 3.9 и 3.10 не было внесено существенных изменений, влияющих на его синтаксис или базовую функциональность.

Важным контекстным изменением стало введение в Python 3.10 улучшенных сообщений об ошибках, которые могут точнее указывать на проблемы, связанные с неправильным вызовом методов класса.

Расширенные примеры применения

Использование classmethod для создания реестра подклассов:

Пример python
class Animal:
    _registry = {}

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

    @classmethod
    def register(cls, animal_type):
        def decorator(subclass):
            cls._registry[animal_type] = subclass
            return subclass
        return decorator

    @classmethod
    def create(cls, animal_type, name):
        subclass = cls._registry.get(animal_type)
        if subclass:
            return subclass(name)
        raise ValueError(f"Неизвестный тип животного: {animal_type}")

@Animal.register("собака")
class Dog(Animal):
    def sound(self):
        return "Гав"

@Animal.register("кошка")
class Cat(Animal):
    def sound(self):
        return "Мяу"

dog = Animal.create("собака", "Шарик")
print(f"{dog.name} говорит {dog.sound()}")
cat = Animal.create("кошка", "Мурка")
print(f"{cat.name} говорит {cat.sound()}")
Шарик говорит Гав
Мурка говорит Мяу

Кэширование данных на уровне класса с использованием classmethod:

Пример python
class DataFetcher:
    _cache = {}

    def __init__(self, source):
        self.source = source

    @classmethod
    def fetch_cached(cls, key):
        if key not in cls._cache:
            # Имитация долгой операции
            cls._cache[key] = f"Данные для ключа '{key}'"
            print(f"Кэширование ключа: {key}")
        return cls._cache[key]

print(DataFetcher.fetch_cached("user_1"))
print(DataFetcher.fetch_cached("user_2"))
print(DataFetcher.fetch_cached("user_1"))  # Возьмется из кэша
print(DataFetcher._cache)
Кэширование ключа: user_1
Данные для ключа 'user_1'
Кэширование ключа: user_2
Данные для ключа 'user_2'
Данные для ключа 'user_1'
{'user_1': "Данные для ключа 'user_1'", 'user_2': "Данные для ключа 'user_2'"}

Наследование classmethod и использование полиморфизма:

Пример python
class Base:
    default_value = "Базовый"

    @classmethod
    def create(cls, value=None):
        obj = cls()  # Создаст экземпляр того класса, из которого вызван
        obj.value = value or cls.default_value
        return obj

class Derived(Base):
    default_value = "Производный"

base_obj = Base.create()
derived_obj = Derived.create()
print(f"Base: {base_obj.value}")
print(f"Derived: {derived_obj.value}")

# Полиморфный вызов
obj2 = Derived.create("Специальное")
print(f"Derived с параметром: {obj2.value}")
Base: Базовый
Derived: Производный
Derived с параметром: Специальное

Аналоги в других языках программирования

Java, Kotlin: статические методы (static). В Java они не полиморфны (не переопределяются). В Kotlin есть companion object для аналога classmethod.

// Kotlin
class Person(val name: String, val age: Int) {
    companion object {
        fun fromString(str: String): Person {
            val parts = str.split(",")
            return Person(parts[0].trim(), parts[1].trim().toInt())
        }
    }
}
val p = Person.fromString("Анна, 25")

C#: статические методы. Для фабрик часто используют статические методы или отдельные фабричные классы.

// C#
public class Person {
    public string Name { get; }
    public int Age { get; }
    private Person(string name, int age) { Name = name; Age = age; }
    public static Person FromString(string str) {
        var parts = str.Split(',');
        return new Person(parts[0].Trim(), int.Parse(parts[1].Trim()));
    }
}
var p = Person.FromString("Петр, 40");

JavaScript: статические методы классов (ключевое слово static). Доступны через класс, а не экземпляр.

// JavaScript
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    static fromString(str) {
        const [name, age] = str.split(',');
        return new Person(name.trim(), parseInt(age.trim()));
    }
}
const p = Person.fromString("Мария, 35");

PHP: статические методы, объявляемые с ключевым словом static. Внутри можно использовать self:: или static:: (позднее статическое связывание).

// PHP
class Person {
    public function __construct(private string $name, private int $age) {}
    public static function fromString(string $str): self {
        [$name, $age] = explode(',', $str);
        return new self(trim($name), (int)trim($age));
    }
}
$p = Person::fromString("Сергей, 50");

Go: отсутствует понятие классов. Фабричные функции - обычные функции, возвращающие экземпляр структуры.

// Go
type Person struct {
    Name string
    Age  int
}
func NewPersonFromString(s string) Person {
    parts := strings.Split(s, ",")
    age, _ := strconv.Atoi(strings.TrimSpace(parts[1]))
    return Person{Name: strings.TrimSpace(parts[0]), Age: age}
}
p := NewPersonFromString("Ольга, 28")

питон classmethod function comments

En
Classmethod Transform a method into a class method