@classmethod и @staticmethod в Python

17 июля 2026
Автор

Антон Ларичев

Введение

В Python методы класса делятся на три типа: обычные методы экземпляра, методы класса (@classmethod) и статические методы (@staticmethod). Декораторы @classmethod и @staticmethod изменяют поведение методов, убирая привязку к конкретному экземпляру и предоставляя разные уровни доступа к данным класса.

Понимание разницы между ними позволяет писать более чистый и структурированный код, правильно группировать логику и избегать передачи лишних зависимостей.

Обычные методы экземпляра

Прежде чем разбирать @classmethod и @staticmethod, стоит вспомнить, как работают обычные методы.

class User:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def greet(self) -> str:
        return f"Привет, меня зовут {self.name}, мне {self.age} лет"


user = User("Антон", 30)
print(user.greet())  # Привет, меня зовут Антон, мне 30 лет

Обычный метод всегда получает первым аргументом self — ссылку на конкретный экземпляр класса. Через self метод может читать и изменять данные этого экземпляра.

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

Статический метод — это обычная функция, которая логически относится к классу, но не имеет доступа ни к экземпляру (self), ни к самому классу (cls). Он не получает никаких неявных аргументов.

Когда использовать @staticmethod

Статические методы подходят для вспомогательных операций, которые связаны с классом тематически, но не зависят от его состояния. Это utility-функции, сгруппированные внутри класса для удобства.

class MathUtils:
    @staticmethod
    def add(a: float, b: float) -> float:
        return a + b

    @staticmethod
    def is_even(n: int) -> bool:
        return n % 2 == 0


print(MathUtils.add(3, 5))    # 8
print(MathUtils.is_even(4))   # True
print(MathUtils.is_even(7))   # False

Статический метод можно вызвать как через класс (MathUtils.add()), так и через экземпляр (obj.add()), но оба варианта работают одинаково — без привязки к чему-либо.

Пример: валидация данных

class Email:
    def __init__(self, address: str):
        if not Email.is_valid(address):
            raise ValueError(f"Некорректный email: {address}")
        self.address = address

    @staticmethod
    def is_valid(address: str) -> bool:
        return "@" in address and "." in address.split("@")[-1]


email = Email("user@example.com")
print(email.address)  # user@example.com

# Проверка без создания экземпляра
print(Email.is_valid("not-an-email"))  # False

Логика валидации не зависит от данных экземпляра и не нуждается в доступе к классу — поэтому @staticmethod здесь идеальный выбор.

Пример: форматирование

class DateFormatter:
    @staticmethod
    def format_date(day: int, month: int, year: int) -> str:
        return f"{day:02d}.{month:02d}.{year}"

    @staticmethod
    def format_iso(day: int, month: int, year: int) -> str:
        return f"{year}-{month:02d}-{day:02d}"


print(DateFormatter.format_date(5, 3, 2024))  # 05.03.2024
print(DateFormatter.format_iso(5, 3, 2024))   # 2024-03-05

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

Метод класса получает первым аргументом не экземпляр (self), а сам класс (cls). Через cls можно обращаться к атрибутам класса, другим методам класса и — что особенно важно — создавать экземпляры этого класса.

Синтаксис и базовый пример

class Counter:
    count = 0

    def __init__(self):
        Counter.count += 1

    @classmethod
    def get_count(cls) -> int:
        return cls.count

    @classmethod
    def reset(cls) -> None:
        cls.count = 0


c1 = Counter()
c2 = Counter()
c3 = Counter()

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

cls здесь — это сам класс Counter, а не его экземпляр. Это позволяет работать с общим состоянием класса.

Альтернативные конструкторы (фабричные методы)

Главное применение @classmethod — создание альтернативных конструкторов. Это паттерн, когда объект можно создать из разных источников данных.

from datetime import date


class Person:
    def __init__(self, name: str, birth_year: int):
        self.name = name
        self.birth_year = birth_year

    @classmethod
    def from_birth_date(cls, name: str, birth_date: str) -> "Person":
        year = int(birth_date.split("-")[0])
        return cls(name, year)

    @classmethod
    def from_dict(cls, data: dict) -> "Person":
        return cls(data["name"], data["birth_year"])

    def get_age(self) -> int:
        return date.today().year - self.birth_year

    def __repr__(self) -> str:
        return f"Person(name={self.name!r}, birth_year={self.birth_year})"


# Три способа создать объект
p1 = Person("Антон", 1994)
p2 = Person.from_birth_date("Мария", "1990-05-15")
p3 = Person.from_dict({"name": "Иван", "birth_year": 1985})

print(p1)  # Person(name='Антон', birth_year=1994)
print(p2)  # Person(name='Мария', birth_year=1990)
print(p3)  # Person(name='Иван', birth_year=1985)

Ключевое преимущество использования cls(...) вместо прямого вызова Person(...) — корректная работа при наследовании.

@classmethod и наследование

Это одно из главных отличий @classmethod от @staticmethod. При наследовании cls указывает на дочерний класс, а не на тот, где определён метод.

class Animal:
    name = "Animal"

    def __init__(self, sound: str):
        self.sound = sound

    @classmethod
    def create_default(cls) -> "Animal":
        return cls("...")

    def speak(self) -> str:
        return f"{self.__class__.name} говорит: {self.sound}"


class Dog(Animal):
    name = "Собака"

    @classmethod
    def create_default(cls) -> "Dog":
        return cls("Гав")


class Cat(Animal):
    name = "Кошка"

    @classmethod
    def create_default(cls) -> "Cat":
        return cls("Мяу")


dog = Dog.create_default()
cat = Cat.create_default()

print(dog.speak())  # Собака говорит: Гав
print(cat.speak())  # Кошка говорит: Мяу

Если бы в фабричном методе использовался Animal(...) напрямую, дочерние классы не смогли бы корректно переопределить поведение. cls решает эту проблему.

Сравнение: self, cls и отсутствие аргумента

Основные различия между тремя типами методов:

Тип метода Первый аргумент Доступ к экземпляру Доступ к классу
Обычный метод self Да Через self.__class__
@classmethod cls Нет Да (напрямую)
@staticmethod Нет Нет

Пример с тремя типами в одном классе

class Temperature:
    unit = "Celsius"

    def __init__(self, value: float):
        self.value = value

    def to_fahrenheit(self) -> float:
        return self.value * 9 / 5 + 32

    @classmethod
    def set_unit(cls, unit: str) -> None:
        cls.unit = unit

    @staticmethod
    def celsius_to_kelvin(celsius: float) -> float:
        return celsius + 273.15


temp = Temperature(100)
print(temp.to_fahrenheit())                # 212.0
print(Temperature.celsius_to_kelvin(100))  # 373.15

Temperature.set_unit("Fahrenheit")
print(Temperature.unit)                    # Fahrenheit

Практический пример: класс конфигурации

Рассмотрим реальный сценарий — класс для работы с конфигурацией приложения.

import json
import os
from typing import Any


class Config:
    _defaults = {
        "debug": False,
        "host": "localhost",
        "port": 8000,
        "db_url": "sqlite:///app.db",
    }

    def __init__(self, settings: dict):
        self._settings = {**self._defaults, **settings}

    def get(self, key: str, default: Any = None) -> Any:
        return self._settings.get(key, default)

    @classmethod
    def from_json(cls, filepath: str) -> "Config":
        with open(filepath, "r", encoding="utf-8") as f:
            data = json.load(f)
        return cls(data)

    @classmethod
    def from_env(cls) -> "Config":
        settings = {
            "debug": os.getenv("DEBUG", "false").lower() == "true",
            "host": os.getenv("HOST", cls._defaults["host"]),
            "port": int(os.getenv("PORT", cls._defaults["port"])),
            "db_url": os.getenv("DATABASE_URL", cls._defaults["db_url"]),
        }
        return cls(settings)

    @staticmethod
    def is_valid_port(port: int) -> bool:
        return 1 <= port <= 65535

    @staticmethod
    def mask_url(url: str) -> str:
        if "@" in url:
            schema, rest = url.split("://", 1)
            credentials, host = rest.rsplit("@", 1)
            return f"{schema}://***@{host}"
        return url


config = Config.from_env()
print(config.get("host"))         # localhost

print(Config.is_valid_port(8080)) # True
print(Config.is_valid_port(99999)) # False
print(Config.mask_url("postgresql://admin:secret@db.example.com/mydb"))
# postgresql://***@db.example.com/mydb

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

Ещё один популярный паттерн — реестр объектов, реализованный через @classmethod.

from typing import Dict, Type


class BasePlugin:
    _registry: Dict[str, Type["BasePlugin"]] = {}

    def __init_subclass__(cls, plugin_name: str = "", **kwargs):
        super().__init_subclass__(**kwargs)
        if plugin_name:
            BasePlugin._registry[plugin_name] = cls

    @classmethod
    def get_plugin(cls, name: str) -> Type["BasePlugin"]:
        if name not in cls._registry:
            raise KeyError(
                f"Плагин '{name}' не найден. Доступные: {list(cls._registry)}"
            )
        return cls._registry[name]

    @classmethod
    def list_plugins(cls) -> list:
        return list(cls._registry.keys())

    def run(self) -> str:
        raise NotImplementedError


class JsonPlugin(BasePlugin, plugin_name="json"):
    def run(self) -> str:
        return "Обработка JSON"


class CsvPlugin(BasePlugin, plugin_name="csv"):
    def run(self) -> str:
        return "Обработка CSV"


print(BasePlugin.list_plugins())  # ['json', 'csv']

PluginClass = BasePlugin.get_plugin("json")
plugin = PluginClass()
print(plugin.run())  # Обработка JSON

Частые ошибки

Ошибка 1: использование @staticmethod там, где нужен @classmethod

class Animal:
    sound = "..."

    # Неправильно: при наследовании всегда вернёт Animal()
    @staticmethod
    def create_bad():
        return Animal()

    # Правильно: при наследовании вернёт экземпляр дочернего класса
    @classmethod
    def create(cls):
        return cls()


class Dog(Animal):
    sound = "Гав"


dog_bad = Dog.create_bad()
dog_good = Dog.create()

print(type(dog_bad))   # <class '__main__.Animal'>  — неожиданное поведение!
print(type(dog_good))  # <class '__main__.Dog'>     — корректно

Ошибка 2: жёсткая привязка к имени класса внутри метода

class Counter:
    count = 0

    # Проблемный вариант: жёсткая привязка к Counter ломает наследование
    @staticmethod
    def increment_bad():
        Counter.count += 1

    # Правильный вариант: cls всегда указывает на актуальный класс
    @classmethod
    def increment(cls):
        cls.count += 1


class SpecialCounter(Counter):
    count = 0


sc = SpecialCounter()
SpecialCounter.increment_bad()  # меняет Counter.count, а не SpecialCounter.count!
SpecialCounter.increment()      # меняет SpecialCounter.count — корректно

Ошибка 3: попытка обратиться к атрибутам экземпляра в @classmethod

class MyClass:
    class_value = 10

    def __init__(self):
        self.instance_value = 42

    @classmethod
    def show(cls):
        print(cls.class_value)    # OK — атрибут класса
        # print(cls.instance_value)  # AttributeError — это атрибут экземпляра

cls — это сам класс, а не экземпляр, поэтому атрибуты, заданные в __init__, через него недоступны.

Краткий чеклист выбора

При выборе типа метода используйте следующую логику:

  • Нужен доступ к данным конкретного объекта — обычный метод с self
  • Нужен альтернативный конструктор или работа с атрибутами класса, важна совместимость с наследованием — @classmethod
  • Метод логически относится к классу, но не зависит ни от экземпляра, ни от самого класса — @staticmethod

Заключение

Декораторы @classmethod и @staticmethod — инструменты для правильной организации методов внутри класса. @staticmethod группирует вспомогательные функции, не привязывая их к состоянию. @classmethod открывает доступ к самому классу, что незаменимо для фабричных методов и работы с атрибутами класса при наследовании.

Грамотное использование этих декораторов делает код более читаемым, явным и удобным для расширения.

Подробнее о Python и объектно-ориентированном программировании вы можете узнать на курсе Python для разработчиков.

Стрелочка влевоДекоратор @property в Python

Постройте личный план изучения Python до уровня Middle — бесплатно!

Python — часть карты развития Backend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Все гайды по Python

Как отправлять запросы с помощью requests в PythonПочему Python выводит значение без команды printКак работает команда print в PythonВозможности Python для автоматизации задачРабота с JSON в Python на примерахPython get — методы получения данныхКак находить и исправлять ошибки в PythonРабота с данными через API и внешние сервисыСтруктура и оформление кода PythonОсновы Django с PythonПолезные приёмы в Python для повседневной работыИспользование locals в Python для отладкиИнтеграция PHP и PythonКак выполнять HTTPS-запросы в PythonFastAPI Python — быстрый старт: создание REST API с нуляКак работать с API в Python
Ввод целого числа в PythonВедение логов в PythonУдаление данных в Python с помощью removeОбработка исключений с помощью try/except в PythonФункция super() в Python — как вызвать метод родителяСоздание собственных контекстных менеджеров в PythonРабота с символами программирования PythonРабота с переменной X в PythonРабота с классами в PythonКак скачать Python на компьютерПростая программа на Python для начинающихОсновы Python для тех, кто начинаетЧто нового в Python 3Поддерживается ли Python 2 и стоит ли его использоватьPython 1 — с чего начиналась история языкаКоманда python print - полное руководство по выводу данныхПользовательские исключения в PythonПравила именования переменных в PythonОсновы Python coreОписание объектов PythonНаследование классов в Python — основы и примерыМножественное наследование в Python — примеры и MROКонтекстный менеджер with в Python — как работает и зачем нуженКомментарии в Python — однострочные, многострочные и docstringКакой Python выбрать для установкиКак вывести целое число с помощью print в PythonКак установить Python на Windows macOS и LinuxКак пользоваться консолью PythonКак получить последний элемент в PythonКак найти значение в PythonКак настроить PythonКак использовать print для строк в PythonКак работает интерпретатор PythonИнструкция по работе с PythonБлок finally в обработке исключений PythonЦелые числа в PythonАбстрактные классы в Python — ABC и abstractmethod
Pydantic: валидация данных в Python
Загрузка данных PythonУправление проектами на GitHub с PythonСоздание веб-приложений на Flask PythonСоздание бота на PythonСоздание интерфейсов Python QTСоздание игр с PygameСоздание GUI в PythonКак работать со словарями в PythonРабота с библиотеками через Python PackagingРабота со временем в Python при помощи модуля timePython name — особенности переменнойМатематические операции в Python с модулем mathPython listing — что это и как использоватьPytest — тестирование на Python: полное руководствоОбработка изображений с OpenCV PythonNumPy в Python — основы и применение в задачахМашинное обучение с PythonИспользование Anaconda с PythonМодуль contextlib в Python — утилиты для контекстных менеджеровБиблиотеки Python и их применение в проектах
Возврат значений из функции в PythonВложенные функции в PythonСоздание собственных декораторов в PythonРабота с функцией map в PythonЦикл while в Python и примеры использованияОбработка чисел, введённых через input в PythonОсновные операторы в Python с примерамиУсловные выражения if else в Python для начинающихКак выполняется вызов функций call в PythonПродвинутые генераторы в Python — send, throw, close и корутиныПозиционные и именованные аргументы в PythonОбъявление переменных и управление областью видимости в PythonПередача аргументов по ссылке и по значению в PythonПередача аргументов через args и kwargs в PythonОсновные методы Python и примеры их использованияОператор match/case в Python 3.10+ — основы структурного сопоставленияПаттерны match/case в Python — деструктуризация, guard и вложенные шаблоныПрактические примеры match/case в Python — реальные сценарии примененияЛокальные и глобальные переменные в PythonЧасто используемые команды PythonКлючевые слова global и nonlocal в PythonКак создавать функции в PythonКак работает сборщик мусора в PythonКак работает область видимости переменных в PythonКак работает функция callable в PythonКак работает функция any и all в PythonКак проверить тип переменной в PythonКак передать функцию как аргумент в PythonКак использовать функцию isinstance в PythonКак использовать функцию filter в PythonКак использовать функцию filter в PythonКак использовать функцию eval безопасно в PythonКак использовать декораторы в PythonИзменяемые и неизменяемые типы данных в PythonГенераторы и yield в Python — как создавать и использоватьГенераторные выражения в Python — синтаксис и примерыФункции в Python и способы их вызоваФункции как объекты в PythonЧто такое замыкания в PythonЧто делает функция reduce в PythonЧто делает функция id в PythonАргументы по умолчанию в PythonАнонимные функции и lambda в PythonАлгоритмы на Python — примеры и объяснение
Запись данных в PythonУстановка pip в PythonУправление зависимостями requirement в PythonУправление библиотеками с помощью Python PackagingУдаление пробелов с помощью strip в PythonСтруктурирование кода в PythonСоздание исполняемого файла Python в exeРазбор traceback в модуле PythonРазбор site-packages в PythonРазбор Program Files в PythonРабота с Unicode кодировками в PythonРабота с системными функциями Python sysРабота с папкой AppData в PythonРабота с модулем logging в PythonРабота с каталогами в PythonРабота с CSV в PythonВиртуальная среда venv в Python — создание и настройкаКак создать простое приложение на PythonИспользование pip в Python для установки пакетовМодули в Python и организация кода в проектеИмпорт модулей в Python и правила подключенияРабота с файлами в Python пошаговоЧто делает компилятор Python и как он работаетПолучение строки из модуля PythonПодключение файлов в Python с includeПеременные среды в PythonСборка проекта с помощью packaging в PythonНастройка Python сервераИспользование Python на UbuntuИспользование консоли PythonИспользование кодировок в PythonИнициализация пакетов PythonИмпорт модулей PythonИмпорт имен в PythonСреда IDLE Python и базовые возможностиЧтение и запись TXT в PythonЧтение файлов в Python с помощью open file
Удаление элементов из списка PythonТипы данных в Python — обзор и рекомендацииОсновные операции со строками в PythonМетоды str в Python и обработка текстаСписки в Python и их ключевые методыСоздание списков данных в PythonРабота со строками и символами в PythonРабота со столбцами в PythonРабота со списком значений в PythonРабота с таблицами в Python с помощью DataFrameРабота с RFR в PythonРабота с пробелами в PythonРабота с массивами в PythonРабота с кортежами tuple PythonРабота с координатами X и Y в PythonРабота с ключами в PythonРабота с элементами данных PythonРабота с двоичными числами PythonРабота с данными NumPy PythonРабота с данными в PythonРабота с большими числами в PythonРабота с битами в PythonРабота с байтами в PythonЧто такое значение в Python и как его определитьМножества в Python и операции с нимиИспользование range в Python для цикловПроверка на четность в PythonПроверка числа в PythonПреобразование типов в PythonПреобразование списка в строку PythonПреобразование числа в строку в PythonПостроение графиков в PythonОпределение индекса элемента в PythonОкругление чисел в PythonОбъединение списков в Python с помощью zipМножества в PythonМассивы в Python и отличие от списковМассив чисел в PythonКортежи данных в PythonКак вычислить сумму чисел в PythonКак получить остаток от деления в PythonКак найти следующее число в PythonИспользование Unicode в PythonТип int в Python и его особенностиИндекс списка в PythonФункции для работы со строками в PythonЭлементы Python и способы доступа к нимДоступ к элементам массива в PythonДеление чисел в PythonРабота с данными в Python на практикеКак работать с числами в Python
Открыть базу знаний

Лучшие курсы по теме

Иконка молнииНовый
изображение курса

Основы Python

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий