Интроспекция в 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) >>> type([1, 2, 3]) >>> type((3, ‘Three’)) >>> def do_something(): . pass . >>> type(do_something) >>> class Fruit: . pass . >>> type(Fruit) >>> type(Fruit()) >>> import os >>> type(os)
Что мы можем сделать с возвращаемыми значениями из функции 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() экземпляр подкласса также является экземпляром базового класса. Другими словами, она сравнивает объект со списком потенциально релевантных классов, что является своего рода сравнением один к нескольким. На рисунке ниже приведен пример работы функции.
hasattr()
Иногда, прежде чем получить доступ к атрибуту объекта, мы можем захотеть проверить, есть ли у него этот атрибут. Мы не хотим видеть следующую ошибку:
>>> class Fruit: . pass . >>> Fruit().tasty Traceback (most recent call last): File "", line 1, in 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.
15. Object introspection¶
In computer programming, introspection is the ability to determine the type of an object at runtime. It is one of Python’s strengths. Everything in Python is an object and we can examine those objects. Python ships with a few built-in functions and modules to help us.
15.1. dir ¶
In this section we will learn about dir and how it facilitates us in introspection.
It is one of the most important functions for introspection. It returns a list of attributes and methods belonging to an object. Here is an example:
my_list = [1, 2, 3] dir(my_list) # Output: ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', # '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', # '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', # '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', # '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', # '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', # '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', # 'remove', 'reverse', 'sort']
Our introspection gave us the names of all the methods of a list. This can be handy when you are not able to recall a method name. If we run dir() without any argument then it returns all names in the current scope.
15.2. type and id ¶
The type function returns the type of an object. For example:
print(type('')) # Output: print(type([])) # Output: print(type(<>)) # Output: print(type(dict)) # Output: print(type(3)) # Output:
id returns the unique ids of various objects. For instance:
name = "Yasoob" print(id(name)) # Output: 139972439030304
15.3. inspect module¶
The inspect module also provides several useful functions to get information about live objects. For example you can check the members of an object by running:
import inspect print(inspect.getmembers(str)) # Output: [('__add__',
There are a couple of other methods as well which help in introspection. You can explore them if you wish.
© Copyright 2017, Muhammad Yasoob Ullah Khalid Revision 9b6262ee .
Read the Docs v: latest
Versions latest Downloads pdf html epub On Read the Docs Project Home Builds Free document hosting provided by Read the Docs.
Saved searches
Use saved searches to filter your results more quickly
Cancel Create saved search
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
This repository has been archived by the owner on Aug 6, 2022. It is now read-only.
Deserialize ROS messages that are unknown at compilation time
License
facontidavide/ros_type_introspection
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Switch branches/tags
Branches Tags
Could not load branches
Nothing to show
Could not load tags
Nothing to show
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Cancel Create
- Local
- Codespaces
HTTPS GitHub CLI
Use Git or checkout with SVN using the web URL.
Work fast with our official CLI. Learn more about the CLI.
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
Latest commit message
Commit time
September 3, 2020 12:51
November 6, 2019 19:17
October 13, 2016 15:07
June 22, 2016 05:33
September 3, 2020 12:51
September 30, 2019 23:56
June 22, 2016 14:01
August 25, 2020 08:30
September 20, 2017 15:02
September 3, 2020 12:51
README.md
This library will be discontinued .
A large refactoring has been done to create a better and simpler library. All the development effort will be moved there.
Have a look at ros_msg_parser
Ros Message Introspection
This simple library extracts information from a ROS Message, even when its type is unknown at compilation time.
Have you ever wanted to build an app that can subscribe to any topic and extract its content, or can read data from any rosbag ? What if the topic and/or the bag contains user defined ROS types ignored at compilation time?
The common solution in the ROS ecosystem is to use Python, that provides the needed introspection. Tools, for instance, like rqt_plot and rqt_bag took this approach. This library implements a C++ alternative.
This library is particularly useful to extract data from two type-erasing classes provided by ROS itself:
- topic_tools::ShapeShifter: a type used to subscribe to any topic, regardless of the original type.
- rosbag::MessageInstance: the generic type commonly used to read data from a ROS bag.
Please take a look to the examples and unit tests to see how to use the library.
Some background
The ROS Message Types can be described as a Interface Description Language. This approach is very well known and commonly used on the web and in distributed systems in general.
A rosmsg is defined by the user; an «IDL compiler», i.e. gencpp, reads this schema and generates a header file that contains the source code that the user shall include in his/her applications. The inclusion of this header file is needed on both the publisher and the subscriber sides.
This approach provides strong and type-safe contracts between the producer and the consumer of the message and, additionally, is needed to implements a fast serialization / deserialization mechanism.
The only «problem» is that in very few use cases (for instance if you want to build a plugin to load ROS bags in MATLAB) you don’t know in advance which ROS Messages you will need to read. Therefore, you won’t be able to include the necessary header files.
Acknowledgements
This library is inspired by these other libraries matlab_rosbag and cpp_introspection.
Основные принципы программирования: интроспекция и рефлексия
На данный момент этот блок не поддерживается, но мы не забыли о нём! Наша команда уже занята его разработкой, он будет доступен в ближайшее время.
Часто во время работы программы нам бывает нужна информация о данных — например, какой у них тип или являются ли они экземпляром класса (в ООП). Опираясь на эти знания, нам нужно проводить над данными некоторые операции, или даже изменять их — но необходимого вида данных у нас может и не быть! Если вы ничего не поняли, не расстраивайтесь — мы подробно во всём разберёмся. Всё, что я здесь описал — это иллюстрация целей двух возможностей, присутствующих почти в каждом современном языке программирования: интроспекции и рефлексии.
Интроспекция
Интроспекция — это способность программы исследовать тип или свойства объекта во время работы программы. Как мы уже упоминали, вы можете поинтересоваться, каков тип объекта, является ли он экземпляром класса. Некоторые языки даже позволяют узнать иерархию наследования объекта. Возможность интроспекции есть в таких языках, как Ruby, Java, PHP, Python, C++ и других. В целом, инстроспекция — это очень простое и очень мощное явление. Вот несколько примеров использования инстроспекции:
// Java if(obj instanceof Person)
//PHP if ($obj instanceof Person) < // делаем что угодно >
В Python самой распространённой формой интроспекции является использование метода dir для вывода списка атрибутов объекта:
# Python class foo(object): def __init__(self, val): self.x = val def bar(self): return self.x . dir(foo(5)) => ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', 'bar', 'x']
В Ruby интроспекция очень полезна — в частности из-за того, как устроен сам язык. В нём всё является объектами — даже класс — и это приводит к интересным возможностям в плане наследования и рефлексии (об этом ниже). Если вы хотите узнать об этом больше, советую прочитать мини-цикл Metaprogramming in Ruby.
Прим. перев. Также не будет лишним прочитать нашу статью, посвящённую интроспекции в Ruby.
Вот несколько простых примеров интроспекции с использованием IRB (Interactive Ruby Shell):
# Ruby $ irb irb(main):001:0> A=Class.new => A irb(main):002:0> B=Class.new A => B irb(main):003:0> a=A.new => # irb(main):004:0> b=B.new => # irb(main):005:0> a.instance_of? A => true irb(main):006:0> b.instance_of? A => false irb(main):007:0> b.kind_of? A => true
Вы также можете узнать у объекта, экземпляром какого класса он является, и даже “сравнить” классы.
# Ruby irb(main):008:0> A.instance_of? Class => true irb(main):009:0> a.class => A irb(main):010:0> a.class.class => Class irb(main):011:0> A > B => true irb(main):012:0> B true
Однако интроспекция — это не рефлексия; рефлексия позволяет нам использовать ключевые принципы интроспекции и делать действительно мощные вещи с нашим кодом.
Рефлексия
Интроспекция позволяет вам изучать атрибуты объекта во время выполнения программы, а рефлексия — манипулировать ими. Рефлексия — это способность компьютерной программы изучать и модифицировать свою структуру и поведение (значения, мета-данные, свойства и функции) во время выполнения. Простым языком: она позволяет вам вызывать методы объектов, создавать новые объекты, модифицировать их, даже не зная имён интерфейсов, полей, методов во время компиляции. Из-за такой природы рефлексии её труднее реализовать в статически типизированных языках, поскольку ошибки типизации возникают во время компиляции, а не исполнения программы (подробнее об этом здесь). Тем не менее, она возможна, ведь такие языки, как Java, C# и другие допускают использование как интроспекции, так и рефлексии (но не C++, он позволяет использовать лишь интроспекцию).
По той же причине рефлексию проще реализовать в интерпретируемых языках, поскольку когда функции, объекты и другие структуры данных создаются и вызываются во время работы программы, используется какая-то система распределения памяти. Интерпретируемые языки обычно предоставляют такую систему по умолчанию, а для компилируемых понадобится дополнительный компилятор и интерпретатор, который следит за корректностью рефлексии.
Мне кажется, что мы сказали много об определении рефлексии, но смысла это пока несёт мало. Давайте взглянем на примеры кода ниже (с рефлексией и без), каждый из которых создаёт объект класса Foo и вызывает метод hello.
// ECMAScript - как JavaScript // Без рефлексии new Foo().hello() // С рефлексией // предполагаем, что Foo принадлежит this new this['Foo']()['hello']() // или не предполагаем new (eval('Foo'))()['hello']() // или вообще не заморачиваемся eval('new Foo().hello()')
// Java // Без рефлексии Foo foo = new Foo(); foo.hello(); // С рефлексией Object foo = Class.forName("complete.classpath.and.Foo").newInstance(); // Альтернатива: Object foo = Foo.class.newInstance(); Method m = foo.getClass().getDeclaredMethod("hello", new Class[0]); m.invoke(foo);
# Python # Без рефлексии obj = Foo() obj.hello() # С рефлексией class_name = "Foo" method = "hello" obj = globals()[class_name]() getattr(obj, method)() # С eval eval("Foo().hello()")
# Ruby # Без рефлексии obj = Foo.new obj.hello # С рефлексией class_name = "Foo" method = :hello obj = Kernel.const_get(class_name).new obj.send method # С eval eval "Foo.new.hello"
Этот список отнюдь не исчерпывает возможности рефлексии. Это очень мощный принцип, который к тому же является обычной практикой в метапрограммировании. Тем не менее, при использовании рефлексии нужно быть очень внимательным. Хотя у неё и есть свои преимущества, код, использующий рефлексию, значительно менее читаем, он затрудняет отладку, а также открывает двери по-настоящему плохим вещами, например, инъекции кода через выражения eval.
Eval-выражения
Некоторые рефлективные языки предоставляют возможность использования eval-выражений — выражений, которые распознают значение (обычно строку) как выражение. Такие утверждения — это самый мощный принцип рефлексии и даже метапрограммирования, но также и самый опасный, поскольку они представляют собой угрозу безопасности.
Рассмотрим следующий пример кода на Python, который принимает данные из стороннего источника в Сети (это одна из причин, по которой люди пользуются eval-выражениями):
session['authenticated'] = False data = get_data() foo = eval(data)
Защита программы будет нарушена, если кто-то передаст в метод get_data() такую строку:
"session.update(authenticated=True)"
Для безопасного использования eval-утверждений нужно сильно ограничивать формат входных данных — и обычно это лишь занимает лишнее время.
Заключение
Интроспекция и рефлексия — это очень мощные инструменты современных языков, и их понимание может позволить вам писать по-настоящему крутой код. Ещё раз отметим: интроспекция — это изучение атрибутов объекта, а рефлексия — это манипуляция ими. Будьте внимательны при использовании рефлексии, поскольку она может сделать ваш код нечитаемым и уязвимым. Чем больше сила, тем больше и ответственность — вот девиз всего, что связано с метапрограммированием.
Следите за новыми постами по любимым темам
Подпишитесь на интересующие вас теги, чтобы следить за новыми постами и быть в курсе событий.