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

В чем разница декораторов @staticmethod и @classmethod с точки зрения принимаемых аргументов?

1.3 Junior🔥 221 комментариев
#Python Core

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

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

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

Разница между @staticmethod и @classmethod в Python

Это фундаментальное различие в Python, касающееся того, какие аргументы получает метод.

Быстрая таблица сравнения

Аспект@staticmethod@classmethodОбычный метод
Первый аргументНичегоcls (класс)self (экземпляр)
Доступ к данным❌ Нельзя✅ К данным класса✅ К данным экземпляра
Переопределение в подклассе❌ Нет✅ Да✅ Да
ИспользованиеУтилитыАльтернативные конструкторыОсновной код

1. @staticmethod — статический метод

class Math:
    @staticmethod
    def add(a, b):
        """Статический метод: НЕ получает ни self ни cls"""
        return a + b

# Вызов
result = Math.add(5, 3)  # 8

# Можно вызвать и на экземпляре (но обычно так не делают)
math_obj = Math()
result = math_obj.add(5, 3)  # Также 8, но self НЕ передаётся

# Характеристики:
# 1. Не имеет доступа к self (экземпляру)
# 2. Не имеет доступа к cls (классу)
# 3. Это просто функция внутри класса
# 4. Логически принадлежит классу, но не использует его

2. @classmethod — метод класса

class Counter:
    count = 0  # Переменная класса
    
    def __init__(self, name):
        self.name = name
        Counter.count += 1
    
    @classmethod
    def get_count(cls):
        """Метод класса: ПОЛУЧАЕТ cls как первый аргумент"""
        return cls.count
    
    @classmethod
    def from_string(cls, data):
        """Альтернативный конструктор"""
        # Получает класс, может создать экземпляр
        name = data.split(':')[0]
        return cls(name)  # Вызовет __init__

# Использование
c1 = Counter('Alice')
c2 = Counter('Bob')

print(Counter.get_count())  # 2 - получает доступ к cls.count
print(c1.get_count())       # 2 - тоже работает (cls = Counter)

# Альтернативный конструктор
c3 = Counter.from_string('Charlie:30')
print(Counter.get_count())  # 3

# Характеристики:
# 1. ПОЛУЧАЕТ cls как первый аргумент
# 2. cls = класс, в котором был вызван метод
# 3. Может создавать экземпляры (cls(...))
# 4. Может изменять данные класса
# 5. Переопределяется в подклассах (polymorphism!)

3. Практический пример: различие в полиморфизме

class Animal:
    """Base класс"""
    kind = 'Unknown'
    
    @staticmethod
    def make_sound():
        """Статический метод - НЕ переопределяется в подклассах"""
        return "Some sound"
    
    @classmethod
    def describe(cls):
        """Метод класса - ДА переопределяется через cls"""
        return f"This is a {cls.kind}"
    
    def instance_method(self):
        """Обычный метод"""
        return f"I am {self.__class__.__name__}"

class Dog(Animal):
    """Подкласс"""
    kind = 'Dog'
    
    # ❌ Не нужно переопределять staticmethod
    # @staticmethod
    # def make_sound():
    #     return "Woof"
    # Если переопределишь - будет отдельный метод, не override
    
    # ✅ Переопределяем classmethod
    @classmethod
    def describe(cls):
        return f"This is a {cls.kind} (bark bark)"

class Cat(Animal):
    """Другой подкласс"""
    kind = 'Cat'
    
    @classmethod
    def describe(cls):
        return f"This is a {cls.kind} (meow meow)"

# Результаты
print(Animal.make_sound())      # "Some sound"
print(Dog.make_sound())          # "Some sound" - ОДИНАКОВО!
print(Cat.make_sound())          # "Some sound" - ОДИНАКОВО!

print(Animal.describe())         # "This is a Unknown"
print(Dog.describe())            # "This is a Dog (bark bark)"  ✅ Переопределено!
print(Cat.describe())            # "This is a Cat (meow meow)"  ✅ Переопределено!

# ✅ classmethod позволяет полиморфизм
# ❌ staticmethod НЕ позволяет полиморфизм

4. Визуализация: что такое cls?

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

class Child(Parent):
    pass

# cls изменяется в зависимости от того, где вызвали!
Parent.show_class()
# cls = <class 'Parent'>
# cls.__name__ = Parent

Child.show_class()
# cls = <class 'Child'>      ✅ ДРУГОЙ КЛАСС!
# cls.__name__ = Child

child_instance = Child()
child_instance.show_class()
# cls = <class 'Child'>      ✅ ВСЕ ЕЩЁ Child (не Child instance!)
# cls.__name__ = Child

# ✅ cls = класс, через который был вызван метод (или экземпляра)

5. Практический пример: Factory Pattern с classmethod

from abc import ABC, abstractmethod
from typing import Type

class Database(ABC):
    """Base класс для разных БД"""
    
    @abstractmethod
    def connect(self):
        pass

class PostgreSQL(Database):
    def connect(self):
        print("Connected to PostgreSQL")

class MySQL(Database):
    def connect(self):
        print("Connected to MySQL")

class SQLite(Database):
    def connect(self):
        print("Connected to SQLite")

class DatabaseFactory:
    """Factory с classmethod"""
    _db_types = {
        'postgres': PostgreSQL,
        'mysql': MySQL,
        'sqlite': SQLite,
    }
    
    @classmethod
    def create_db(cls, db_type: str) -> Database:
        """Factory method с classmethod"""
        db_class = cls._db_types.get(db_type)
        if not db_class:
            raise ValueError(f"Unknown DB type: {db_type}")
        # ✅ Создаёт нужный класс через cls
        return db_class()  # Или cls._db_types[db_type]()

# Использование
db1 = DatabaseFactory.create_db('postgres')
db1.connect()  # Connected to PostgreSQL

db2 = DatabaseFactory.create_db('mysql')
db2.connect()  # Connected to MySQL

# classmethod позволяет полиморфный factory pattern

6. Альтернативные конструкторы с classmethod

from datetime import datetime

class User:
    def __init__(self, id: int, name: str, email: str):
        self.id = id
        self.name = name
        self.email = email
        self.created_at = datetime.now()
    
    # ❌ ПЛОХО: несколько инициализаторов
    # def __init__(self, ...):
    # def __init__(self, ...):
    # Можно только один __init__!
    
    # ✅ ПРАВИЛЬНО: использовать classmethod для альтернативных конструкторов
    
    @classmethod
    def from_dict(cls, data: dict):
        """Создать из словаря"""
        return cls(
            id=data['id'],
            name=data['name'],
            email=data['email']
        )
    
    @classmethod
    def from_json_string(cls, json_str: str):
        """Создать из JSON строки"""
        import json
        data = json.loads(json_str)
        return cls.from_dict(data)  # Переиспользуем другой конструктор
    
    @classmethod
    def from_database_row(cls, row):
        """Создать из строки БД"""
        return cls(
            id=row[0],
            name=row[1],
            email=row[2]
        )

# Разные способы создания одного объекта
user1 = User(1, 'Alice', 'alice@example.com')

user2 = User.from_dict({
    'id': 2,
    'name': 'Bob',
    'email': 'bob@example.com'
})

user3 = User.from_json_string(
    '{"id": 3, "name": "Charlie", "email": "charlie@example.com"}'
)

user4 = User.from_database_row((4, 'David', 'david@example.com'))

# ✅ classmethod позволяет множеств альтернативных конструкторов

7. Когда использовать что

# ✅ @staticmethod когда:
# - Метод логически связан с классом
# - НО не нужен доступ к self или cls
# - Это просто утилита

class StringUtils:
    @staticmethod
    def is_email_valid(email: str) -> bool:
        """Утилита для проверки email"""
        return '@' in email
    
    @staticmethod
    def capitalize_words(text: str) -> str:
        """Утилита для капитализации"""
        return ' '.join(word.capitalize() for word in text.split())

# ✅ @classmethod когда:
# - Нужен доступ к классу (cls)
# - Создание альтернативных конструкторов
# - Изменение данных класса
# - Factory pattern
# - Хотите переопределение в подклассах (полиморфизм)

class FileHandler:
    supported_formats = ['.txt', '.pdf', '.doc']
    
    @classmethod
    def add_format(cls, format: str):
        """Добавить поддерживаемый формат"""
        cls.supported_formats.append(format)
    
    @classmethod
    def is_supported(cls, filename: str) -> bool:
        """Проверить поддержку формата"""
        extension = '.' + filename.split('.')[-1]
        return extension in cls.supported_formats

# ✅ Обычный метод когда:
# - Нужен доступ к self (данным экземпляра)
# - Основная логика класса

class BankAccount:
    def __init__(self, balance: float):
        self.balance = balance
    
    def withdraw(self, amount: float):
        """Обычный метод: работает с self.balance"""
        if amount > self.balance:
            raise ValueError("Insufficient funds")
        self.balance -= amount

8. Подробное сравнение аргументов

class Example:
    class_var = "I'm class variable"
    
    def __init__(self, value):
        self.instance_var = value
    
    # ===== СТАТИЧЕСКИЙ МЕТОД =====
    @staticmethod
    def static_method(arg1, arg2):
        # Аргументы: arg1, arg2 (ничего больше)
        # Доступ: не к self, не к cls
        # Может делать: только то, что передано в аргументах
        return arg1 + arg2
    
    # ===== МЕТОД КЛАССА =====
    @classmethod
    def class_method(cls, arg1, arg2):
        # ПЕРВЫЙ аргумент: cls (АВТОМАТИЧЕСКИ передаётся)
        # Остальные: arg1, arg2 (как указаны)
        # Доступ: к cls (классу) и его переменным
        print(f"cls.class_var = {cls.class_var}")
        return f"Created via {cls.__name__}: {arg1} + {arg2}"
    
    # ===== ОБЫЧНЫЙ МЕТОД =====
    def instance_method(self, arg1, arg2):
        # ПЕРВЫЙ аргумент: self (АВТОМАТИЧЕСКИ передаётся)
        # Остальные: arg1, arg2 (как указаны)
        # Доступ: к self (экземпляру) и его переменным
        print(f"self.instance_var = {self.instance_var}")
        return f"Instance method: {arg1} + {arg2}"

# Вызовы и что передаётся
print(Example.static_method(5, 3))
# Аргументы: 5, 3
# Результат: 8

print(Example.class_method(5, 3))
# Аргументы: cls (автоматически Example), 5, 3
# Результат: Created via Example: 5 + 3

obj = Example(42)
print(obj.instance_method(5, 3))
# Аргументы: self (автоматически obj), 5, 3
# Результат: Instance method: 5 + 3

9. Визуальное сравнение сигнатур

@staticmethod
def method(arg1, arg2):
    # def method(arg1, arg2):
    # Первый аргумент = arg1
    # Второй аргумент = arg2
    # Никаких автоматических!

@classmethod
def method(cls, arg1, arg2):
    # def method(cls, arg1, arg2):
    # Первый аргумент = cls (автоматический)
    # Второй аргумент = arg1
    # Третий аргумент = arg2
    # cls передаётся АВТОМАТИЧЕСКИ

def method(self, arg1, arg2):
    # def method(self, arg1, arg2):
    # Первый аргумент = self (автоматический)
    # Второй аргумент = arg1
    # Третий аргумент = arg2
    # self передаётся АВТОМАТИЧЕСКИ

10. Итоговый вывод

@staticmethod:

  • НЕ получает self
  • НЕ получает cls
  • Получает только явно переданные аргументы
  • Не может переопределяться в подклассах (для полиморфизма)
  • Используется для утилит

@classmethod:

  • ПОЛУЧАЕТ cls как ПЕРВЫЙ аргумент (автоматически)
  • Получает остальные аргументы как обычно
  • cls = класс, через который был вызван
  • МОЖЕТ переопределяться в подклассах (полиморфизм!)
  • Используется для альтернативных конструкторов, factory pattern'ов

Обычный метод:

  • ПОЛУЧАЕТ self как ПЕРВЫЙ аргумент (автоматически)
  • Получает остальные аргументы как обычно
  • self = экземпляр, на котором был вызван
  • МОЖЕТ переопределяться в подклассах
  • Основная логика класса

Ключевое отличие: @staticmethod не получает ничего, @classmethod получает cls, обычный метод получает self.

В чем разница декораторов @staticmethod и @classmethod с точки зрения принимаемых аргументов? | PrepBro