Если бы методы класса дублировались в каждом экземпляре, то для списка из 1000 000 int
'ов было бы по 1 млн копий каждого метода положенного типу int
, на деле же каждый экземпляр имеет ссылку на тип/класс int
, через которую получает доступ к общим методам.
В Python всё является объектом, в том числе создаваемые пользователем классы/типы и функции создаваемые с помощью def
/lambda
. Функции написанные в коде тела класса называются методами и представляют собой функции оборачиваемые на лету в дополнительный объект типа Method
(условное название). Принадлежат они объекту класса, а не его экземплярам.
Объект-обёртка позволяет при вызове метода неявно передавать первым аргументом объект у которого этот метод вызывается: self
, cls
. В случае обычного метода это сам экземпляр, обычно называемый self
в сигнатуре метода, в случае classmethod
поведение меняется декоратором @classmethod
и первым аргументом передаётся объект класса, который принято называть cls
. staticmethod
сделан так, чтобы при обращении к методу, возвращалась сама функция, без оборачивания в объект типа Method
.
Код функций написанных в юзерском классе на этапе компиляции превращается в code objects, затем на этапе исполнения отрабатывают конструкции def
внутри класса и создаются function objects - заготовки под методы. Ссылками на эти function objects владеет объект-класс и добраться до них можно через его атрибуты, CPython хранит function objects в сегменте heap, как и все остальные объекты исполняемой программы. Одна функция внутри тела класса соответствует одному объекту в heap'е. Экземпляры же получают доступ к этому объекту через класс родитель. И для 100, и для 1000 экземпляров будет 1 function object в heap'e.
Пример:
Конструкция class MyClass
в коде ведёт к созданию объекта MyClass
. Имена в теле класса MyClass
становятся его атрибутами. При создании экземпляра obj = MyClass()
новому объекту obj
записывается ссылка на MyClass
. При вызове у obj
метода - obj.class_body_method(some_arg)
, происходит приблизительно следующее:
поиск атрибута class_body_method
в определённом порядке - у самого экземпляра obj
, в цепочке mro его родителя - MyClass
, в метаклассе - типе от которого происходит MyClass
(обычно это type
). В экземпляре ссылка на метод может храниться только если он был целенаправленно сохранён в качестве атрибута экземпляра.
так как методы класса реализованы при помощи descriptor protocol, после нахождении атрибута/дескриптора class_body_method
, у него вызывается метод __get__
, в котором происходят манипуляции, необходимые для превращения функции в метод определённого типа. В связи с тем, что превращение функции в обычный метод происходит при каждом обращении, id'шники вновь создаваемого метод-объекта будут разные, но не для staticmethod
, так как там оборачивания не происходит и возвращается сама функция, каждый раз один и тот же объект, соответственно и id'шник не меняется.
Демонстрация различных моментов упомянутых в предыдущем тексте
class MyClass:
def class_body_method_1(self):
pass
def class_body_method_2(self, arg2, some_arg=[]):
some_arg.append(arg2)
print(some_arg)
@staticmethod
def class_body_staticmethod():
pass
obj1 = MyClass()
obj2 = MyClass()
obj3 = MyClass()
Смена id'шника при каждом обращении к обычному методу:
In []: id(obj1.class_body_method_1)
Out[]: 140714545102720
In []: id(obj1.class_body_method_1)
Out[]: 140714546939712
Но не в случае @staticmethod:
In []: id(obj1.class_body_staticmethod)
Out[]: 140714331142272
In []: id(obj1.class_body_staticmethod)
Out[]: 140714331142272
Потому что при обращении к обычному методу каждый раз заново создаётся и возвращается объект-метод:
In []: obj1.class_body_method_1
Out[]: <bound method MyClass.class_body_method_1 of <__main__.MyClass object at 0x7ffaa8a0c790>>
Имеющий ссылку на один и тот же объект-функцию:
In []: obj1.class_body_method_1.__func__
Out[]: <function __main__.MyClass.class_body_method_1(self)>
In []: id(obj1.class_body_method_1.__func__)
Out[]: 140714331143568
In []: id(obj1.class_body_method_1.__func__)
Out[]: 140714331143568
А staticmethod просто возвращает саму функцию:
In []: obj1.class_body_staticmethod
Out[]: <function __main__.MyClass.class_body_staticmethod()>
Следствием и доказательством одного функции-объекта для всех экземпляров являются, например, такие вещи (один дефолтный массив на всех):
In []: obj1.class_body_method_2("a")
['a']
In []: obj1.class_body_method_2("b")
['a', 'b']
In []: obj2.class_body_method_2("c")
['a', 'b', 'c']
In []: obj3.class_body_method_2("d")
['a', 'b', 'c', 'd']