Декораторы @classmethod и @staticmethod

Приветствую всех на внеочередном занятии по ООП. Меня часто спрашивают: чем отличаются обычные методы класса от методов, объявленных декораторами:

@classmethod и @staticmethod

Давайте разбираться. На уровне теории это выглядит так. Предположим, у нас есть класс Vector, который содержит два атрибута: MIN_COORD = 0, MAX_COORD = 100; обычный метод setCoords; метод уровня класса: validate и статический метод – norm2:

Так вот, все обычные методы в качестве первого параметра обязательно должны получать ссылку на экземпляр этого класса. И благодаря ей, они могут менять состояние как объекта класса, так и получать доступ к свойствами и методам самого класса Vector.

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

Наконец, третий тип методов, объявленные через декоратор @staticmethod, являются, своего рода, совершенно изолированными и от свойств и методов класса и, уж тем более, от свойств и методов экземпляра. Их можно воспринимать как отдельные, самостоятельные функции, объявленные внутри класса. Обычно, это делают для удобства, т.к. их функционал так или иначе связан с тематикой класса.

Итак, давайте на практике посмотрим, что все это значит. Я объявлю класс Vector вот в таком виде:

class Vector:
    MIN_COORD = 0
    MAX_COORD = 100
 
    def setCoords(self, x, y):
        if Vector.validate(x) and Vector.validate(y):
            self.x = x
            self.y = y
 
    @classmethod
    def validate(cls, arg):
        if arg >= cls.MIN_COORD and arg <= cls.MAX_COORD:
            return True
        return False
 
    @staticmethod
    def norm2(x, y):
        return x*x + y*y

И для удобства открою консоль Python, где импортирую этот класс из модуля ex1:

from ex1 import Vector

Далее, смотрите, непосредственно через класс Vector можно вызвать метод класса:

Vector.validate(5)

или, статический метод:

Vector.norm2(1, 2)

Но нельзя вызвать обычный метод:

Vector.setCoords(1, 2)

Так как здесь первым параметром нужно указывать ссылку на экземпляр класса self, из которого подразумевается вызов этого метода. У нас же, нет никакого объекта, только сам класс, отсюда и возникает такая ошибка. Давайте его создадим:

v = Vector()

Тогда можно вызвать setCoords, либо через экземпляр класса:

v.setCoords(1, 2)

либо, через сам класс, но уже с явной передачей первого параметра – ссылки на ранее созданный объект:

Vector.setCoords(v, 10, 20)

Я думаю, этот момент понятен. И теперь самое главное: зачем это надо и когда какой метод объявлять? В некотором смысле ответ на этот вопрос дает теория, рассмотренная вначале.

  1. Если нам нужен метод, который бы «работал» с атрибутами экземпляров классов, то это однозначно обычный метод класса с первым параметром self, который указывает на текущий объект.
  2. Если нужен метод, который можно вызывать непосредственно из класса (или экземпляра) и, который бы имел доступ к свойствам и методам этого класса, то его следует объявить как метод класса через декоратор @classmethod.
  3. Если нужен метод, который можно вызывать непосредственно из класса, но доступ к его атрибутам не предполагается, то достаточно его объявить как статический через декоратор @staticmethod.

И вот здесь есть один тонкий момент, особенно для тех, кто программирует на других языках, например, C++ или Java. Как бы мы ни объявляли методы внутри класса, экземпляры этих классов, при их вызове, будут обращаться напрямую к этому классу:

То есть, декораторы @classmethod и @staticmethod лишь как бы ограничивают область видимости внутри этих методов, но доступ к ним из вне происходит по одной схеме.

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