Introspection in Python
Introspection is about getting information about an object at runtime. This information could be about the object’s type, its data attributes and methods, its class inheritance hierarchy, documentation, method signature, and more. Introspection can also be about knowing the current runtime context such as stack information.
Introspection is an additional language feature in the programmer’s toolbox. It can help the programmer code for specific scenarios that are perhaps harder to solve otherwise. Tracing and debugging tools benefit from introspection. In Python, data attributes and methods can be added or removed from instances even after instantiation. Given this dynamic nature of objects, introspection becomes a useful feature.
Discussion
- What built-in functions facilitate object introspection in Python? The following built-in functions are useful:
- callable() : Returns True if object is callable. Eg. callable(mycar) . Objects can be made callable by defining __call__ method in their class.
- dir() : Obtain all attributes (data and methods) of an object. Eg. dir(mycar) .
- hasattr() : Check if object has an attribute. Eg. hasattr(mycar, ‘num_gears’) . This is useful since attributes can be dynamically added/removed at runtime. Note that if ‘num_gears’ in dir(mycar) is equivalent to hasattr(mycar, ‘num_gears’) .
- help() : Obtain help on an object’s interface based on docstrings.
- id() : Obtain the unique ID of the object. Eg. id(mycar) .
- issubclass() : Check if a class is a subclass of a specified class. Eg. issubclass(Honda, Car) .
- isinstance() : Check if an object is of a specified class. Eg. isinstance(mycar, Honda) will be True when mycar = Honda() . If Honda is a subclass of Car, isinstance(mycar, Car) is also True. Note that type(mycar) is Car is False.
- sys.getsizeof() : Part of the sys module, this obtains the size of the object in bytes.
- type() : Obtain the object’s type. Eg. type(2) will give .
- vars() : Obtain all instance variables (name and value) as a dictionary. Eg. vars(mycar) . This is equivalent to mycar.__dict__ .
- What object attributes help with introspection in Python? There are some attributes that give useful information about objects. For example, given an instance mycar , mycar.__class__ retrieves its class. Attributes of the class give more information: __name__ , __qualname__ , __str__ , __repr__ , __doc__ , and __self__ . We briefly describe these:
- __name__ : Original name of the class, function or method.
- __qualname__ : Qualified name of the class function or method. This is often useful where class/function/method definitions are nested.
- __doc__ : Documentation string, which can also be retrieved by calling built-in function help() .
- __self__ : Instance to which a method is bound.
- Could you describe Python’s inspect module? Python module inspect provides many functions to enable introspection. The module documentation notes that it provides four main kinds of services: «type checking, getting source code, inspecting classes and functions, and examining the interpreter stack.» Inspect documentation has full details. We note some functions:
- Checking types: ismodule() , isclass() , ismethod() , isfunction() , isgenerator() , isabstract() , isbuiltin() , isawaitable() , and many more.
- Inspecting objects: getmembers() is quite useful. For example, inspect.getmembers(mycar, inspect.ismethod) will return only methods of the instance mycar . Function getfullargspec() returns names and default values of a function. These can be nicely formatted using formatargspec() . Callables can be inspected using signature() . Class definitions relevant here are Signature , Parameter and BoundArguments .
- Retrieving source code: getdoc() , getcomments() , getfile() , getmodule() , getsourcefile() , getsourcelines() , getsource() , and cleandoc() .
- Examining the stack: getframeinfo() , getouterframes() , getinnerframes() , currentframe() , stack() and trace() .
- How can I obtain the current runtime context in Python? The Python interpreter maintains a stack of frames. The bottom-most frame is called the global frame or module frame. When a function is called, a new frame is created and pushed to the stack. When that function returns, the frame is popped off the stack. Python’s inspect module provides many functions to inspect the interpreter stack. Function currentframe() returns a frame object representing the current frame. More information about the frame object can be obtained by calling getframeinfo() , which returns a named tuple Traceback(filename, lineno, function, code_context, index) . To obtain the entire stack, call stack() instead. To inspect the call graph, we can use getouterframes() . When an exception is raised and later handled in an outer frame, we can use trace() or getinnerframes() . These can be useful for logging and debugging. Apart from inspecting the stack, two built-in functions help us inspect variable scope. These are globals() and locals() . At the module level, both functions return the same dictionary.
- How can I inspect class hierarchy in Python? Class hierarchy can easily be inspected by accessing the class attribute __bases__ and recursively printing the class names up the hierarchy. If an instance is being inspected, we can first get to its class via the __class__ attribute and then process the __bases__ attribute. In the figure, we show an example of how this can be done. The method inspect.getclasstree() from inspect module is an alternative. Given a list of classes, it returns the class hierarchy. For every class, its base classes are returned as a tuple. Another useful method is inspect.getmro() to which a class name must be passed as an argument. This is equivalent to calling the built-in class method mro() . Either way, these don’t return the inheritance hierarchy but they do tell us the order in which methods are looked up the hierarchy.
- What are some specialized modules for Python introspection? Python processes source code in three steps: (i) parsing, where it tokenizes the source code and produces an Abstract Syntax Tree ( AST ); (ii) compiling, where it transforms the AST into Python bytecode; (iii) interpreting, where the Python Virtual Machine ( VM ) interprets and executes the bytecode. In this context, there are two Python modules that help with introspection: ast to inspect the AST , and dis to inspect the bytecode. While inspect module has functions to inspect the call stack, sys module also has functions that can help with debugging recursive calls: _getframe() , getrecursionlimit() and setrecursionlimit() . Support we decorate the function foo() . Accessing foo.__name__ will return the name of the function returned by the decorator. This is probably not what we want. We can solve this with the wraps() function from the functools module. The figure shows an example how to use this. An alternative is to use inspect.signature() function.
- Could you share some tips on using Python’s introspection features? While type() and isinstance() could be useful, code that rely on them are hard to maintain. We might be checking for specific types but when another type must be handled, we need to update the code. Instead, Python programmers prefer the approach of duck typing. With duck typing, we don’t care about an object’s type. Instead, we focus on the interfaces the object exposes. In this approach, hasattr() can be useful though this method can cause problems in Python 2 when called on a property attribute. An alternative to hasattr() is to simply call a method or access a data attribute without doing any checks. If it doesn’t exist, we should handle the exception. Another alternative is to use an Abstract Base Class ( ABC ) from the collections.abc module.
Milestones
Интроспекция в Python
Интроспекция (introspection) в контексте объектно-ориентированных языков программирования — это возможность запросить тип и структуру объекта во время выполнения программы.
Среди языков, поддерживающих интроспекцию — C++ (с RTTI), Go, Java, Kotlin, JavaScript, Perl, Ruby, Smalltalk, PHP и Python.
В PHP и Python интроспекция интегрирована в сам язык.
В Python интроспекция может быть функционально реализована с помощью:
- встроенных методов dir(), type(), isinstance(), hasattr(), id();
- встроенного модуля inspect;
- идти непосредственно от имени объекта с помощью встроенных аттрибутов __class__ и __dict__.
Пользоваться интроспекцией в Python особенно удобно, благодаря парадигме, что “всё является объектом”. Любая сущность, являясь объектом, имеет метаданные (данные об объекте), называемые аттрибутами, и связаные с этой сущностью функциональности, называемые методами. В Python новый класс по-умолчанию является сам по себе объектом метакласса type.
Наиболее часто используемые функции интроспекции в Python:
dir()
- Предоставляет список атрибутов и методов, доступных для указанного объекта, который может быть объявленной переменной или функцией.
- Возвращаемое значение это отсортированный в алфавитном порядке список.
- При вызове функции dir() без аргумента она возвращает имена, доступные в локальной области видимости.
type()
Возвращает тип объекта, который может быть примитивным типом данных, объектом, классом или модулем.
Можно напрямую сравнить возвращаемое значение с типом, который мы хотим проверить, используя == или is.
isinstance()
Позволяет определить, является ли определенный объект экземпляром указанного класса.
isinstance() может принимать кортеж в качестве второго аргумента,
При использовании type() проводится сравнение один к одному. По сути, мы сравниваем тип объекта с типом, который мы указали, чтобы проверить, совпадают ли они.
isinstance() является более гибкой функцией. Фактически она определяет, является ли объект экземпляром указанного класса (классов) или его подкласса. Для isinstance() экземпляр подкласса также является экземпляром базового класса. Другими словами, она сравнивает объект со списком потенциально релевантных классов, что является своего рода сравнением один к нескольким.
hasattr()
Функция hasattr() проверяет существование атрибута с именем name в объекте object. Возвращает True, если атрибут существует, иначе False.
Реализация функция hasattr() основывается на вызове функции getattr() с последующей проверкой на предмет брошенного ей исключения AttributeError.
id()
Функция id() возвращает уникальный идентификатор для указанного объекта.
В CPython идентификатор объекта — это адрес объекта в памяти.
- Все объекты в Python имеют свой уникальный идентификатор. Идентификатор присваивается объекту при его создании. Идентификатор является адресом памяти объекта и будет отличаться при каждом запуске программы.
- Объекты могут иметь одинаковый идентификатор, если периоды их существования не пересекаются.
- Некоторые объекты могут иметь один и тот же идентификатор, например: мелкие целые от -5 до 256, True и False.
Модуль inspect
Модуль inspect также предоставляет несколько полезных функций для получения информации об объектах. Например, можно проверить элементы объекта:
Шпаргалка по интроспекции в Python
Интроспекция — это возможность запросить тип и структуру объекта во время выполнения программы.
Встроенные функции
Функция | Возвращаемое значение |
---|---|
type(obj) | тип объекта |
dir(obj) | пространство имен объекта |
dir() | текущее пространство имен |
id(obj) | адрес объекта в памяти |
help(obj) | подсказка к объекту |
hasattr(obj, ‘attr’) | наличие атрибута у объекта |
getattr(obj, ‘attr'[, default]) | возвращает атрибут объекта |
locals() | словарь локальных переменных |
globals() | словарь глобальных переменных |
Переменные
Переменная | Значение |
---|---|
__annotations__ | словарь аннотаций типов |
__name__ | имя модуля, если он импортирован, или ‘__main__’ , если запущен непосредственно. |
__file__ | путь к файлу, содержащему объект в системе |
Модуль sys
Функция | Возвращаемое значение |
---|---|
sys.argv | список аргументов, с которыми запущен интерпретатор |
sys.executable | адрес интерпретатора в системе |
sys.flags | флаги командной строки |
sys.getrecursionlimit() | лимит рекурсии |
sys.getsizeof(obj[, default]) | размер объекта |
sys.hash_info | параметры хеширования |
sys.modules | словарь загруженных модулей |
sys.path | список путей для поиска модулей |
sys.platform | операционная система |
sys.version | версия Python |
sys.version_info | версия Python в форме кортежа |
Практический Python для начинающих
Станьте junior Python программистом за 7 месяцев