Понимание создания объектов и метаклассов
Last updated
Was this helpful?
Last updated
Was this helpful?
Если вы переходите на Python с другого языка, то вполне нормально чувствовать себя немного запутанным в отношении создания объектов и наследования, особенно когда люди начинают говорить о «метаклассах». В этой статье Рупеш Мишра объясняет, как создание экземпляров, наследование и даже метаклассы работают в Python.
В этой статье мы рассмотрим процесс инстанцирования объектов, за которым следует внутренний Python для создания объектов. Я начну с основ создания объектов, а затем мы углубимся в понимание конкретных методов, таких как __new__, __init__ и __call__. Мы также поймем метакласс в Python и его роль в процессе создания объекта. Хотя это продвинутые темы, статья объясняет каждую тему шаг за шагом и с нуля, так что даже новички могут ее понять.
Обратите внимание, что эта статья написана с учетом Python3.
В Python3 все классы неявно наследуются от встроенного базового класса object. Класс object предоставляет некоторые общие методы, такие как __init__, __str__ и __new__, которые могут быть переопределены дочерним классом. Рассмотрим код ниже, например:
В приведенном выше коде класс Human не определяет никаких атрибутов или методов. Однако по умолчанию класс Human наследует базовый класс object и в результате имеет все атрибуты и методы, определенные базовым классом object. Мы можем проверить все атрибуты и методы, унаследованные или определенные классом Human, используя функцию dir.
Вывод функции dir показывает, что класс Human имеет множество методов и атрибутов, большинство из которых доступны классу Human из базового класса object. Python предоставляет атрибут __bases__ для каждого класса, который можно использовать для получения списка классов, наследуемых данным классом.
Приведенный выше вывод показывает, что класс Human имеет object в качестве базового класса. Мы также можем просмотреть атрибуты и методы, определенные классом object, используя функцию dir.
Приведенное выше определение класса Human эквивалентно следующему коду; здесь мы явно наследуем базовый класс object. Хотя вы можете явно наследовать базовый класс object, это не обязательно!
Базовый класс object предоставляет методы __init__ и __new__, которые используются для создания и инициализации объектов класса. Мы подробно обсудим __init__ и __new__ в последней части руководства.
Python — это объектно-ориентированный язык программирования. Всё в Python является объектом или экземпляром. Классы, функции и даже простые типы данных, такие как целое число и число с плавающей запятой, также являются объектами некоторого класса в Python. У каждого объекта есть класс, из которого он создается. Чтобы получить класс или тип объекта, Python предоставляет нам функцию type и свойство __class__, определенные для самого объекта.
Давайте разберемся с функцией type с помощью простых типов данных, таких как int и float.
В отличие от других языков, в Python 9 является объектом класса int, и на него ссылается переменная a. Аналогично, 9.0 — это объект класса float, на который ссылается переменная b.
Мы также можем использовать свойство __class__ объекта, чтобы найти тип или класс объекта.
После простых типов данных давайте теперь разберемся с функцией типа и атрибутом __class__ с помощью определяемого пользователем класса Human. Рассмотрим класс Human, определенный ниже:
Приведенный выше код создает экземпляр human_obj класса Human. Мы можем узнать класс (или тип human_obj), из которого был создан human_obj, используя либо функцию type, либо свойство __class__ объекта human_obj.
Выводы type(human_obj)
и human_obj.__class__
показывают, что human_obj имеет тип Human (т. е. human_obj был создан из класса Human).
Поскольку функции в Python также являются объектами, мы можем найти их тип или класс, используя функцию type или атрибут __class__.
Таким образом, simple_function является объектом класса function.
Например, класс Human (из которого был создан human_obj) сам по себе является объектом. Да, вы не ослышались! Даже классы имеют класс, из которого они создаются или инстанцируются.
Давайте узнаем тип или класс класса Human.
Таким образом, приведенный выше код показывает, что класс Human и любой другой класс в Python являются объектами класса type. Этот type является классом и отличается от функции type, которая возвращает тип объекта. Класс type, из которого создаются все классы, в Python называется метаклассом Metaclass. Давайте узнаем больше о метаклассе.
Ранее в статье мы проверили, что переменные a и b являются объектами классов int и float соответственно. Поскольку int и float являются классами, они должны иметь класс или метакласс, из которого они созданы.
Таким образом, класс type является метаклассом классов int и float. Класс type является даже метаклассом для встроенного класса object, который является базовым классом для всех классов в Python. Поскольку type сам по себе является классом, что такое метакласс класса type? Класс type является метаклассом самого себя!
Метакласс — это наименее обсуждаемая тема, и обычно он редко используется в повседневном программировании. Я углубляюсь в эту тему, потому что метакласс играет важную роль в процессе создания объекта, который мы рассмотрим позже в этой статье.
Две важные концепции, которые мы рассмотрели до сих пор, заключаются в следующем:
Все классы в Python являются объектами класса type, и этот класс типов называется Metaclass.
Каждый класс в Python по умолчанию наследуется от базового класса object.
Имея общее представление о метаклассе Metaclass и объектах в Python, давайте теперь разберемся с процессом создания и инициализации объекта в Python. Рассмотрим класс Human, как определено ниже:
Вывод приведенного выше кода показывает, что human_obj является экземпляром класса Human с first_name как Virat и last_name как Kohli. Если мы внимательно посмотрим на приведенный выше код, естественно возникнут некоторые вопросы:
В соответствии с определением класса Human мы ничего не возвращаем из метода __init__; как вызов класса Human возвращает human_obj?
Мы знаем, что метод __init__ используется для инициализации объекта, но как метод __init__ получает себя?
В этом разделе мы подробно обсудим каждый из этих вопросов и ответим на них.
Создание объекта в Python — это двухэтапный процесс. На первом этапе Python создает объект, а на втором этапе он инициализирует объект. В большинстве случаев нас интересует только второй шаг (т. е. шаг инициализации). Python использует метод __new__ на первом этапе (т. е. создание объекта) и использует метод __init__ на втором этапе (т. е. инициализации).
Если класс не определяет эти методы, они наследуются от базового класса object. Поскольку класс Human не определяет метод __new__, в процессе инстанцирования объекта вызывается метод __new__ класса object, а для инициализации вызывается метод __init__ класса Human. Далее мы подробно рассмотрим каждый из этих методов.
Метод __new__ — это первый шаг в процессе создания экземпляра объекта. Это статический метод класса object, который принимает cls или ссылку на класс в качестве первого параметра. Остальные аргументы (Вират и Кохли) передаются при вызове класса - Human("Virat", "Kohli")
. Метод __new__ создает экземпляр типа cls (т. е. он выделяет память для объекта, вызывая метод __new__ суперкласса, т. е. класса object, с помощью super().__new__(cls)
). Затем он возвращает экземпляр типа cls.
Обычно он не выполняет никакой инициализации, так как это работа метода __init__. Однако, когда вы переопределяете метод __new__, вы также можете использовать его для инициализации объекта или изменения его по мере необходимости перед его возвратом.
Мы можем изменить процесс создания объекта, переопределив метод __new__ класса object. Рассмотрим пример ниже:
В приведенном выше примере мы переопределили метод __new__ класса object. Он принимает первые аргументы как cls — ссылку на класс Human.
Внутри метода __new__ класса Human мы сначала вызываем метод __new__ класса object, используя super().__new__(cls). Метод __new__ класса object создает и возвращает экземпляр класса, который был передан в качестве аргумента методу __new__. Здесь, когда мы передаем cls (т. е. ссылку на класс Human); метод __new__ класса object вернет экземпляр типа Human.
Рассмотрим другой пример. В этом примере мы создаем новый класс с именем Animal и переопределяем метод __new__. Здесь, когда мы вызываем метод __new__ класса object из метода __new__ класса Animal, вместо передачи ссылки на класс Animal в качестве аргумента методу __new__ класса object мы передаем ссылку на класс Human. Следовательно, объект, возвращаемый методом __new__ класса object, будет иметь тип Human, а не Animal. В результате объект, возвращаемый при вызове класса Animal (т. е. Animal()
), будет иметь тип Human.
Метод __init__ — это второй шаг процесса создания экземпляра объекта в Python. Он принимает первый аргумент как объект или экземпляр, возвращаемый методом __new__. Остальные аргументы - это аргументы, переданные при вызове класса (Human("Virat", "Kohli")
). Эти аргументы используются для инициализации объекта. Метод __init__ не должен ничего возвращать. Если вы попытаетесь вернуть что-либо с помощью метода __init__, это вызовет исключение, как показано ниже:
Рассмотрим простой пример, чтобы понять методы __new__ и __init__.
В приведенном выше коде мы переопределили методы __new__ и __init__ класса object. __new__ создает объект (human_obj) типа класса Human и возвращает его. Как только метод __new__ завершен, Python вызывает метод __init__ с объектом human_obj в качестве первого аргумента. Метод __init__ инициализирует human_obj с first_name как Virat и last_name как Kohli. Поскольку создание объекта — это первый шаг, а инициализация — второй шаг, метод __new__ всегда будет вызываться перед методом __init__.
И __init__, и __new__ называются магическими методами в Python. Магические методы имеют имена, которые начинаются и заканчиваются на __ (двойное подчеркивание или "dunder"). Магические методы вызываются Python неявно; вам не нужно вызывать их явно. Например, Python неявно вызывает метод __new__ и __init__. Давайте рассмотрим еще один волшебный метод, __call__.
Метод __call__ — это магический метод в Python, который используется для того, чтобы сделать объекты вызываемыми. Вызываемые объекты — это объекты, которые можно вызвать. Например, функции являются вызываемыми объектами, поскольку их можно вызывать с помощью круглых скобок.
Рассмотрим пример, чтобы лучше понять вызываемые объекты:
Попробуем вызвать целочисленный объект integer. Поскольку целочисленные объекты не вызываются, их вызов вызовет исключение.
Вызываемая (callable) функция используется для определения того, является ли объект вызываемым. Вызываемая функция принимает ссылку на объект в качестве аргумента и возвращает значение True
, если объект кажется вызываемым, или False
, если объект не является вызываемым. Если вызываемая функция возвращает значение True
, объект может быть не вызываемым; однако, если он возвращает False
, то объект определенно не может быть вызван.
Давайте определим, являются ли классы в Python вызываемыми. Здесь мы определим, можно ли вызвать определенный ранее класс Human.
Да, классы в Python можно вызывать, и они должны быть такими! Вы так не думаете? Когда мы вызываем класс, он возвращает экземпляр этого класса. Давайте выясним, являются ли объекты, созданные из класса, вызываемыми.
Таким образом, human_obj не вызывается через класс human_obj (т. е. класс Human является вызываемым).
Чтобы сделать любой объект в Python вызываемым, Python предоставляет метод __call__, который должен быть реализован классом объекта. Например, чтобы сделать объект human_obj вызываемым, класс Human должен реализовать метод __call__. Как только класс Human реализует метод __call__, все объекты класса Human могут вызываться как функции (т. е. с использованием круглых скобок).
Приведенный выше вывод кода показывает, что после реализации метода __call__ в классе Human human_obj становится вызываемым объектом. Мы можем вызвать human_obj, используя круглые скобки (например, human_obj()
). Когда мы используем human_obj()
, в фоновом режиме Python вызывает метод __call__ класса Human. Итак, вместо того, чтобы вызывать human_obj как human_obj()
, мы можем напрямую вызвать метод __call__ для human_obj (т. е. human_obj.__call()
). И human_obj()
, и human_obj.__call__()
эквивалентны, и это одно и то же.
Мы знаем, что функции являются вызываемыми объектами, поэтому их класс (то есть function) должен реализовывать метод __call__. Давайте вызовем метод __call__ для определенной ранее функции print_function.
Грубо говоря, метод __call__ для класса type выглядит примерно так, как показано ниже. Это просто для целей объяснения; мы рассмотрим фактическое определение метода __call__ позже в этом руководстве.
Имея представление о методе __call__ и о том, как вызов класса вызывает метод __call__ класса type, давайте найдем ответы на следующие вопросы, касающиеся процесса инициализации объекта:
Кто вызывает метод __new__ и __init__?
Кто передает объект self методу __init__?
Поскольку метод __init__ вызывается после метода __new__, а метод __init__ ничего не возвращает, как вызов класса возвращает объект (т. е. как вызов класса Human возвращает объект human_obj)?
Рассмотрим пример создания экземпляра объекта в Python.
Давайте разберемся в приведенном выше коде; когда мы делаем Human("Virat", "Kohli")
в фоновом режиме, Python вызовет метод __call__ класса type, который определен как приведенный выше фрагмент кода. Как показано выше, метод __call__ класса type принимает класс Human в качестве первого аргумента (cls — это класс Human), а остальные аргументы передаются при вызове класса Human. Метод __call__ класса type сначала вызовет метод __new__, определенный в классе Human, если таковой имеется; в противном случае вызывается метод __new__ родительского класса класса Human (т. е. метод __new__ класса object). Метод __new__ вернет объект human_obj. Теперь метод __call__ класса type будет вызывать метод __init__, определенный в классе Human, с human_obj в качестве первого аргумента. __init__ инициализирует human_obj переданными аргументами, и, наконец, метод __call__ вернет human_obj.
Итак, при создании и инициализации объекта в Python выполняются следующие шаги:
Вызвать класс Human - Human()
; это внутренне вызывает метод __call__ класса type (т. е. type.__call__(Human, "Virat", "Kohli")
).
type.__call__ сначала вызовет метод __new__, определенный в классе Human. Если метод __new__ не определен в классе Human, будет вызван метод __new__ класса object.
Метод __new__ вернет объект типа Human, т.е. human_obj.
Теперь type.__call__ вызовет метод __init__, определенный в классе Human, с human_obj в качестве первого аргумента. Этот human_obj будет self в методе __init__.
Метод __init__ инициализирует human_obj с first_name как Virat и thelast_name как Kohli. Метод __init__ ничего не вернет.
В конце концов, type.__call__ вернет объект human_obj.
В соответствии с определением type.__call__ всякий раз, когда мы создаем новый объект, всегда будет вызываться метод __new__, но вызов метода __init__ зависит от вывода метода __new__. Метод __init__ будет вызываться только в том случае, если метод __new__ возвращает объект типа класса Human или подкласса класса Human.
Давайте разберемся в некоторых случаях.
Случай 1: если возвращаемый объект из метода __new__ имеет тип Human (т. е. класс метода __init__), будет вызван метод __init__.
Выведет:
Случай 2: Если метод __new__ ничего не возвращает, то __init__ вызываться не будет.
Выведет:
В приведенном выше коде вызывается метод __new__ класса Human; следовательно, создается obj типа Human (т. е. для obj выделяется память). Однако, поскольку метод __new__ не возвратил human_obj, метод __init__ вызываться не будет. Также у human_obj не будет ссылки на созданный объект, так как он не был возвращен из метода __new__.
Случай 3: метод __new__ возвращает целочисленный объект.
В приведенном выше коде вызывается метод __new__ класса Human; следовательно, создается obj типа Human (т. е. для obj выделяется память). Однако метод __new__ вернул не human_obj, а целое число со значением 10, которое не относится к типу Human; следовательно, метод __init__ вызываться не будет. Кроме того, human_obj не будет иметь ссылку на созданный объект, но будет ссылаться на целочисленное значение 10.
В сценариях, где метод __new__ не возвращает экземпляр класса, и мы хотим инициализировать объект, мы должны вызвать метод __init__ явно внутри метода __new__, как показано ниже:
Выведет:
В приведенном выше случае метод __new__ вернул целочисленный объект; следовательно, значение human_obj будет равно 10.
В этой статье мы рассмотрели магические методы __new__, __init__ и __call__ и обсудили метакласс в Python. При этом теперь мы лучше понимаем процессы создания и инициализации объектов в Python.
https://eli.thegreenplace.net/2012/04/16/python-object-creation-sequence
https://realpython.com/python-metaclasses/#old-style-vs-new-style-classes
https://docs.python.org/3/reference/datamodel.html#special-method-names
Мы знаем, что когда мы вызываем класс (например, Human("Virat", "Kohli")
), вызывается метод __call__ класса type. Однако каково определение метода __call__ класса type? Поскольку мы говорим о CPython, __call__ класса type определено на языке C. Если мы конвертируем его в Python и упрощаем, он будет выглядеть примерно так: