W3docs

Python @staticmethod и @classmethod

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

Python предоставляет каждому методу внутри класса один из трёх стилей привязки: он может быть привязан к экземпляру, к самому классу или ни к чему. Декораторы @classmethod и @staticmethod управляют двумя последними стилями.

В этой главе рассматриваются:

  • Три типа методов и их различия
  • @staticmethod — обычная функция, хранящаяся внутри класса
  • @classmethod — метод, получающий класс в качестве первого аргумента
  • Фабричные методы: наиболее распространённое практическое применение @classmethod
  • Альтернативные конструкторы и их взаимодействие с наследованием
  • Когда выбирать @staticmethod, @classmethod или функцию на уровне модуля
  • Распространённые подводные камни

Перед чтением убедитесь, что вы знакомы с классами и объектами Python и наследованием Python. Для вычисляемого доступа к атрибутам смотрите @property. Для подробного изучения того, как работают декораторы в целом, смотрите Декораторы Python.

Три типа методов

Прежде чем рассмотреть каждый декоратор, вот сравнение бок о бок:

Метод экземпляра@classmethod@staticmethod
Первый параметрself (экземпляр)cls (класс)нет
Получает экземпляр?ДаНетНет
Получает класс?Через type(self)Да (напрямую)Нет
Вызывается на экземпляреДаДаДа
Вызывается на классеДа (но self отсутствует)ДаДа
Типичное применениеРабота с данными экземпляраФабричные методы, состояние на уровне классаУтилиты и вспомогательные функции
class Demo:
    def instance_method(self):
        return f"instance method — self is {self}"

    @classmethod
    def class_method(cls):
        return f"class method — cls is {cls}"

    @staticmethod
    def static_method():
        return "static method — no self, no cls"

d = Demo()
print(d.instance_method())   # instance method — self is <__main__.Demo object at 0x...>
print(d.class_method())      # class method — cls is <class '__main__.Demo'>
print(d.static_method())     # static method — no self, no cls

# All three can also be called directly on the class:
print(Demo.class_method())   # class method — cls is <class '__main__.Demo'>
print(Demo.static_method())  # static method — no self, no cls

@staticmethod

Статический метод — простейший из трёх. Это обычная функция, которая просто находится в пространстве имён класса. Python не передаёт self или cls автоматически.

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

    @staticmethod
    def is_even(n):
        return n % 2 == 0

print(MathUtils.add(3, 4))   # 7
print(MathUtils.is_even(10)) # True

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

Используйте @staticmethod, когда вспомогательная функция логически принадлежит классу — для ясности, группировки или разграничения пространства имён — но не нуждается в чтении или изменении состояния экземпляра или класса:

  • Вспомогательные функции валидации, вызываемые до создания объекта.
  • Чистые функции преобразования или вычисления, значимые только в контексте одного класса.
  • Утилиты, используемые несколькими методами одного класса, но нигде больше.
class Temperature:
    def __init__(self, celsius):
        if not Temperature._is_valid(celsius):
            raise ValueError(f"Temperature {celsius} °C is below absolute zero")
        self.celsius = celsius

    @staticmethod
    def _is_valid(celsius):
        return celsius >= -273.15

    @staticmethod
    def celsius_to_fahrenheit(celsius):
        return celsius * 9 / 5 + 32

t = Temperature(100)
print(Temperature.celsius_to_fahrenheit(100))  # 212.0
print(Temperature._is_valid(-300))             # False

Обратите внимание, что _is_valid имеет префикс _, сигнализирующий о том, что метод является внутренним для класса. Вызывающие стороны, которым нужны только объекты Temperature, никогда его не видят — они просто получают ValueError, если передают невозможное значение.

@staticmethod vs функция на уровне модуля

Функция на уровне модуля и @staticmethod почти идентичны по поведению. Разница заключается в том, где находится функция:

  • Если функция относится только к Temperature (или вызывается исключительно из Temperature), поместите её внутрь класса как @staticmethod.
  • Если это общая утилита, используемая по всему модулю, поместите её на уровне модуля.

Разницы в производительности нет. Это исключительно вопрос организации кода.

@classmethod

Метод класса получает класс в качестве первого аргумента (по соглашению называемого cls — но, как и self, это соглашение, а не ключевое слово). Поскольку он имеет ссылку на класс, он может:

  • Читать или изменять атрибуты уровня класса.
  • Создавать и возвращать новые экземпляры класса (фабричные методы).
  • Корректно работать с подклассами (полиморфные фабрики).
class Counter:
    _count = 0  # class-level attribute

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

    @classmethod
    def get_count(cls):
        return cls._count

    @classmethod
    def reset(cls):
        cls._count = 0

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

Фабричные методы — наиболее важный случай применения

Наиболее распространённым и ценным применением @classmethod является фабричный метод (также называемый альтернативным конструктором). Фабричный метод создаёт экземпляры из различных видов входных данных, не загромождая __init__ условной логикой.

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __repr__(self):
        return f"Date({self.year}, {self.month}, {self.day})"

    @classmethod
    def from_string(cls, date_string):
        """Create a Date from an ISO 8601 string, e.g. '2024-03-15'."""
        year, month, day = (int(p) for p in date_string.split("-"))
        return cls(year, month, day)

    @classmethod
    def from_tuple(cls, date_tuple):
        """Create a Date from a (year, month, day) tuple."""
        return cls(*date_tuple)

d1 = Date(2024, 3, 15)
d2 = Date.from_string("2024-03-15")
d3 = Date.from_tuple((2024, 3, 15))

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

__init__ остаётся простым — он лишь хранит три целых числа. Методы класса берут на себя логику преобразования. Это чище, чем единственный __init__ с несколькими необязательными параметрами и ветками if/elif.

Почему cls важен для наследования

Когда фабричный метод класса вызывает cls(...) вместо жёсткого кодирования имени класса, он создаёт экземпляр того класса, для которого был вызван метод — даже подкласса. Именно поэтому внутри @classmethod всегда следует предпочитать cls(...) вместо ИмяКласса(...).

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __repr__(self):
        return f"{type(self).__name__}({self.year}, {self.month}, {self.day})"

    @classmethod
    def from_string(cls, date_string):
        year, month, day = (int(p) for p in date_string.split("-"))
        return cls(year, month, day)  # uses cls, not Date


class DateTime(Date):
    pass  # inherits from_string


dt = DateTime.from_string("2024-03-15")
print(dt)           # DateTime(2024, 3, 15)  — correct subclass
print(type(dt))     # <class '__main__.DateTime'>

Если бы from_string жёстко задавал return Date(year, month, day), вызов DateTime.from_string(...) возвращал бы Date, а не DateTime — тихо нарушая контракт наследования.

Изменение состояния на уровне класса

Методы класса также могут выступать как именованные конструкторы с побочными эффектами или манипулировать переменными класса, отслеживающими общее состояние:

class Registry:
    _instances = []

    def __init__(self, name):
        self.name = name
        Registry._instances.append(self)

    @classmethod
    def all(cls):
        return list(cls._instances)

    @classmethod
    def clear(cls):
        cls._instances.clear()

Registry("alice")
Registry("bob")
Registry("carol")
print([r.name for r in Registry.all()])  # ['alice', 'bob', 'carol']
Registry.clear()
print(Registry.all())                    # []

Вызов на экземпляре и на классе

Как @staticmethod, так и @classmethod можно вызывать и на экземпляре, и на классе. Python поддерживает обе формы:

class Circle:
    PI = 3.14159265

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return Circle.PI * self.radius ** 2

    @classmethod
    def unit_circle(cls):
        """Return a circle with radius 1."""
        return cls(1)

    @staticmethod
    def describe():
        return "A circle is a round plane figure."

c = Circle(5)

# staticmethod — callable on instance or class
print(c.describe())          # A circle is a round plane figure.
print(Circle.describe())     # A circle is a round plane figure.

# classmethod — callable on instance or class
unit = c.unit_circle()
print(unit.radius)           # 1
print(Circle.unit_circle().radius)  # 1

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

Совместное использование @classmethod и @staticmethod

Метод класса может делегировать работу по валидации статическому методу, поскольку у метода класса есть доступ к cls для его вызова:

class PositiveNumber:
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return f"PositiveNumber({self.value})"

    @staticmethod
    def _validate(value):
        if value <= 0:
            raise ValueError(f"Expected a positive number, got {value!r}")

    @classmethod
    def create(cls, value):
        cls._validate(value)
        return cls(value)

n = PositiveNumber.create(42)
print(n)  # PositiveNumber(42)

try:
    PositiveNumber.create(-5)
except ValueError as e:
    print(e)  # Expected a positive number, got -5

Быстрый справочник: какой декоратор выбрать?

СитуацияРекомендация
Метод читает или записывает selfОбычный метод экземпляра
Метод создаёт новый экземпляр@classmethod (фабричный / альтернативный конструктор)
Метод читает или записывает атрибут класса@classmethod
Метод является чистой вспомогательной функцией без данных класса или экземпляра@staticmethod (или функция на уровне модуля)
Метод валидирует входные данные перед созданием объекта@staticmethod
Метод должен корректно работать в подклассах@classmethod (используйте cls, а не жёстко заданное имя класса)

Распространённые подводные камни

Забытый cls в @classmethod

Если вы жёстко кодируете имя класса вместо использования cls, наследование тихо ломается:

class Animal:
    @classmethod
    def create(cls):
        return cls()          # correct — returns an instance of the actual class

class Dog(Animal):
    pass

print(type(Dog.create()))     # <class '__main__.Dog'>  — correct

Внутри метода класса всегда используйте cls(...), никогда Animal(...).

Обращение к self или cls в @staticmethod

@staticmethod не получает неявного первого аргумента. Попытка обратиться к self или cls внутри него является ошибкой:

class Bad:
    label = "bad"

    @staticmethod
    def show():
        # print(cls.label)  # NameError: name 'cls' is not defined
        print("use @classmethod if you need cls")

Bad.show()  # use @classmethod if you need cls

Если вам нужен cls в том, что вы считали статическим методом, переключите его на @classmethod.

Путаница с декораторами

Методы @classmethod должны иметь cls в качестве первого явного параметра, а методы @staticmethodни одного. Их перепутывание вызывает TypeError во время вызова, а не при определении — что может удивить:

class Broken:
    @staticmethod
    def forgot_cls(cls):   # cls is just a regular positional argument here
        return cls

# Broken.forgot_cls()  # TypeError: forgot_cls() missing 1 required positional argument: 'cls'

Переопределение в подклассах

Оба декоратора работают с super() и могут быть переопределены:

class Base:
    @classmethod
    def who(cls):
        return f"Base.who called with cls={cls.__name__}"

class Child(Base):
    @classmethod
    def who(cls):
        parent = super().who()
        return f"Child.who — parent said: {parent}"

print(Child.who())
# Child.who — parent said: Base.who called with cls=Child

Обратите внимание, что cls в Base.who всё равно равен Child — потому что метод был вызван через Child.

Практический пример: класс User

Вот полный пример, объединяющий методы экземпляра, фабричный метод класса и статический метод-валидатор:

import re

class User:
    _all_users = []

    def __init__(self, name, email):
        User._validate_email(email)
        self.name = name
        self.email = email
        User._all_users.append(self)

    def __repr__(self):
        return f"User(name={self.name!r}, email={self.email!r})"

    # --- instance method ---
    def greet(self):
        return f"Hello, my name is {self.name}."

    # --- factory / alternative constructor ---
    @classmethod
    def from_dict(cls, data):
        """Create a User from a dict like {'name': 'Alice', 'email': '[email protected]'}."""
        return cls(data["name"], data["email"])

    # --- class-level query ---
    @classmethod
    def count(cls):
        return len(cls._all_users)

    # --- pure helper, no instance or class data needed ---
    @staticmethod
    def _validate_email(email):
        pattern = r"^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$"
        if not re.match(pattern, email):
            raise ValueError(f"Invalid email address: {email!r}")

# Create via normal constructor
u1 = User("Alice", "[email protected]")

# Create via factory
u2 = User.from_dict({"name": "Bob", "email": "[email protected]"})

print(u1.greet())    # Hello, my name is Alice.
print(u2.greet())    # Hello, my name is Bob.
print(User.count())  # 2

try:
    User("Carol", "not-an-email")
except ValueError as e:
    print(e)         # Invalid email address: 'not-an-email'

Этот шаблон — __init__ для обычного создания объектов, @classmethod для альтернативных конструкторов, @staticmethod для вспомогательных функций — встречается по всей стандартной библиотеке Python (см. datetime.date.today(), datetime.date.fromisoformat(), int.from_bytes()).

Резюме

  • Метод экземпляра получает self и имеет полный доступ к состоянию объекта.
  • @classmethod получает cls — сам класс — вместо экземпляра. Используйте его для фабричных методов и всего, что работает с состоянием на уровне класса. Всегда используйте cls(...) внутри него, чтобы подклассы работали корректно.
  • @staticmethod не получает ни self, ни cls. Используйте его для чистой утилитарной логики, которая принадлежит пространству имён класса, но не нуждается в данных объекта или класса.

Для вычисляемых атрибутов, выглядящих как обычный доступ к атрибутам, смотрите @property. Для полного механизма декораторов, лежащего в основе всех трёх, смотрите Декораторы Python.

Was this page helpful?