За счёт какого механизма функция, объявленная в классе, при доступе через экземпляр превращается в связанный метод?
Функция, размещённая в классе, является дескриптором: у неё есть метод __get__. При обращении через экземпляр этот метод создаёт связанный метод, который хранит экземпляр в качестве self и передаёт его первым аргументом при вызове.
При обращении через класс функция обычно возвращается без привязки к экземпляру, поэтому аргумент self нужно передать явно.
Такой механизм появился как часть единой модели доступа к атрибутам Python. Он позволяет реализовать методы не отдельным специальным правилом, а через общий протокол дескрипторов, который также используется для свойств, методов класса и статических методов.
Благодаря этому поведение атрибута зависит от контекста доступа: один и тот же объект функции может возвращаться напрямую или оборачиваться в связанный метод.
Функция в теле класса хранится как атрибут класса, а не как отдельная копия в каждом экземпляре. При этом вызов через экземпляр должен автоматически получать ссылку на конкретный объект.
Если не учитывать связывание, вызов метода потребовал бы вручную передавать экземпляр. Непонимание этого механизма приводит к ошибкам при работе с функциями класса, ручном вызове методов и создании собственных дескрипторов.
При поиске атрибута экземпляра Python находит функцию в классе или в одном из классов его MRO. Поскольку функция реализует протокол дескриптора, Python вызывает её __get__, передавая экземпляр и класс-владелец.
Если доступ выполняется через экземпляр, например obj.method, функция возвращает объект связанного метода. В нём сохранены исходная функция, доступная через __func__, и экземпляр, доступный через __self__. При вызове связанного метода экземпляр автоматически становится первым аргументом.
При доступе через класс, например Class.method, экземпляр отсутствует. В этом случае функция возвращается без привязки, и вызывающий код должен сам передать объект, если функция ожидает self.
Обе первые строки дают одинаковый результат. В первом случае self подставляется при получении user.greet, во втором экземпляр передаётся явно через доступ к функции класса.
Функции являются не-data-дескрипторами: они реализуют __get__, но не реализуют __set__ или __delete__. Это отличает их, например, от обычного свойства с сеттером и влияет на приоритет атрибутов экземпляра при поиске.
Декораторы staticmethod и classmethod изменяют это поведение. staticmethod отключает автоматическую передачу экземпляра, а classmethod связывает функцию с самим классом, передавая его первым аргументом.
В ORM-классе метод запроса должен работать как с конкретным объектом, так и с классом-моделью. Разработчик попытался хранить одну и ту же функцию отдельно в каждом экземпляре, чтобы вручную передавать объект.
Вариант с копированием функции в каждый экземпляр увеличивает расход памяти и обходит стандартный механизм поиска атрибутов. Вариант с staticmethod экономит память, но лишает функцию автоматического доступа к экземпляру.
Выбран обычный метод класса: функция хранится один раз в классе, а при доступе через экземпляр автоматически получает нужный объект. Для операций, которым нужен класс, применён classmethod, поскольку он связывает функцию с классом, а не с конкретным экземпляром.
Результат: сохраняется единая реализация методов, уменьшается дублирование и явно выражается, какой контекст нужен каждой операции.
Связанный метод хранит ссылку на исходную функцию в __func__ и на объект, к которому он привязан, в __self__. Поэтому это не новая независимая функция и не копия тела метода, а объект-обёртка с фиксированным первым аргументом.
Не обязательно. Каждое получение метода через экземпляр может создать новый объект связанного метода, хотя он будет ссылаться на ту же функцию и тот же экземпляр. Поэтому сравнивать такие значения по идентичности через is обычно нельзя; содержательно они могут иметь одинаковые __func__ и __self__.
При доступе через класс экземпляр для дескриптора отсутствует, поэтому автоматического связывания не происходит. Выражение Class.method возвращает функцию в несвязанном виде, и вызов через неё должен явно передать объект первым аргументом. Это позволяет использовать одну функцию как обычный атрибут класса и сохраняет возможность явного вызова без дополнительной магии.