Точное объяснение Армина Ронахера, приведенное выше, с подробным изложением его ответов, чтобы новички вроде меня хорошо его поняли:
Разница в методах, определенных в классе, будь то статический метод или метод экземпляра (есть еще один тип - метод класса - здесь не обсуждается, поэтому его пропускают), заключается в том, связаны ли они каким-либо образом с экземпляром класса или нет , Например, скажите, получает ли метод ссылку на экземпляр класса во время выполнения
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 вызывает метод CPO __get__()
для доступа к содержащемуся в нем значению.
Чтобы определить, является ли 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 (это может быть экземпляр list, str, function и т. Д.) И предоставляется средой выполнения
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 в 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>
Вариант использования статического метода - это больше пространство имен и возможность сопровождения кода (удаление его из класса и обеспечение его доступности в модуле и т. Д.).
Возможно, лучше по возможности писать статические методы, а не методы экземпляра, когда это возможно, если, конечно, вам не нужно контекстуализировать методы (такие как переменные экземпляра доступа, переменные класса и т. Д.). Одна из причин - облегчить сборку мусора, не сохраняя ненужные ссылки на объекты.