@classmethod в Python: методы класса на примерах
Python classmethod превращает функцию внутри класса в метод класса: первым аргументом она получает сам класс cls, а не экземпляр self. Такой метод можно вызвать без создания объекта, и он корректно работает при наследовании. Разберем синтаксис декоратора @classmethod, фабричные методы, работу с атрибутами класса и отличия от @staticmethod.

Содержание статьи
Синтаксис и принцип работы
Метод класса объявляют декоратором @classmethod перед def. Первый параметр по соглашению называют cls: Python сам подставит в него класс, через который произошел вызов. Имя можно выбрать любое, но другие программисты ожидают именно cls.
class Counter:
created = 0
def __init__(self):
Counter.created += 1
@classmethod
def total(cls):
return f"{cls.__name__}: создано {cls.created}"
Counter(); Counter()
print(Counter.total()) # Counter: создано 2
print(Counter().total()) # Counter: создано 3Вызов работает и через класс, и через экземпляр. Во втором случае Python все равно передаст в cls класс объекта, а не сам объект. Поэтому внутри метода класса нет доступа к атрибутам конкретного экземпляра вроде self.name.
Функция classmethod() без декоратора
classmethod - встроенная функция, и декоратор лишь короткая запись ее вызова. Код ниже делает то же самое, что @classmethod, но читается хуже, поэтому в реальных проектах его почти не пишут.
class Person:
age = 25
def print_age(cls):
print("Возраст:", cls.age)
print_age = classmethod(print_age)
Person.print_age() # Возраст: 25Подробнее о том, как устроены декораторы и почему запись через @ равна повторному присваиванию, рассказано в статье декораторы в Python.
Альтернативные конструкторы
Главное применение методов класса - фабричные методы, которые создают объект из данных другого формата. У класса один __init__, а способов получить исходные данные бывает несколько: строка из файла, словарь из JSON, год рождения вместо возраста. Метод класса разбирает входные данные и вызывает cls(...).
from datetime import date
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_birth_year(cls, name, year):
return cls(name, date.today().year - year)
@classmethod
def from_string(cls, line):
name, age = line.split(";")
return cls(name.strip(), int(age))
p1 = Person.from_birth_year("Иван", 2000)
p2 = Person.from_string("Анна; 31")
print(p1.name, p1.age, p2.name, p2.age)Такой прием встречается и в стандартной библиотеке: dict.fromkeys(), datetime.fromisoformat(), datetime.fromtimestamp(), int.from_bytes() - все это методы класса.
Метод класса и наследование
Преимущество cls перед явным именем класса проявляется в наследниках. Если фабрика написана через cls(...), то при вызове из дочернего класса она вернет объект дочернего класса, и переопределять ее не придется.
class Shape:
def __init__(self, size):
self.size = size
@classmethod
def unit(cls):
return cls(1)
class Square(Shape):
def area(self):
return self.size ** 2
sq = Square.unit()
print(type(sq).__name__, sq.area()) # Square 1Если бы внутри unit() стояло return Shape(1), вызов Square.unit() вернул бы объект без метода area(). Механизм поиска методов в цепочке классов описан в материале про наследование классов.
Изменение атрибутов класса
Методы класса удобны для работы с общим состоянием: счетчиками, настройками, реестрами объектов. Присваивание через cls.атрибут меняет значение для всех экземпляров, а не создает копию в одном объекте.
class Config:
debug = False
@classmethod
def enable_debug(cls):
cls.debug = True
a, b = Config(), Config()
Config.enable_debug()
print(a.debug, b.debug) # True TrueУчтите, что при вызове из наследника cls указывает на наследника, и присваивание создаст атрибут уже у него, не затронув родителя. Иногда это нужно, иногда приводит к неожиданному поведению, поэтому для общего на всю иерархию значения пишут имя базового класса явно.
Сравнение с staticmethod и обычным методом
В классе Python бывают три вида методов. Они отличаются тем, что получают первым аргументом.
| Вид метода | Первый аргумент | Доступ к данным | Типичная задача |
|---|---|---|---|
| обычный | self - экземпляр | атрибуты объекта и класса | поведение конкретного объекта |
@classmethod | cls - класс | только атрибуты класса | фабрики, общее состояние |
@staticmethod | ничего не получает | только переданные аргументы | вспомогательная функция по теме класса |
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
@staticmethod
def c_to_f(c):
return c * 9 / 5 + 32
@classmethod
def from_fahrenheit(cls, f):
return cls((f - 32) * 5 / 9)
print(Temperature.c_to_f(100)) # 212.0
print(Temperature.from_fahrenheit(212).celsius) # 100.0Правило выбора простое: если методу нужен класс, берите @classmethod, если не нужен ни класс, ни объект, подойдет @staticmethod. Общие принципы устройства классов собраны в статье классы и объекты.
Ограничения и подводные камни
- Метод класса не видит атрибуты экземпляра, созданные в
__init__. Попытка обратиться кcls.nameзакончитсяAttributeError, если такого атрибута нет у класса. - Связка
@classmethodповерх@propertyработала в Python 3.9 и 3.10, в 3.11 была объявлена устаревшей и удалена в 3.13. Для вычисляемых атрибутов класса используйте обычный метод класса или метакласс. - Порядок декораторов важен:
@classmethodобычно ставят самым верхним, чтобы он оборачивал уже готовую функцию.
Справка по встроенной функции есть в документации Python.
Частые вопросы
Ответы на вопросы, которые возникают при первом знакомстве с методами класса.
Обязательно ли называть параметр cls?
Нет, Python передаст класс в первый параметр под любым именем. Но cls - общепринятое соглашение, и линтеры предупреждают об отступлении от него.
Можно ли вызвать метод класса через объект?
Да. Вызов obj.method() работает, и в cls попадет type(obj). Результат будет таким же, как у type(obj).method().
Может ли метод класса вызвать обычный метод?
Напрямую нет, потому что у него нет экземпляра. Сначала нужно создать объект через cls(...), а затем вызвать у него нужный метод.
Когда лучше staticmethod, а когда classmethod?
Если функция создает объект или читает атрибуты класса, выбирайте classmethod. Если это просто утилита, логически связанная с классом, например проверка формата строки, хватит staticmethod.