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

Что передается в параметре cls в декораторе @classmethod?

1.8 Middle🔥 91 комментариев
#Python Core#Soft Skills#Архитектура и паттерны

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

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

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

Параметр cls в @classmethod

Прямой ответ

В параметр cls передаётся сам класс, а не экземпляр. Это похоже на self в обычных методах, но вместо экземпляра передаётся класс.

Базовый пример

class Person:
    def __init__(self, name):
        self.name = name
    
    # Обычный метод — получает экземпляр
    def greet(self):
        print(f"Hello, I am {self.name}")
    
    # Класс-метод — получает класс
    @classmethod
    def create_from_string(cls, name_string):
        name = name_string.strip().title()
        return cls(name)  # Вызываем конструктор класса

# Использование
p1 = Person("alice")
p1.greet()  # "Hello, I am alice"

# Класс-метод
p2 = Person.create_from_string("  bob  ")
p2.greet()  # "Hello, I am Bob"

Ключевое отличие:

class Demo:
    def instance_method(self):
        print(type(self))  # <class '__main__.Demo'> — это экземпляр
        print(self.__class__)  # <class '__main__.Demo'>
    
    @classmethod
    def class_method(cls):
        print(cls)  # <class '__main__.Demo'> — это сам класс
        print(type(cls))  # <class 'type'> — класс это объект типа

d = Demo()
d.instance_method()  # self — это экземпляр d
Demo.class_method()  # cls — это класс Demo

Почему это полезно

1. Alternative constructors (альтернативные конструкторы)

from datetime import datetime

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
    
    # Стандартный конструктор из трёх чисел
    # Date(2024, 3, 23)
    
    # Альтернативный конструктор из строки
    @classmethod
    def from_string(cls, date_string):
        year, month, day = map(int, date_string.split('-'))
        return cls(year, month, day)
    
    # Альтернативный конструктор из timestamp
    @classmethod
    def from_timestamp(cls, timestamp):
        dt = datetime.fromtimestamp(timestamp)
        return cls(dt.year, dt.month, dt.day)
    
    def __repr__(self):
        return f"Date({self.year}, {self.month}, {self.day})"

# Используем
d1 = Date(2024, 3, 23)  # Стандартный
d2 = Date.from_string("2024-03-23")  # Из строки
d3 = Date.from_timestamp(1711188000)  # Из timestamp

print(d1)  # Date(2024, 3, 23)
print(d2)  # Date(2024, 3, 23)
print(d3)  # Date(2024, 3, 23)

2. Работа с наследованием

class Animal:
    def __init__(self, name):
        self.name = name
    
    @classmethod
    def create_dummy(cls):
        # cls — это конкретный класс (Animal, Dog, Cat)
        return cls("Unknown")

class Dog(Animal):
    def bark(self):
        print(f"{self.name} says: Woof!")

class Cat(Animal):
    def meow(self):
        print(f"{self.name} says: Meow!")

# create_dummy вернёт правильный класс
dog = Dog.create_dummy()
print(type(dog))  # <class '__main__.Dog'>
dog.bark()  # "Unknown says: Woof!"

cat = Cat.create_dummy()
print(type(cat))  # <class '__main__.Cat'>
cat.meow()  # "Unknown says: Meow!"

# БЕЗ @classmethod приходилось бы писать отдельный метод для каждого класса

3. Singleton паттерн

class Database:
    _instance = None
    
    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = cls()  # Создаём один раз
        return cls._instance

db1 = Database.get_instance()
db2 = Database.get_instance()

print(db1 is db2)  # True — один и тот же объект

4. Счётчик экземпляров класса

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

c1 = Counter("first")
c2 = Counter("second")
c3 = Counter("third")

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

Отличие от staticmethod

class Math:
    # @staticmethod — не получает ничего
    @staticmethod
    def add(a, b):
        return a + b
    
    # @classmethod — получает класс
    @classmethod
    def multiply_by_two(cls, a):
        # cls здесь не используется, но доступен
        return a * 2

print(Math.add(2, 3))  # 5
print(Math.multiply_by_two(4))  # 8

# Разница видна при наследовании
class AdvancedMath(Math):
    @staticmethod
    def add(a, b):
        return a + b + 100  # Переопределяем
    
    @classmethod
    def multiply_by_two(cls, a):
        # cls.name даст доступ к конкретному классу
        return a * 2 + (1 if cls.__name__ == "AdvancedMath" else 0)

print(AdvancedMath.add(2, 3))  # 105 — переопределение работает
print(AdvancedMath.multiply_by_two(4))  # 9

Реальный пример: ORM модель

class BaseModel:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
    
    @classmethod
    def all(cls):
        """Получить все объекты из БД"""
        # cls — это конкретная модель (User, Post и т.д.)
        return database.query(cls).all()
    
    @classmethod
    def find_by_id(cls, id):
        """Найти объект по ID"""
        return database.query(cls).filter(id=id).first()
    
    @classmethod
    def create(cls, **kwargs):
        """Создать и сохранить объект"""
        obj = cls(**kwargs)
        database.save(obj)
        return obj

class User(BaseModel):
    pass

class Post(BaseModel):
    pass

# Используем
users = User.all()  # SELECT * FROM users
user = User.find_by_id(1)  # SELECT * FROM users WHERE id = 1
new_user = User.create(name="Alice", email="alice@example.com")  # INSERT

posts = Post.all()  # SELECT * FROM posts
post = Post.find_by_id(1)  # SELECT * FROM posts WHERE id = 1

# Без @classmethod пришлось бы передавать класс как параметр:
# User.all(User) — некрасиво

Как работает мотанно?

class Example:
    name = "Example"
    
    @classmethod
    def show(cls):
        print(f"Class name: {cls.__name__}")
        print(f"Class itself: {cls}")
        print(f"name attribute: {cls.name}")

Example.show()
# Output:
# Class name: Example
# Class itself: <class '__main__.Example'>
# name attribute: Example

# При вызове: Example.show()
# Python автоматически передаёт Example в параметр cls
# Это как: Example.show(Example)

Практическая подсказка

Используй @classmethod когда:

  • Нужен альтернативный конструктор (from_string, from_dict и т.д.)
  • Работаешь с переменными класса
  • Нужно гарантировать, что вернётся правильный тип при наследовании
  • Реализуешь паттерны (singleton, factory)

Не используй @classmethod когда:

  • Нужен простой утилитарный метод без привязки к классу (@staticmethod)
  • Нужен доступ к экземпляру (обычный метод)

Заключение

В параметр cls передаётся сам класс, а не экземпляр. Это позволяет:

  1. Создавать альтернативные конструкторы
  2. Работать с переменными класса
  3. Правильно работать с наследованием
  4. Писать фабрики и паттерны

Память:

  • self = текущий экземпляр
  • cls = текущий класс
  • @classmethod = метод класса
  • @staticmethod = просто функция
Что передается в параметре cls в декораторе @classmethod? | PrepBro