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()) # 100100
Хотя код работает, использование 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 для создания реестра подклассов:
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:
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 и использование полиморфизма:
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")