На этом шаге мы рассмотрим особенности использования перечисленных методов.
До сего момента все пояснения были весьма теоретизированными. Более того, понятно, что важно на практике развить интуитивное понимание того, как эти типы методов различаются. Именно поэтому теперь мы пробежимся по нескольким примерам.
Взглянем на то, как эти методы себя ведут в действии, когда мы их вызываем. Начнем с создания экземпляра класса, а затем вызовем три определенных в нем разных метода.
Класс MyClass был создан так, чтобы реализация каждого метода возвращала кортеж, содержащий информацию, которую мы можем использовать, чтобы проследить, что происходит и к каким частям класса или объекта этот метод может получить доступ.
Вот что происходит, когда мы вызываем метод экземпляра:
>>> obj = MyClass()
>>> obj.method()
('вызван метод экземпляра', <__main__.MyClass object at 0x000002BE18808220>)
Этот результат подтверждает, что в данном случае метод экземпляра с именем method() имеет доступ к экземпляру объекта (напечатанному как <__main__.MyClass object>) через аргумент self.
Во время вызова этого метода Python заменяет аргумент self на объект экземпляра, obj. Чтобы получить тот же самый результат, мы можем проигнорировать синтаксический сахар, предоставляемый синтаксической конструкцией вызова с точкой, obj.method(), и передать объект экземпляра вручную:
>>> MyClass.method(obj)
('вызван метод экземпляра', <__main__.MyClass object at 0x000002BE18808220>)
Между прочим, методы экземпляра могут также получать доступ непосредственно к самому классу через атрибут self.__class__. Это делает методы экземпляра мощными с точки зрения ограничений доступа - они могут свободно модифицировать состояние в экземпляре объекта и в самом классе.
Теперь давайте испытаем метод класса:
>>> obj.classmethod()
('вызван метод класса', <class '__main__.MyClass'>)
Вызов метода classmethod() показал, что у него нет доступа к объекту <__main__.MyClass object>, а есть только к объекту <class '__main__.MyClass'>, представляющему сам класс (в Python все является объектом, даже сами классы).
Обратите внимание на то, как Python автоматически передает класс в качестве первого аргумента в функцию, когда мы вызываем метод MyClass.classmethod(). В Python такое поведение запускается вызовом метода через точечный синтаксис (dot syntax). Параметр self в методах экземпляра работает таким же образом.
Также обратите внимание на то, что обозначение этих параметров как self и cls является всего-навсего согласованным правилом именования. С тем же успехом вы можете назвать их the_object и the_class и получить тот же самый результат. Важно лишь то, что в списке параметров для этого конкретного метода они располагаются первыми.
Теперь самое время вызвать статический метод:
>>> obj.staticmethod()
'вызван статический метод'
Заметили, как мы вызвали метод staticmethod() объекта и смогли сделать это успешно? Некоторые разработчики удивляются, когда узнают, что статический метод можно вызывать на экземпляре объекта.
За кадром, когда статический метод вызывается с использованием точечного синтаксиса, Python просто накладывает ограничения доступа, не передавая аргумент self или cls.
Этим подтверждается, что статические методы не могут получить доступ ни к состоянию экземпляра объекта, ни к состоянию класса. Они работают как обычные функции, но при этом они принадлежат пространству имен класса (и каждого экземпляра).
Теперь давайте посмотрим, что произойдет при попытке вызвать эти методы на самом классе, не создавая экземпляр объекта заранее:
>>> MyClass.classmethod() ('вызван метод класса', <class '__main__.MyClass'>) >>> MyClass.staticmethod() 'вызван статический метод' >>> MyClass.method() Traceback (most recent call last): . . . . MyClass.method() TypeError: method() missing 1 required positional argument: 'self'
Мы нормально смогли вызвать classmethod() и staticmethod(), а вот попытка вызвать метод экземпляра method() не удалась с исключением TypeError.
Такого результата следовало ожидать. На этот раз мы не создали экземпляр объекта и попытались вызвать функцию экземпляра непосредственно на самом шаблоне класса. Иными словами, в Python нет способа заполнить аргумент self, и поэтому данный вызов терпит неудачу с исключением TypeError.
Это должно сделать различие между этими тремя типами методов чуть яснее. В следующих шагах мы приведем более реалистичные примеры, которые покажут, когда использовать эти конкретные типы методов.
В этих примерах будем исходить из этого элементарного класса Pizza:
>>> class Pizza: def __init__(self, ingredients): self.ingredients = ingredients def __repr__(self): return f'Pizza({self.ingredients!r})' >>> Pizza(['сыр', 'помидоры']) Pizza(['сыр', 'помидоры'])
На следующем шаге мы продолжим изучение этого вопроса.