← Назад к вопросам

Как работает декоратор @classmethod?

2.0 Middle🔥 141 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает декоратор @classmethod

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

Основные различия: @classmethod vs @staticmethod vs обычные методы

class MyClass:
    class_variable = "I am a class variable"
    
    # Обычный метод экземпляра
    def instance_method(self):
        # self — экземпляр класса
        return f"Instance method called on {self}"
    
    # Метод класса
    @classmethod
    def class_method(cls):
        # cls — сам класс, не экземпляр
        return f"Class method called on {cls.__name__}"
    
    # Статический метод
    @staticmethod
    def static_method():
        # Нет ни self, ни cls
        return "Static method called"

# Использование
obj = MyClass()
print(obj.instance_method())     # Instance method called on <__main__.MyClass object>
print(MyClass.class_method())    # Class method called on MyClass
print(MyClass.static_method())   # Static method called

# Интересно: class_method можно вызвать и на экземпляре
print(obj.class_method())        # Class method called on MyClass

Как @classmethod работает внутри

class Demo:
    @classmethod
    def show(cls):
        print(f"cls = {cls}")
        print(f"cls.__name__ = {cls.__name__}")

# Когда вызываешь Demo.show(), Python автоматически передаёт сам класс Demo
Demo.show()
# cls = <class "__main__.Demo">
# cls.__name__ = Demo

Типичные применения @classmethod

1. Альтернативные конструкторы (Factory Methods):

from datetime 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 из года рождения"""
        age = datetime.now().year - birth_year
        return cls(name, age)  # Возвращает экземпляр класса
    
    @classmethod
    def from_string(cls, person_str):
        """Создаёт объект Person из строки Name:Age"""
        name, age = person_str.split(":")
        return cls(name, int(age))

# Использование
person1 = Person("Alice", 30)
person2 = Person.from_birth_year("Bob", 1990)
person3 = Person.from_string("Charlie:25")

print(person1.name, person1.age)  # Alice 30
print(person2.name, person2.age)  # Bob 34 (примерно)
print(person3.name, person3.age)  # Charlie 25

2. Работа с переменными класса:

class Counter:
    count = 0  # Переменная класса
    
    def __init__(self, name):
        self.name = name
        Counter.count += 1  # Увеличиваем счётчик класса
    
    @classmethod
    def get_count(cls):
        """Возвращает количество созданных объектов"""
        return cls.count
    
    @classmethod
    def reset_count(cls):
        """Сбрасывает счётчик"""
        cls.count = 0

obj1 = Counter("First")
obj2 = Counter("Second")
obj3 = Counter("Third")

print(Counter.get_count())  # 3
Counter.reset_count()
print(Counter.get_count())  # 0

3. Наследование и @classmethod:

class Animal:
    @classmethod
    def from_name_and_species(cls, name, species):
        """Factory method для создания животных"""
        obj = cls()
        obj.name = name
        obj.species = species
        return obj

class Dog(Animal):
    pass

class Cat(Animal):
    pass

# Обратите внимание: cls правильно указывает на подкласс
dog = Dog.from_name_and_species("Rex", "Canis familiaris")
cat = Cat.from_name_and_species("Whiskers", "Felis catus")

print(type(dog).__name__)  # Dog
print(type(cat).__name__)  # Cat

4. Конфигурирование класса:

class Database:
    _config = {"host": "localhost", "port": 5432}
    
    @classmethod
    def configure(cls, host=None, port=None):
        """Настраивает параметры подключения"""
        if host:
            cls._config["host"] = host
        if port:
            cls._config["port"] = port
    
    @classmethod
    def get_connection_string(cls):
        config = cls._config
        return f"postgresql://{config["host"]}:{config["port"]}/db"

Database.configure(host="prod.server.com", port=5433)
print(Database.get_connection_string())
# postgresql://prod.server.com:5433/db

@classmethod в цепочке наследования

class Base:
    value = "Base"
    
    @classmethod
    def get_value(cls):
        return cls.value

class Child(Base):
    value = "Child"

print(Base.get_value())    # Base
print(Child.get_value())   # Child (cls содержит Child, не Base)

Практический пример: Реестр классов

class Plugin:
    _registry = {}
    
    def __init__(self, name):
        self.name = name
    
    @classmethod
    def register(cls, name, plugin_class):
        """Регистрирует плагин"""
        cls._registry[name] = plugin_class
    
    @classmethod
    def get(cls, name):
        """Возвращает зарегистрированный плагин"""
        return cls._registry.get(name)
    
    @classmethod
    def list_all(cls):
        """Выводит все зарегистрированные плагины"""
        return list(cls._registry.keys())

class AudioPlugin(Plugin):
    pass

class VideoPlugin(Plugin):
    pass

# Регистрация плагинов
Plugin.register("audio", AudioPlugin)
Plugin.register("video", VideoPlugin)

print(Plugin.list_all())      # [audio, video]
print(Plugin.get("audio"))    # <class AudioPlugin>

Сравнение @classmethod и @staticmethod

class Example:
    name = "Example"
    
    @classmethod
    def class_method(cls):
        # Может получить доступ к переменным класса
        return f"Class method: {cls.name}"
    
    @staticmethod
    def static_method():
        # НЕ может получить доступ к переменным класса
        return "Static method: no access to class"

print(Example.class_method())   # Class method: Example
print(Example.static_method())  # Static method: no access to class

Лучшие практики

  • Используй @classmethod для factory methods — альтернативных конструкторов
  • Используй @classmethod для работы с переменными класса — когда нужно изменить состояние класса
  • Вместо явного вызова cls() — всегда используй cls() в factory методах для поддержки наследования
  • Документируй factory methods — явно указывай, что это альтернативный конструктор
  • Используй @staticmethod, если не нужен доступ к классу — это проще и понятнее

@classmethod — мощный инструмент для создания более гибких и переиспользуемых классов, особенно в контексте наследования и factory patterns.

Как работает декоратор @classmethod? | PrepBro