Точное объяснение от Армина Ронахера выше, расширяя его ответы, чтобы новички вроде меня хорошо поняли:
Разница в методах, определенных в классе, будь то статический метод или метод экземпляра (есть еще один тип - метод класса - здесь не обсуждается, поэтому его пропускают), заключается в том, связаны ли они каким-либо образом с экземпляром класса или нет. Например, скажите, получает ли метод ссылку на экземпляр класса во время выполнения
class C:
a = []
def foo(self):
pass
C # this is the class object
C.a # is a list object (class property object)
C.foo # is a function object (class property object)
c = C()
c # this is the class instance
Свойство __dict__словаря объекта класса содержит ссылку на все свойства и методы объекта класса и, таким образом,
>>> C.__dict__['foo']
<function foo at 0x17d05b0>
метод foo доступен, как указано выше. Здесь важно отметить, что все в python является объектом, и поэтому ссылки в словаре выше сами указывают на другие объекты. Позвольте мне назвать их Объектами Свойств Класса - или как CPO в рамках моего ответа для краткости.
Если CPO является дескриптором, то интерпретатор python вызывает __get__()метод CPO для доступа к значению, которое он содержит.
Чтобы определить, является ли CPO дескриптором, интерпретатор python проверяет, реализует ли он протокол дескриптора. Для реализации дескриптора протокола необходимо реализовать 3 метода
def __get__(self, instance, owner)
def __set__(self, instance, value)
def __delete__(self, instance)
например
>>> C.__dict__['foo'].__get__(c, C)
где
self является CPO (это может быть экземпляр списка, str, функции и т. д.) и предоставляется средой выполнения
instance является экземпляром класса, в котором определен этот CPO (объект 'c' выше), и он должен быть предоставлен нами для простоты
ownerэто класс, где этот CPO определен (объект класса 'C' выше) и должен быть предоставлен нами. Однако это потому, что мы называем это в СРО. когда мы вызываем его в экземпляре, нам не нужно указывать его, поскольку среда выполнения может предоставить экземпляр или его класс (полиморфизм)
value является предполагаемым значением для CPO и должно быть предоставлено нами
Не все CPO являются дескрипторами. Например
>>> C.__dict__['foo'].__get__(None, C)
<function C.foo at 0x10a72f510>
>>> C.__dict__['a'].__get__(None, C)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__get__'
Это потому, что класс списка не реализует протокол дескриптора.
Таким образом, аргумент self in c.foo(self)необходим, потому что его сигнатура метода на самом деле такова C.__dict__['foo'].__get__(c, C)(как объяснено выше, C не нужен, так как его можно обнаружить или преобразовать). Именно поэтому вы получаете TypeError, если вы не передаете требуемый аргумент экземпляра.
Если вы заметили, что на метод все еще ссылаются через класс Object C, и связывание с экземпляром класса достигается посредством передачи контекста в форме объекта экземпляра в эту функцию.
Это довольно круто, поскольку, если вы решили не сохранять контекст или не привязываться к экземпляру, все, что нужно, - это написать класс, чтобы обернуть дескриптор CPO и переопределить его __get__()метод, не требуя контекста. Этот новый класс является тем, что мы называем декоратором, и применяется через ключевое слово@staticmethod
class C(object):
@staticmethod
def foo():
pass
Отсутствие контекста в новом обернутом CPO fooне приводит к ошибке и может быть проверено следующим образом:
>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>
Вариант использования статического метода - это скорее пространство имен и возможность сопровождения кода (удаление его из класса и обеспечение его доступности в модуле и т. Д.).
Возможно, лучше по возможности писать статические методы, а не методы экземпляра, если, конечно, вам не нужно контекстуализировать методы (такие как переменные экземпляра доступа, переменные класса и т. Д.). Одна из причин - облегчить сборку мусора, не сохраняя ненужные ссылки на объекты.