Интроспекция в Python

Многие языки программирования поддерживают интроспекцию, и Python не является исключением. В общем, в контексте объектно-ориентированных языков программирования, интроспекция — это способность объекта во время выполнения получить тип, доступные атрибуты и методы, а также другую информацию, необходимую для выполнения дополнительных операций с объектом.

В этой статье мы рассмотрим пять самых полезных функций интроспекции в Python.


dir()

Первая функция — это функция dir(). Она предоставляет список атрибутов и методов, доступных для указанного объекта, который может быть объявленной переменной или функцией.

>>> a = [1, 2, 3]
>>> dir(a)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

Как вы можете заметить, значение, которое вернула функция dir() — это отсортированный в алфавитном порядке список. Это подразумевает, что мы можем проверить существование определенного атрибута или метода, чтобы увидеть, может ли объект выполнить эту операцию. Пример приведен ниже.

>>> b = [1, 2, 3]
>>> b_dir = dir(b)
>>> 'index' in b_dir
True
>>> 'pop' in b_dir
True

При вызове функции dir() без аргумента она возвращает имена, доступные в локальной области видимости, как показано ниже. Это может быть полезно, если вы хотите проверить, что было определено и использовано в вашей программе.

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b', 'b_dir']

type()

Другой часто используемой функцией интроспекции является функция type(). Как видно из названия, эта функция возвращает тип объекта, который может быть примитивным типом данных, объектом, классом или модулем. Давайте посмотрим на примеры ниже:

>>> type(1.2)
<class 'float'>
>>> type([1, 2, 3])
<class 'list'>
>>> type((3, 'Three'))
<class 'tuple'>
>>> def do_something():
... pass
... 
>>> type(do_something)
<class 'function'>
>>> class Fruit:
... pass
... 
>>> type(Fruit)
<class 'type'>
>>> type(Fruit())
<class '__main__.Fruit'>
>>> import os
>>> type(os)
<class 'module'>

Что мы можем сделать с возвращаемыми значениями из функции type()? Мы можем напрямую сравнить возвращаемое значение с типом, который мы хотим проверить, используя == или is. Ниже приведено несколько примеров.

>>> type(1.2) == int
False
>>> type(1.2) == float
True
>>> type([1,2]) == list
True
>>> type((1,2)) is tuple
True

isinstance()

Еще одной из функций интроспекции, которая особенно полезна, является функция isinstance(). Используя эту функцию, мы можем определить, является ли определенный объект экземпляром указанного класса. Простой пример приведен ниже.

>>> isinstance([1,2], list)
True
>>> isinstance([1,2], tuple)
False
>>> isinstance((1,2), tuple)
True

Следует также отметить, что функция isinstance() может принимать кортеж в качестве второго аргумента, как показано ниже.

>>> isinstance(1, (int, float, tuple))
True
>>> isinstance(1, int) or isinstance(1, float) or isinstance(1, tuple)

Рассмотрим более практический пример использования, который включает в себя пользовательский класс:

>>> class Fruit:
... pass
... 
>>> apple = Fruit()
>>> isinstance(apple, Fruit)
True

Возможно, вы заметили, что и type(), и isinstance() могут быть использованы для определения того, принадлежит ли указанный объект определенному типу. Тем не менее, они не одинаковы.

Когда мы используем type(), чтобы определить, принадлежит ли объект определенному типу, мы проводим сравнение один к одному. По сути, мы сравниваем тип объекта с типом, который мы указали, чтобы проверить, совпадают ли они.

А вот isinstance() является более гибкой функцией. Фактически она определяет, является ли объект экземпляром указанного класса (классов) или его подкласса.  Для isinstance() экземпляр подкласса также является экземпляром базового класса. Другими словами, она сравнивает объект со списком потенциально релевантных классов, что является своего рода сравнением один к нескольким. На рисунке ниже приведен пример работы функции.

интроспекция в python

hasattr()

Иногда, прежде чем получить доступ к атрибуту объекта, мы можем захотеть проверить, есть ли у него этот атрибут. Мы не хотим видеть следующую ошибку:

>>> class Fruit:
... pass
... 
>>> Fruit().tasty
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Fruit' object has no attribute 'tasty'

Чтобы подобного не произошло, мы можем с помощью функции hasattr() проверить, есть ли у объекта такой атрибут, прежде чем обращаться к нему.

>>> class Fruit:
... tasty = True
... 
>>> fruit = Fruit()
>>> if hasattr(fruit, 'tasty'):
... print('The fruit is tasty')
... else:
... print('The fruit is not tasty')
... 
The fruit is tasty

Возможно, вы не обратили на это внимание, но мы можем использовать функцию dir() для достижения того же самого результата.

Как вы помните, функция dir() возвращает доступные атрибуты и методы для указанного объекта, так что мы можем напрямую сравнить, является ли атрибут одним из элементов возвращаемого списка. Ниже приведен обновленный код.

>>> class Fruit:
... tasty = True
... 
>>> fruit = Fruit()
>>> if 'tasty' in dir(fruit):
... print('The fruit is tasty')
... else:
... print('The fruit is not tasty')
... 
The fruit is tasty

id()

И последняя, но не менее важная функция интроспекции в Python — это функция id(). Она возвращает идентификатор указанного объекта. В CPython идентификатор объекта — это адрес объекта в памяти, который является уникальным для всех существующих объектов. Объекты могут иметь одинаковый идентификатор, если периоды их существования не пересекаются. Ниже приведен простой пример этой функции.

a = 2
b = 1
id(a)
140339209865072
id(b)
140339209865096

Один из распространенных примеров кода в Python — это обмен значений двух переменных. Этого можно добиться, просто выполнив следующий код a, b = b, a. Давайте посмотрим, что происходит после обмена.

id(a)
140339209865096
id(b)
140339209865072
a, b
(1, 2)

Как мы можем заметить, эти две переменные успешно поменялись местами, что видно из их адресов в памяти и значений.


Выводы

Проводить интроспекцию в Python очень удобно.

Если вы хотите узнать о более продвинутых  методах интроспекции в Python, то можете изучить модуль inspect.

Пять функций, которые были рассмотрены в этой статье, являются наиболее распространенными. Вы смело можете использовать их в своих проектах на Python.