Сообщить об ошибке.

Что такое статический метод в классах Python и зачем нужен

Начнем с примера простого класса, который содержит обычный метод и статический метод класса:

# файл `test.py`
class MyClass:
    def method(self):
        return 'instance method called', self

    @staticmethod
    def mystaticmethod():
        return 'static method called'

Как работают статические методы класса в Python?

В Python, статические методы класса отмечаются декоратором @staticmethod, следовательно в приведенном выше примере, статический метод класса будет определен в функции mystaticmethod().

Этот тип метода не принимает ни параметра self как метод экземпляра класса, ни параметра cls как метод класса. При этом, конечно, статический метод может принимать произвольное количество других параметров.

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

# запускаем 
# $ python3 -i test.py
>>> obj = MyClass()
>>> obj.method()
# ('instance method called', <__main__.MyClass object at 0x7f9748403f40>)

# вызываем статический метод
>>> obj.mystaticmethod()
# 'static method called'

Некоторые разработчики удивляются, когда узнают, что для экземпляра объекта можно вызвать статический метод. За кулисами Python просто применяет ограничение доступа, не передавая аргумент self или cls, когда статический метод вызывается с использованием синтаксиса точной нотации.

Это подтверждает, что статические методы не могут получить доступ ни к состоянию экземпляра объекта, ни к состоянию класса. Они работают как обычные функции, но принадлежат пространству имен класса (каждого его экземпляра).

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

>>> MyClass.mystaticmethod()
# 'static method called'
>>> MyClass.method()
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: method() missing 1 required positional argument: 'self'

Видно, что нормально вызвать можно только .mystaticmethod(), попытка вызвать метод экземпляра method() не удалась из-за ошибки TypeError. Этого следовало ожидать, т. к. экземпляр объекта не был создан. Это означает, что Python не может заполнить аргумент self и следовательно, вызов не выполняется.

Для чего нужны статические методы класса в Python?

Рассмотрим пример класса Date, имеющего дело с информацией о дате из материала о методах класса.

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

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('.'))
        date1 = cls(day, month, year)
        return date1

    def string_to_db(self):
        return f'{self.year}-{self.month}-{self.day}'


# создадим экземпляр класса из строки с датой
# за это отвечает метод класса `from_string()`
>>> date = Date.from_string('30.12.2020')
>>> date.string_to_db()
# '2020-12-30'

А что если строка будет не того формата, который ждет метод класса from_string()? Посыпятся ошибки? Программа сломается? Что делать? Обернуть создание экземпляра в try/except и ловить ошибку или проще проверить строку с датой на валидность формата?

И так, есть строка даты, которую необходимо каким-то образом проверять и эта задача логически связана с классом Date. Вот где статический метод может быть полезен.

Добавим статический метод проверки валидности строки с датой в класс Date:

class Date(object):
    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year
        
    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('.'))
        date1 = cls(day, month, year)
        return date1
    
    @staticmethod
    def is_date_valid(date_as_string):
        # не полный пример проверки валидности
        # и приведен чисто в учебных целях
        if date_as_string.count('.') == 2:
            day, month, year = map(int, date_as_string.split('.'))
            return day <= 31 and month <= 12 and year <= 3999
        
    def string_to_db(self):
        return f'{self.year}-{self.month}-{self.day}'

Как видно из кода статического метода is_date_valid(), у него нет никакого доступа к тому, что такое класс - это в основном просто функция, синтаксически называемая как метод, но без доступа к объекту и его внутренним элементам (атрибутам и другим методам).

Использование класса Date(), в частности, его статического метода:

# список строк с датами
dates = [
    '30.12.2020', 
    '30-12-2020',
    '01.01.2021', 
    '12.31.2020'
    ]

for string_date in dates:
    # проверяем валидность строки с датой
    if Date.is_date_valid(string_date):
        # если все нормально, то создаем 
        #экземпляр из этой строки
        date = Date.from_string(string_date)
        # далее делаем, что-то с экземпляром
        string_to_db = date.string_to_db()
        print(string_to_db)
    else: 
        print(f'Неправильная дата или формат строки с датой')    
    
# 2020-12-30
# Неправильная дата или формат строки с датой
# 2020-1-1
# Неправильная дата или формат строки с датой