Функции isinstance() и issubclass() в Python
Функция isinstance() в Python используется для проверки, является ли объект экземпляром указанного класса или нет.
Синтаксис функции isinstance():
Эта функция возвращает True, если объект является экземпляром аргумента classinfo или экземпляром подкласса classinfo.
Если объект не является экземпляром classinfo или его подкласса, функция возвращает False.
Аргумент classinfo может быть кортежем типов. В этом случае isinstance() вернет True, если объект является экземпляром любого из типов.
Если classinfo не является типом или кортежем типов, возникает исключение TypeError.
Пример
Давайте рассмотрим несколько простых примеров функции isinstance() со встроенными типами данных, такими как строка, кортеж, список, dict, байты и т.д.
string
bytes
Кортеж
Класс и наследование
Давайте посмотрим на пример функции isinstance() с настраиваемым классом и наследование с несколькими классами.
Кортеж классов
Резюме
Python isinstance() – это служебная функция, которая проверяет, принадлежит ли объект определенному типу или нет. Мы можем использовать его для проверки типа и выполнения операций в зависимости от типа объекта.
Функция issubclass() в Python используется для проверки, является ли класс подклассом другого класса или нет.
Синтаксис функции issubclass():
Эта функция возвращает True, если класс является подклассом classinfo.
Класс считается подклассом самого себя. Мы также можем передать кортеж классов в качестве аргумента classinfo, в этом случае функция вернет True, если класс является подклассом любого из классов в кортеже.
Поскольку объект является базовым классом в Python, функция вернет True, если classinfo передается как класс объекта.
Пример
Давайте определим несколько классов и подклассов для нашего примера.
Теперь посмотрим на результат работы функции issubclass() с разными аргументами.
С кортежем классов
Давайте посмотрим на другой пример, где мы проверим, является ли OrderedDict подклассом dict или нет.
Сравнение issubclass() и isinstance()
Функции issubclass() и isinstance() очень похожи, за исключением того, что первые работают с классами, тогда как последние работают с экземплярами классов.
Инстанциирование классов и экземпляры — Python: Введение в ООП
Экземпляры
Класс, как мы уже увидели, может хранить данные. Но типичный класс присутствует в программе в единственном экземпляре. Поэтому сам по себе класс не очень полезен, ведь хранить определения можно и в модулях. Весь смысл использования классов заключается в их инстанциировании.
Инстанциированием (instantiation) называют процесс (акт) создания на основе класса экземпляра (instance) — такого объекта, который получает доступ ко всему содержимому класса, но при этом обладает и способностью хранить собственные данные. При этом, имея объект, всегда можно узнать, экземпляром какого класса он является.
Давайте объявим класс и создадим пару экземпляров, а заодно и познакомимся с синтаксисом инстанциирования классов:
При выводе объекта класса в REPL можно увидеть строку, похожую на вывод информации о классе, только вместо «class» в строчке упоминается «object».
Атрибуты класса и экземпляры
В предыдущем примере класс был пустой. Теперь воспроизведём его, но добавим на этот раз атрибут:
Давайте же переименуем Боба:
Вот вы и увидели то самое «собственное состояние объекта»! Person продолжает давать имя всем экземплярам, пока те не изменят значение своего атрибута. В момент присваивания нового значения атрибуту экземпляра, экземпляр получает свой собственный атрибут!
Стоит прямо сейчас заглянуть «под капот» объектной системы Python, чтобы вы в дальнейшем могли исследовать объекты самостоятельно. Это и интересно, и полезно — как при обучении, так и при отладке объектного кода.
Итак, внутри каждого объекта Python хранит… словарь! Имена атрибутов в пространствах имён выступают ключами этого словаря, а значения являются ссылками на другие объекты. Словарь этот всегда называется __dict__ и тоже является атрибутом. Обращаясь к этому словарю, вы можете получить доступ к значениям атрибутов:
Надо сказать, что это очень разумный подход! Да, Python мог бы копировать словарь класса при инстанциировании. Но это привело бы к излишнему потреблению памяти. А вот «коллективное использование», напротив, позволяет память экономить!
И, конечно же, словарь __dict__ объекта может быть изменён. Когда мы давали Бобу имя, мы на самом деле сделали что-то такое:
Мы даже можем добавить Бобу фамилию и сделать это через модификацию __dict__ :
Проверка принадлежности экземпляра к классу
Как вы уже могли заметить, в Python многие «внутренние штуки» имеют имена, заключённые в двойные символы подчёркивания. В разговоре питонисты обычно проговаривают подобные имена примерно так: «дАндер-класс», что является калькой с «dunder class», где «dunder», в свою очередь, это сокращение от «double underscore», то есть «двойной символ подчеркивания». Полезно запомнить этот стиль именования!
А ещё стоит запомнить, что практически всегда, когда вы хотите использовать что-то, названное в dunder-стиле, «есть способ лучше»! Так с __dict__ напрямую работать не приходится, потому что есть возможность обращаться к атрибутам «через точку». Вот и __class__ в коде встречается редко. А рекомендуемый способ проверки принадлежности к классу выглядит так:
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
46) тип () и isinstance ()
Что такое type () в Python?
Используя команду type (), вы можете передать один аргумент, и возвращаемое значение будет типом класса данного аргумента, например: type (object).
Также возможно передать три аргумента в type (), то есть type (name, base, dict), в таком случае он вернет вам объект нового типа.
В этом уроке вы узнаете:
Синтаксис для типа ():
type () может использоваться двумя способами, как показано ниже:
Параметры : тип (объект)
Параметры : тип (имя, основания, дикт)
Возвращаемое значение:
Если объект является единственным параметром, переданным type (), он вернет вам тип объекта.
Если параметры, передаваемые типу, являются типом (объект, базы, dict), в таком случае он вернет объект нового типа.
Пример типа ()
В этом примере у нас есть строковое значение, число, значение с плавающей запятой, комплексное число, список, кортеж, dict и set. Мы будем использовать переменные с типом, чтобы увидеть выходные данные для каждой из них.
Вывод:
Пример: использование type () для объекта класса.
Когда вы проверяете объект, созданный из класса, используя type (), он возвращает тип класса вместе с именем класса. В этом примере мы создадим класс и проверим тип объекта, созданный из теста класса.
Вывод:
Пример: использование имени, оснований и dict в type ()
Тип также может быть вызван с использованием синтаксиса: тип (имя, базы, dict).
Три параметра, передаваемые в type (), т. Е. Name, base и dict, являются компонентами, составляющими определение класса. Имя представляет имя класса, base — это базовый класс, а dict — это словарь атрибутов базового класса.
В этом примере мы собираемся использовать все три параметра: имя, базы и dict в type ().
Инстанцирование в Python
Допустим, у вас есть класс Foo :
Что происходит, когда вы создаёте его объект?
Какой метод вызывается первым при этом вызове Foo? Большинство новичков, да и, возможно, немало опытных питонистов тут же ответят: «метод __init__». Но если внимательно приглядеться к сниппетам выше, вскоре станет понятно, что такой ответ неверен.
__init__ не возвращает никакого результата, а Foo(1, y=2), напротив, возвращает экземпляр класса. К тому же __init__ принимает self в качестве первого параметра, чего не происходит при вызове Foo(1, y=2). Создание экземпляра происходит немного сложнее, о чём мы и поговорим в этой статье.
Порядок создания объекта
Инстанцирование в Python состоит из нескольких стадий. Понимание каждого шага делает нас чуть ближе к пониманию языка в целом. Foo — это класс, но в Питоне классы это тоже объекты! Классы, функции, методы и экземпляры — всё это объекты, и всякий раз, когда вы ставите скобки после их имени, вы вызываете их метод __call__. Так что Foo(1, y=2) — это эквивалент Foo.__call__(1, y=2). Причём метод __call__ объявлен в классе объекта Foo. Какой же класс у объекта Foo?
Так что класс Foo — это экземпляр класса type и вызов метода __call__ последнего возвращает класс Foo. Теперь давайте разберём, что из себя представляет метод __call__ класса type. Ниже находятся его реализации на C в CPython и в PyPy. Если надоест их смотреть, прокручивайте чуть дальше, чтобы найти упрощённую версию:
CPython
Если забыть про всевозможные проверки на ошибки, то коды выше примерно эквивалентны такому:
__new__ выделяет память под «пустой» объект и вызывает __init__, чтобы его инициализировать.
Кастомизация
Теперь давайте переключим наше внимание на __new__. Этот метод выделяет память под объект и возвращает его. Вы вольны кастомизировать этот процесс множеством разных способов. Следует отметить, что, хотя __new__ и является статическим методом, вам не нужно объявлять его используя @staticmethod: интерпретатор обрабатывает __new__ как специальный случай.
Распространённый пример переопределения __new__ — создание Синглтона:
Обратите внимание, что __init__ будет вызываться каждый раз при вызове Singleton(), поэтому следует соблюдать осторожность.
Другой пример переопределения __new__ — реализация паттерна Борг («Borg»):
Учтите, что хотя примеры выше и демонстрируют возможности переопределения __new__, это ещё не значит что его обязательно нужно использовать:
__new__ — одна из самых частых жертв злоупотреблений. То, что может быть сделано переопределением этого метода, чаще всего лучше достигается другими средствами. Тем не менее, когда это действительно необходимо, __new__ — крайне полезный и мощный инструмент.
Редко можно встретить проблему в Python, где лучшим решением было использование __new__. Но когда у вас есть молоток, каждая проблема начинает выглядеть как гвоздь, поэтому всегда предпочитайте использованию нового мощного инструмента использование наиболее подходящего.
Простое объяснение методов класса, экземпляра класса и статических методов в Python
Хочешь знать больше о Python?
Подпишись на наш канал о Python в Telegram!
Перевод статьи «Python’s Instance, Class, and Static Methods Demystified».
В этой статье я постараюсь объяснить, что стоит за методами класса, статическими методами и обычными методами экземпляра класса.
Если вы сможете достигнуть интуитивного понимания разницы между этими методами, вы также сможете писать объектно-ориентированный код на Python. Такой код проще для понимания, а в долгосрочной перспективе его легче поддерживать.
Обзор статических методов, методов класса и экземпляра класса
Давайте начнем с написания класса (на Python 3), который будет содержать простые примеры методов всех трех видов:
Примечание для пользователей Python 2. Декораторы @staticmethod и @classmethod доступны в Python начиная с версии 2.4, а значит, этот пример тоже будет работать. Вместо использования простого объявления класса class MyClass: вы можете объявить класс нового стиля, который будет наследоваться от object, с синтаксисом class MyClass(object):. Все остальное не требует каких-то уточнений.
Методы экземпляра класса
Первый метод в MyClass, под названием method, это обычный метод экземпляра класса. Это самый базовый вид методов, которым вы будете пользоваться чаще всего. Как видите, этот метод принимает один параметр (self), который при вызове метода указывает на экземпляр MyClass (хотя методы могут принимать и больше одного параметра).
При помощи параметра self методы экземпляра класса могут иметь свободный доступ к атрибутам и другим методам того же объекта. Благодаря этому они на многое способны, когда речь заходит о модификации состояния объекта.
Методы экземпляра также могут иметь доступ к самому классу — при помощи атрибута self.__class__. Это означает, что они могут менять состояние не только объекта, но и класса.
Методы класса
Давайте сравним то, что мы узнали о методах экземпляра класса, со вторым методом — MyClass.classmethod. Я обозначил этот метод при помощи декоратора @classmethod, чтобы было видно, что это метод класса.
Методы класса вместо параметра self принимают параметр cls. Этот параметр при вызове метода указывает не на экземпляр объекта, а на класс.
Поскольку метод класса имеет доступ только к аргументу cls, он не может изменять состояние экземпляра объекта. Для этого нужен доступ к self. Но, тем не менее, методы класса могут изменять состояние класса в целом, что затронет и все экземпляры этого класса.
Статические методы
Третий метод, MyClass.staticmethod, обозначен при помощи декоратора @staticmethod, чтобы показать, что этот метод статический.
Методы такого типа не принимают в качестве параметра ни self, ни cls (хотя, безусловно, они свободно могут принимать другие параметры в любых количествах).
Таким образом, статический метод не может изменять состояние ни объекта, ни класса. Виды данных, которые могут принимать статические методы, ограничены. Эти методы помещаются в класс, просто чтобы они находились в пространстве имен этого класса, т. е., для организационных целей.
Давайте посмотрим, как все это работает!
Пока что все изложенное было чистой теорией. Но я считаю, что вы должны научиться интуитивно улавливать разницу между методами на практике. Сейчас мы разберем несколько более конкретных примеров.
Давайте посмотрим, как все эти методы ведут себя, когда мы их вызываем. Начнем с создания экземпляра класса, а затем вызовем все три метода.
В MyClass реализация каждого метода возвращает сведения, благодаря которым мы можем понимать, к каким частям класса или объекта может иметь доступ метод, а также отслеживать происходящее.
Вот что происходит при вызове метода экземпляра класса:
Мы видим, что method (т. е., метод экземпляра класса) имеет доступ к экземпляру объекта (это видно по выводу ) при помощи аргумента self.
При вызове этого метода Python замещает аргумент self экземпляром объекта (obj). Мы можем проигнорировать синтаксический сахар dot-call синтаксиса (obj.method()) и получить тот же результат, передав экземпляр объекта вручную:
Можете догадаться, что произойдет, если вы попытаетесь вызвать этот метод без первоначального создания экземпляра класса?
Кстати, методы экземпляра класса при помощи атрибута self.__class__ также могут иметь доступ и к самому классу. Это делает данные методы особенно полезными в условиях ограничений доступа: они могут изменять состояние экземпляра объекта и самого класса.
Теперь давайте испытаем метод класса:
Стоит отметить, что при вызове MyClass.classmethod() Python автоматически передает класс в качестве первого аргумента функции. Это поведение Python запускается, если метод вызывается при помощи dot-синтаксиса. В методах экземпляра класса аналогично работает параметр self.
Пожалуйста, обратите внимание, что эти параметры именуются self и cls лишь в силу соглашений. С тем же успехом вы можете назвать их the_object и the_class. Важно то, что они идут первыми в списке параметров метода.
А теперь давайте вызовем статический метод:
Как видите, мы успешно вызвали staticmethod() через объект. Некоторые разработчики удивляются, когда узнают, что можно вызывать статический метод через экземпляр объекта.
Просто когда при вызове статического метода с использованием dot-синтаксиса не передаются аргументы self или cls, Python применяет ограничения доступа.
Этот пример подтверждает, что статические методы не имеют доступа ни к состоянию экземпляра объекта, ни к состоянию класса. Они работают как обычные функции, но при этом относятся к пространству имен класса (и каждого его экземпляра).
Теперь давайте посмотрим, что произойдет, если мы попытаемся вызвать эти методы в самом классе, т. е., без предварительного создания экземпляра объекта:
Этого и следовало ожидать: в этот раз мы не создавали экземпляр объекта и попытались вызвать функцию экземпляра класса прямо из самого класса. Это означает, что у Python не было никакой возможности заполнить аргумент self и, как следствие этого, вызов метода провалился.
Это должно более четко разграничить три вида методов. Но я на этом не остановлюсь. В следующих двух разделах я разберу два немного более реалистичных примера использования разных видов методов.
Мои примеры будут основаны на классе Pizza:
Примечание. В этом примере кода (а также в последующих) для форматирования строки, возвращаемой при помощи __repr__, мы будем использовать Python 3.6 f-strings. В Python 2 и версиях Python 3 до 3.6 для форматирования строки следует использовать другие выражения, например:
Фабрики вкусной пиццы и @classmethod
Наверняка вы знаете, что существует множество вкусных вариантов пиццы:
Итальянцы придумали свои рецепты пиццы столетия назад, так что все они уже имеют какие-то устоявшиеся названия. Мы этим воспользуемся и дадим пользователям нашего класса Pizza лучший интерфейс для создания тех объектов пиццы, о которых они мечтают.
Это можно сделать красиво и чисто, используя методы класса в качестве фабричных функций для разных видов пиццы:
Обратите внимание, что я использую аргумент cls в фабричных методах margherita и prosciutto, а не вызываю конструктор Pizza напрямую.
Вы можете использовать этот прием, чтобы придерживаться принципа DRY (Don’t Repeat Yourself, «Не повторяйся»). Если в какой-то момент мы решим переименовать этот класс, нам не придется обновлять еще и имя конструктора во всех фабричных функциях classmethod.
Что же мы можем сделать при помощи этих фабричных методов? Давайте испытаем их:
Как видите, мы можем использовать фабричные функции для создания новых объектов Pizza в нужной нам конфигурации. Внутри все они используют один конструктор __init__ и по сути просто предоставляют шорткат для запоминания всех возможных ингредиентов.
Можно взглянуть на это применение методов класса и под другим углом: они позволяют вам определить альтернативные конструкторы для ваших классов.
Python допускает только один метод __init__ для каждого класса. Использование методов класса позволяет добавить столько альтернативных конструкторов, сколько нужно. Таким образом вы можете сделать интерфейс ваших классов самодокументированным (в определенной степени) и упростить их использование.
Когда стоит использовать статические методы
Здесь придумать хороший пример немного сложнее. Но знаете что? Я просто продолжу растягивать аналогию с пиццей.
Вот что я придумал:
Что здесь изменилось? Для начала, я изменил конструктор и __repr__, чтобы принимался дополнительный аргумент radius.
Также я добавил метод экземпляра класса area(), который вычисляет и возвращает площадь пиццы (это также хороший кандидат для @property, но пример у нас игрушечный).
Вместо вычисления площади непосредственно в area(), с использованием всем известной формулы площади круга, я вынес его в отдельный статический метод circle_area().
Конечно, этот пример несколько простоват, но он хорошо помогает объяснить некоторые преимущества статических методов.
Как мы уже знаем, статические методы не имеют доступа к состоянию класса или объекта, потому что не принимают аргументы cls или self. Это большое ограничение, но также и отличный сигнал, показывающий, что данный метод ни от чего не зависит.
В приведенном выше примере ясно, что circle_area() никоим образом не может изменять класс или экземпляр класса. (Конечно, это всегда можно обойти при помощи глобальной переменной, но здесь мы не будем это разбирать).
Так в чем же польза?
Когда вы обозначаете метод в качестве статического, это не только подсказывает читателю, что данный метод не будет модифицировать класс или экземпляр класса — это ограничение также применяется средой выполнения Python.
Подобные приемы позволяют четко описывать элементы вашей архитектуры классов. Конечно, эти ограничения можно легко обойти. Но на практике они помогают избежать случайных модификаций, идущих вразрез с оригинальным дизайном.
Другими словами, использование статических методов и методов классов это способ сообщить о намерениях разработчика. Одновременно это способ обеспечить осуществление этих намерений в достаточной мере, чтобы избежать большинства ошибок, которые можно было бы допустить по невнимательности, и багов, которые разрушили бы дизайн.
Написание некоторых из ваших методов подобным образом (нечасто и там, где это имеет смысл) может принести пользу в плане поддерживаемости кода. Также это снижает вероятность того, что другие разработчики используют ваши классы неправильно.
Статические методы также имеют преимущество при тестировании кода.
Поскольку метод circle_area() совершенно не зависит от остального класса, его гораздо проще тестировать. Мы можем это сделать при помощи модульного теста, не беспокоясь об экземпляре класса в целом. То есть, этот метод тестируется, как обычная функция. Опять же, это облегчает поддержку кода в будущем.




