Python

Согласно модели данных Python, язык предлагает три вида методов: статические, класса и экземпляра класса. Давайте посмотрим, что же происходит за кулисами каждого из видов методов. Понимание принципов их работы поможет в создании красивого и эффективного кода. Начнём с самого простого примера, в котором демонстрируются все три вида методов.

class ToyClass:
    def instancemethod(self):
        return 'instance method called', self
    
    @classmethod
    def classmethod(cls):
        return 'class method called', cls
    @staticmethod
    def staticmethod():
        return 'static method called'

Методы экземпляра класса

Это наиболее часто используемый вид методов. Методы экземпляра класса принимают объект класса как первый аргумент, который принято называть self и который указывает на сам экземпляр. Количество параметров метода не ограничено.

Используя параметр self , мы можем менять состояние объекта и обращаться к другим его методам и параметрам. К тому же, используя атрибут self.__class__ , мы получаем доступ к атрибутам класса и возможности менять состояние самого класса. То есть методы экземпляров класса позволяют менять как состояние определённого объекта, так и класса.

Встроенный пример метода экземпляра — str.upper():

>>> "welcome".upper()   # <- вызывается на строковых данных
'WELCOME'

Методы класса

Методы класса принимают класс в качестве параметра, который принято обозначать как cls. Он указывает на класс ToyClass, а не на объект этого класса. При декларации методов этого вида используется декоратор classmethod.

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

Встроенный пример метода класса — dict.fromkeys()— возвращает новый словарь с переданными элементами в качестве ключей.

dict.fromkeys('AEIOU')  # <- вызывается при помощи класса dict
{'A': None, 'E': None, 'I': None, 'O': None, 'U': None}

Статические методы

Статические методы декларируются при помощи декоратора staticmethod. Им не нужен определённый первый аргумент (ни self, ни cls).

Их можно воспринимать как методы, которые “не знают, к какому классу относятся”.

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


С теорией достаточно. Давайте разберёмся с работой методов, создав объект нашего класса и вызвав поочерёдно каждый из методов: instancemethod, classmethod and staticmethod.

>>> obj = ToyClass()
>>> obj.instancemethod()
('instance method called', ToyClass instance at 0x10f47e7a0>)
>>> ToyClass.instancemethod(obj)
('instance method called', ToyClass instance at 0x10f47e7a0>)

Пример выше подтверждает то, что метод instancemethod имеет доступ к объекту класса ToyClass через аргумент self. Кстати, вызов функции obj.instancemethod() используется лишь для удобства, то есть можно использовать и ToyClass.instancemethod(obj)

Теперь давайте вызовем метод класса:

>>> obj.classmethod()
('class method called', <class ToyClass at 0x10f453a10>)

Мы видим, что метод класса classmethod() имеет доступ к самому классу ToyClass, но не к его конкретному экземпляру объекта. Запомните, в Python всё является объектом. Класс тоже объект, который мы можем передать функции в качестве аргумента.

Заметьте, что self и cls — не обязательные названия и эти параметры можно называть иначе.

def instancemethod(self, ...)
def classmethod(cls, ...)
-------то же самое, что и----------
def instancemethod(my_object, ...)
def classmethod(my_class, ...)

Это лишь общепринятые обозначения, которым следуют все. Тем не менее они должны находиться первыми в списке параметров.

Вызовем статический метод:

>>> obj.staticmethod()
static method called

Да, это может вас удивить, но статические методы можно вызывать через объект класса. Вызов через точку нужен лишь для удобства. На самом же деле в случае статического метода никакие аргументы (self илиcls) методу не передаются.

То есть статические методы не могут получить доступ к параметрам класса или объекта. Они работают только с теми данными, которые им передаются в качестве аргументов.

Теперь давайте вызовем те же самые методы, но на самом классе.

>>> ToyClass.classmethod()
('class method called', <class ToyClass at 0x10f453a10>)
>>> ToyClass.staticmethod()
'static method called'
>>> ToyClass.instancemethod()
TypeError: unbound method instancemethod() 
must be called with ToyClass instance as 
first argument (got nothing instead)

Метод класса и статический метод работают, как нужно. Однако вызов метода экземпляра класса выдаёт TypeError, так как метод не может получить на вход экземпляр класса.

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

from datetime import date
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
@classmethod
    def from_birth_year(cls, name, year):
        return cls(name, date.today().year - year)
@staticmethod
    def is_adult(age):
        return age > 18
person1 = Person('Sarah', 25)
person2 = Person.from_birth_year('Roark', 1994)
>>> person1.name, person1.age
Sarah 25
>>> person2.name, person2.age
Roark 24
>>> Person.is_adult(25)
True

Когда использовать каждый из методов?

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

Чаще всего метод класса используется тогда, когда нужен генерирующий метод, возвращающий объект класса. Как видим, метод класса from_birth_year используется для создания объекта класса Person по году рождения, а не возрасту. 

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

Запомнить

  • Методы экземпляра класса получают доступ к объекту класса через параметр self и к классу через self.__class__.
  • Методы класса не могут получить доступ к определённому объекту класса, но имеют доступ к самому классу через cls.
  • Статические методы работают как обычные функции, но принадлежат области имён класса. Они не имеют доступа ни к самому классу, ни к его экземплярам.
  • Даже если вы программируете только ради интереса, изучение ООП в Python поможет писать код так, чтобы в дальнейшем было легче искать ошибки и использовать его повторно.

Читайте также:


Перевод статьи Rachit Tayal: Python’s Instance, Class and Static Methods