При вызове classmethod через подкласс какой класс получает его первым аргументом и почему?
classmethod получает первым аргументом класс, через который выполнен доступ к методу. Поэтому при вызове через подкласс в метод передаётся именно этот подкласс, а не класс, в котором метод был первоначально объявлен.
Это обеспечивается дескриптором classmethod: при доступе к атрибуту он формирует связанный метод, подставляя в него объект-класс, участвующий в обращении.
Обычный метод класса автоматически связывается с экземпляром, что удобно для операций над состоянием конкретного объекта. Однако часть операций относится к самому классу: например, создание экземпляров из альтернативного представления или работа с реестром подклассов.
classmethod предоставляет для таких операций единый механизм вызова без ручной передачи класса. В отличие от вызова обычного метода через экземпляр, первым аргументом становится класс, а не объект.
Если метод должен создавать объект текущего подкласса, жёсткая ссылка на базовый класс нарушит полиморфизм. В результате вызов фабричного метода у подкласса может вернуть экземпляр базового класса, что приведёт к потере переопределённых настроек или поведения.
Важно различать класс, где метод объявлен, и класс, через который к нему обратились. classmethod использует второй вариант, поэтому один унаследованный метод может корректно работать для всей иерархии.
При обращении к атрибуту класса Python находит объект classmethod в пространстве имён класса или его предках. Этот объект реализует протокол дескриптора и при доступе возвращает вызываемый объект, связанный с классом доступа.
Если метод объявлен в Base, но вызывается через Child, первым аргументом получает Child. Это сохраняется и при обращении через экземпляр Child: дескриптор всё равно передаёт класс, а не экземпляр.
Вызов Child.make() не означает, что Python заменяет тело метода или создаёт его копию. Метод остаётся определённым в Base, но при связывании получает другой первый аргумент.
Практическое следствие — фабричные методы должны обычно использовать cls(...), а не имя конкретного базового класса. Ограничение состоит в том, что конструктор инициализации всех предполагаемых подклассов должен поддерживать совместимый интерфейс.
В библиотеке есть базовый класс конфигурации и несколько специализированных подклассов. Метод from_mapping должен создавать объект конкретного вызывающего класса, чтобы результат SpecialConfig.from_mapping(...) был экземпляром SpecialConfig.
Вариант с BaseConfig(...) прост, но нарушает расширяемость: подкласс получает объект базового типа. Ручная передача класса в обычную функцию сохраняет гибкость, однако ухудшает интерфейс и позволяет передать несоответствующий тип.
Выбран classmethod с созданием через cls(...). Такой вариант сохраняет полиморфизм, не требует дублирования фабрики в подклассах и использует стандартный протокол связывания методов. Результат — корректное создание экземпляров подклассов при условии совместимых конструкторов.
Нет. В classmethod первым аргументом остаётся класс экземпляра, через который выполнен доступ. Сам экземпляр автоматически вторым аргументом не добавляется; если он нужен, его можно явно передать при вызове.
Будет передан промежуточный подкласс, через который выполнено обращение. Поиск тела метода и значение первого аргумента — разные этапы: тело может быть найдено в базовом классе, но связывание происходит с классом доступа.
classmethod при доступе создаёт связанный вызываемый объект и автоматически передаёт класс. staticmethod также является дескриптором, но возвращает функцию без автоматической подстановки ни экземпляра, ни класса. Поэтому staticmethod удобен для логики, размещённой в пространстве имён класса, но не нуждающейся в контексте класса.