Что такое рефлексия простыми словами в информатике

Рефлексия

Введение в рефлексию. Класс System.Type

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

Основной функционал рефлексии сосредоточен в пространстве имен System.Reflection. В нем мы можем выделить следующие основные классы:

Assembly : класс, представляющий сборку и позволяющий манипулировать этой сборкой

AssemblyName : класс, хранящий информацию о сборке

MemberInfo : базовый абстрактный класс, определяющий общий функционал для классов EventInfo, FieldInfo, MethodInfo и PropertyInfo

EventInfo : класс, хранящий информацию о событии

FieldInfo : хранит информацию об определенном поле типа

MethodInfo : хранит информацию об определенном методе

PropertyInfo : хранит информацию о свойстве

ConstructorInfo : класс, представляющий конструктор

Module : класс, позволяющий получить доступ к определенному модулю внутри сборки

ParameterInfo : класс, хранящий информацию о параметре метода

Эти классы представляют составные блоки типа и приложения: методы, свойства и т.д. Но чтобы получить информацию о членах типа, нам надо воспользоваться классом System.Type.

Класс System.Type представляет изучаемый тип, инкапсулируя всю информацию о нем. С помощью его свойств и методов можно получить эту информацию. Некоторые из его свойств и методов:

Метод FindMembers() возвращает массив объектов MemberInfo данного типа

Метод GetConstructors() возвращает все конструкторы данного типа в виде набора объектов ConstructorInfo

Метод GetEvents() возвращает все события данного типа в виде массива объектов EventInfo

Метод GetFields() возвращает все поля данного типа в виде массива объектов FieldInfo

Метод GetInterfaces() получает все реализуемые данным типом интерфейсы в виде массива объектов Type

Метод GetMembers() возвращает все члены типа в виде массива объектов MemberInfo

Метод GetMethods() получает все методы типа в виде массива объектов MethodInfo

Метод GetProperties() получает все свойства в виде массива объектов PropertyInfo

Свойство Name возвращает имя типа

Свойство Assembly возвращает название сборки, где определен тип

Свойство Namespace возвращает название пространства имен, где определен тип

Свойство IsArray возвращает true, если тип является массивом

Свойство IsClass возвращает true, если тип представляет класс

Свойство IsEnum возвращает true, если тип является перечислением

Свойство IsInterface возвращает true, если тип представляет интерфейс

Получение типа

Получение типа через typeof :

Здесь определен класс User с некоторым набором свойств и полей. И чтобы получить его тип, используется выражение Type myType = typeof(User);

Получение типа с помощью метода GetType класса Object :

В отличие от предыдущего примера здесь, чтобы получить тип, надо создавать объект класса.

Первый параметр указывает на полное имя класса с пространством имен. В данном случае класс User находится в пространстве имен TestConsole. Второй параметр указывает, будет ли генерироваться исключение, если класс не удастся найти. В данном случае значение false означает, что исключение не будет генерироваться. И третий параметр указывает, надо ли учитывать регистр символов в первом параметре. Значение true означает, что регистр игнорируется.

В данном случае класс основной программы и класс User находятся в одном проекте и компилируются в одну сборку exe. Однако может быть, что нужный нам класс находится в другой сборке dll. Для этого после полного имени класса через запятую указывается имя сборки:

Теперь исследуем тип и получим всю информацию о нем.

Источник

Рефлексия в C++14

Данная статья является расшифровкой (с небольшими правками) доклада Антона antoshkka Полухина — “Немного магии для C++14”.

Я тут недавно ковырялся с C++ и случайно открыл пару новых приемов метапрограммирования, которые позволяют делать рефлексию в C++14. Пара мотивационных примеров. Вот у вас есть какая-то POD структура, в ней какие-то поля:

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

Функция main, в ней создаем переменную нашей структуры, как-то ее инициализируем через aggregate инициализацию, а потом эту переменную пытаемся вывести в std::cout. И в этот момент у нас, по идее, должна быть ошибка компиляции: мы не определили оператор вывода в поток для нашей структуры, компилятор не знает как все это скомпилировать и вывести. Однако, оно скомпилируется и выведет содержимое структуры:

Мы можем вернуться к коду, поменять имена полей, поменять название структуры, поменять имя переменной, все что угодно можем делать — код продолжит работать и правильно выводить содержимое структуры. Давайте посмотрим как оно работает.

В заголовочном файле magicget.hpp описан оператор, он работает с любыми типами данных:

Этот оператор вызывает метод flat_write. Метод flat_write выводит фигурные скобки и содержит стрёмную строку посередине:

Давайте посмотрим дальше, что делает функция print:

Функция print выводит или не выводит запятую в зависимости от индекса поля с которым мы работаем, а дальше идет вызов функции flat_get и комментарий, что она работает как std::get, то есть по индексу возвращает поле из структуры. Появляется закономерный вопрос: как же это работает?

Работает это следующим образом: оператор вывода в поток определяет количество полей вашей структуры, итерируется по полям через индексы и выводит каждый из полей по индексу. Таким образом получается то, что вы видели в начале статьи.

Давайте далее разберемся как сделать методы flat_get и flat_tuple_size, которые работают с пользовательскими структурами, определяют количество полей в структуре, выводят эту структуру по полям:

Начнем с простого. Будем подсчитывать количество полей в структуре. У нас имеется POD-структура T:

для этой структуры мы можем написать выражение:

Это aggregate инициализация структуры. Это выражение успешно скомпилируется в том случае, если количество аргументов меньше либо равно количеству полей внутри структуры и каждый из типов аргументов соответствуют типу поля внутри структуры.

Вот из этой абракадабры мы попробуем получить количество полей внутри структуры T. Как мы это будем делать? Мы возьмем нашу структуру T и попробуем ее проинициализировать каким-то огромным количеством аргументов. Это не скомпилируется. Мы один из аргументов отбросим и попробуем еще раз. Это тоже не скомпилируется, но когда-нибудь мы доберемся до того количества аргументов, которое равно количеству полей внутри нашей структуры, и тогда это соберется. В этот момент нам нужно просто запомнить количество аргументов — и вот мы готовы: у нас есть количество полей внутри структуры. Это базовая идея. Пойдемте в детали.

Как много аргументов нам нужно с самого начала, если наша структура T содержит в себе только char или unsigned char и прочие типы размером в 1 байт? В этом случае количество полей внутри структуры T будет равно размеру этой структуры. Если у нас поля другие, например, int или указатель, то тогда количество полей будет меньше чем размер структуры.

Мы получили количество полей с которого надо начинать aggregate инициализацию. То есть мы будем инициализировать нашу структуру T с количеством аргументов равным sizeof(T). Если не получилось скомпилировать, то один аргумент откидываем, пробуем опять, если скомпилировалось, то мы нашли количество полей внутри структуры. Остается одна проблема: даже если мы угадали с количеством аргументов внутри структуры, код все равно не соберется. Потому что нам нужно точно знать тип поля.

Давайте сделаем workaround. Мы сделаем структуру с оператором неявного приведения типа к любому типу:

Это значит, что переменные этой структуры приводятся к любому типу: int, double, std::string, std::vector, любые пользовательские типы, к чему угодно.

Полностью рецепт: мы берем структуру T и пробуем сделать aggregate инициализацию этой структуры с количеством аргументов равным sizeof(T), где каждый аргумент — это инстанс нашей структуры ubiq. На этапе aggregate инициализации каждый инстанс из ubiq превратится в тип поля внутри структуры T, и нам остается только подобрать количество аргументов. Если много аргументов и не скомпилировалось, один отбрасываем и пытаемся опять. Если скомпилировалось, то считаем количество аргументов — и мы получили результат.

Теперь немного кода. Слегка меняем структуру ubiq: добавляем шаблонный параметр чтобы проще было использовать эту структуру при variadic темплейтах. Также нам понадобится std::make_index_sequence (сущность из C++14, которая разворачивается в std::index_sequence — длинную цепочку из циферок).

Готовы увидеть страшный код? Поехали.

Эта функция имеет trailing return type, то есть тип этой функции это decltype от T с
aggregate инициализацией. Если мы угадали с количеством аргументов, то это выражение скомпилируется, мы проваливаемся в тело этой функции и в выходную переменную out пишем количество аргументов, которые у нас есть. Если не получилось (мы не угадали количество аргументов), тогда компилятор подумает, что это не ошибка, а substitution failure, и он должен найти другую функцию с таким же именем, но менее специализированную. Он возьмет функцию #2. Функция #2 отбрасывает один из индексов (то есть уменьшает количество аргументов на единичку) и вызывает detect_fields_count опять. Опять будет вызвана либо первая функция, либо вторая. Таким образом мы пробежимся по аргументам и найдем количество полей внутри структуры. Это была простая часть.

Впереди сложная: как получить тип поля внутри структуры T?

У нас уже есть наше выражение T c aggregate инициализацией и у нас передаются инстансы ubiq’а внутрь. У каждого инстанса ubiq’а вызывается оператор неявного приведения типа, и мы знаем тип поля внутри этого оператора. Все что нам нужно теперь — это как-то эту информацию схватить и вытащить во внешней scope, там, где мы можем с ней работать — за пределы aggregate инициализации структуры T. К несчастью, в C++ нет механизма записать тип данных в переменную. Точнее, есть std::type_index и std::type_info, но они бесполезны на этапе компиляции. Мы из них потом обратно тип не вытащим.

Давайте попробуем как-то обойти это ограничение. Для этого вспомним, что такое POD (но очень приблизительно: комитет по стандартизации любит менять определение каждые три года).

POD-структура — это структура, поля которой помечены либо public, либо private, либо protected (нас интересует только public поля). И все поля внутри этой структуры либо другие POD структуры, либо фундаментальные типы: указатели, int, std::nullptr_t. На пару минут забудем про указатели и у нас получится, что фундаментальных типов достаточно мало, меньше 32-x, и это значит, что мы каждому фундаментальному типу можем присвоить некий идентификатор (интегральную циферку). Эту циферку мы можем записать в выходной массив, вытащить этот выходной массив за пределы implicit conversion оператора, а там уже циферку обратно преобразовать к типу. Вот такая простая идея.

Поехала имплементация. Для этого меняем нашу структур ubiq:

Здесь теперь имеется указатель на выходной массив, и у этого выходного массива ужасное имя ref_, но так вышло. Также поменялся оператор неявного приведения типа: теперь он вызывает функцию type_to_id. Она конвертирует тип к идентификатору и этот идентификатор мы записываем в выходной массив ref_. Осталось нагенерировать кучу методов type_to_id. Мы это будем делать с помощью макроса:

Макрос нам сгенерирует функцию type_to_id, которая превращает тип в идентификатор и также сгенерирует для нас функцию id_to_type, которая превращают идентификатор обратно в тип. Пользователю этот макрос не виден. Сразу как мы его использовали, мы его undefine-ем. Регистрируем фундаментальные типы (тут приведены не все):

Нолик не используем. Скажу почему позже. Зарегистрировали все фундаментальные типы. Теперь делаем функцию, которая превращает тип T в массив идентификаторов полей внутри этого типа T. Все самое интересное находится в теле этой функции:

Здесь идет aggregate инициализация временной переменной, и мы передаем туда инстансы ubiq’а. В этот раз они держат указатель на выходной массив: здесь types это выходной массив, в который мы запишем идентификаторы типов полей. После этой строчки (после инициализации временной переменной) выходной массив types будет хранить в себе идентификаторы типа каждого поля. Функция type_to_array_of_type_ids является constexpr, т.е все можно использовать на этапе компиляции. Красотень! Нам осталось идентификаторы превратить обратно в типы. Делается это вот так:

Нулевая строчка: здесь мы получаем массив идентификаторов. Здесь тип переменной a — это что-то похожее на std::array, но сильно дотюненный, чтобы им можно было пользоваться в constexpr выражениях (потому что у нас C++14, а не C++17, где большинство проблем с constexpr для std::arrary поправлено).

В строчке #1 мы создаем интегральную константу от элемента из массива. Интегральная константа — это std::integral_constant первый параметр для которой это size_t_, а вторым параметром будет как раз наша a[I]. size_t_ — это using декларация, алиас. В строчке #2 мы конвертируем идентификатор обратно к типу, а в строчке #3 мы создаем std::tuple, и каждый элемент из этого tuple’а точно соответствует типам данных внутри структуры T, структуры внутри которой мы заглядывали. Теперь мы можем сделать что-нибудь очень стрёмное. Например, reinterpret_cast пользовательской структуры к tuple’у. И мы можем работать с пользовательской структурой как с tuple’ом. Ну да стрёмненько: reinterpret_cast.

Предупреждаю, не пытайтесь скопировать и запустить потому, что код немного упрощен. Например у std::tuple’а не регламентирован порядок создания и уничтожения аргументов: некоторые имплементации инициализируют аргументы с конца вперед, хранят их не в том порядке, из-за чего std::tuple не работает. Надо сделать свой.

Поехали дальше. Что делать с указателями: указатели на константные указатели, на указатели на int’ы и т.п?

У нас имеется функция type_to_id. Она возвращает std::size_t и куча битиков из этого std::size_t у нас не использовано: мы использовали только под 32 фундаментальных типа. Так вот, эти битики можно использовать под кодирование информации об указателе. Например, если у нас в пользовательской структуре содержится поле с типом unsigned char, то в бинарном виде она будет выглядеть следующим образом:

unsigned char c0; // 0b00000000 00000000 00000000 00000001

Наименее значащий бит содержит идентификатор char’а. Это единичка: так мы его назначили в макросе. Если у нас указатель unsigned char, то наиболее значащие битики будут теперь хранить информацию о том, что это указатель:

unsigned char* с1; // 0b00100000 00000000 00000000 00000001

Если у нас указатель константный, то наиболее значащие битики хранят информацию о том, что это константный указатель:

const unsigned char* с2; // 0b01000000 00000000 00000000 00000001

Если мы добавляем дополнительный уровень индирекции (еще один указатель), то другие наиболее старшие битики поменяются и будут хранить информацию о том, что у нас указатель:

const unsigned char** с3; // 0b01000100 00000000 00000000 00000001

Меняем нижележащий тип: наиболее значащие биты не меняются, наименее значащие теперь содержат идентификатор семерку, что означает, что мы работаем с short’ом:

const short** s0; // 0b01000100 00000000 00000000 00000111

Добавляем функции, которые преобразовывают тип в идентификатор (и соответственно добавляют эти битики):

И добавляем обратные функции, которые идентификатор преобразовывают обратно к типу:

Здесь if_extension — это std::enable_if с алиасами и кучей магии. Магия заключается в том, что в зависимости от идентификатора она позволяют вызвать только одну из представленных функций.

Что делать с enum’ами я не знаю. Единственное, что я смог придумать — это вызывать std::underlying_type. То есть мы теряем информацию о том что это за enum: мы не можем зарегистрировать все пользовательские enum’ы в нашем списке фундаментальных типов, это просто невозможно. Вместо этого мы кодируем только то, как этот enum хранится. Eсли он int, то мы будем сохранять его как int, если пользователь указал class enum: char, то мы получим char и будем кодировать только char, информация о типе enum’а потеряется.

Со сложными структурами и классами та же проблема: мы не можем их все зарегистрировать нашем списке фундаментальных типов. Поэтому мы будем просто еще раз заглядывать внутрь класса и все поля, которые есть в этом классе кодировать так, как будто они находятся в нашем классе нулевого уровня.

Допустим у нас структура а, в ней имеется поле тип которого это структура b, мы заглянем внутрь b и все поля из b затащим внутрь a. Я упрощаю: там еще куча логики с alignment’ами чтобы не поломалось.

Делается это так: добавляется одна функция type_to_id:

В этот раз она может возвращать массив (все прошлые возвращали size_t). Нам надо будет поменять нашу структуру ubiq чтобы она могла работать с массивами, добавить логики на то, как определять offset’ы, куда нам писать offset’ы и информацию о том с какой подструктурой мы работаем. Это все долго и не очень интересно, есть пара примеров как это получается, это тоже технические детали.

Что это нам дает и где это все можно использовать? Нет, вам не надо писать это самому т.к есть готовая библиотека, которая все это реализует. Вот что это библиотека дает вам.

Во-первых сравнения: больше не надо самому вручную писать сравнения для
POD-структур. Там есть три метода. Т.е вы можете вообще ничего не писать, а подключить один заголовочный файл и для всех POD-структур у вас будут работать сравнение из коробки.

Есть гетерогенные сравнения: можно две структуры с одинаковыми полями, но
разными типами данных, сравнить друг с другом.

Есть универсальная функция хеширования: передаете туда любую пользовательскую структуру, и она считает от нее хэш.

Операторы ввода-вывода: то что мы уже увидели во введении, тоже всё есть и работает.

Когда я в первый раз рассказывал об этой метапрограммной магии, то очень обрадовались разработчики каких-то железяк (я точно не вспомню). Они говорят, что у них есть 1000 различных плоских структур, которые представляют собой различные протоколы. То есть структура один-в-один мапится на протокол. Для каждой из этих структур у них по три сериализатора (в зависимости от того на каком железе и какие там провода используются в дальнейшем). И того у них 3000 сериализаторов. Они были этим очень недовольны. С помощью этой библиотеке они смогли 3000 сериализаторов упростить до 3-х сериализаторов. Они были безумно рады.

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

Наконец можно написать более обобщенные алгоритмы: например можно доработать boost::spirit, так, чтобы он сразу парсил в пользовательскую структуру, и чтобы не надо было эту структуру объявлять с помощью макросов из boost::fusion и boost::spirit. Кстати, один из разработчиков boost::spirit ко мне подошел и сказал: “- Это гениально! Я хочу эту штуку, дай мне ссылку на библиотеку”. Я ему дал.

Пара примеров. Есть такая страшная структура:

В ней куча полей. Проблема в том, что мы эту структуру хотим в какой-то контейнер передать. Например в std::set. Без библиотеки пришлось бы писать страшный компаратор для этой структуры. Можно было бы воспользоваться std::tie для того чтобы написать компаратор, но если структура поменяется, то надо помнить, что везде надо внести соответствующие изменения. Ад. Лучше об этом не задумываться. С библиотекой все работает из коробки: берете структуру, пихаете ее в std::set, все работает. Также работает сериализация этой структуры:

Просто в ofstream гоните переменные ни о чем не думая.

Также работает десериализация: просто из потока гоните значения обратно в структуру, вставляете в контейнер, которым хотите пользоваться:

Красота: кода минимум.

Мой любимый пример потому, что он самый бессмысленный, но выглядит красиво. Функция flat_tie: она позволяет вам проинициализировать вашу структуру из std::tuple.

Теперь у вас my_struct::i хранит значение 10, а my_struct::s хранит 11 внутри структуры s.

Таким образом на этот момент у нас имеется библиотека, которая позволяет вам делать рефлексию, какую-никакую, но работающую в C++14, куча примеров того где это можно использовать.

Но в библиотеке используется reinterpret_cast. А reinterpret_cast’ы я не люблю: они не позволяют делать функции constexpr, да и некрасиво это.

Поэтому давайте попробуем это исправить. Исправим это быстренько и простенько: всего в полмегабайта кода уложимся. Делать мы это будем в C++17. Идея следующая: в C++17 добавили structure binding. Это такая вещь, которая позволяет нам структуру разложить на поля, получить ссылки на поля. Единственная проблема состоит в том, что нужно точно знать количество полей внутри структуры иначе все не соберется, и через enable_if невозможно использовать этот structure binding. Но мы же в начале статьи уже научились получать количество полей внутри структуры. Из количества полей делаем тип данных и используем tag dispatching:

Генерим кучу функций as_tuple_impl:

У этих функций as_tuple_impl вторым параметром будет идти количество полей внутри структуры T. Если мы определили, что внутри структуры T у нас одно поле, то вызовется первая функция as_tuple_impl. Она использует structure binding, достает первое поле, делает кортеж в котором будет ссылка на это поле, возвращает этот кортеж пользователю. Если у нас два поля внутри структуры, то вызываем structure binding для двух полей. Это уже вторая функция, она раскладывает пользовательскую структурно на поля а и b, возвращаем кортеж, который хранит ссылки на первое поле и на второе поле. Красота!

Самое приятное, что все это constexpr, и теперь мы можем написать std::get который работает с пользовательскими полями и структурами — и все это на этапе компиляции. Красотень неимоверная. Единственная проблема это то, что код со structure binding еще не тестировался: компиляторы еще не поддерживают structure binding. Поэтому это красивая теория, и скорее всего с парой изменений она заработает.

Источник

Основные принципы программирования: интроспекция и рефлексия

Авторизуйтесь

Основные принципы программирования: интроспекция и рефлексия

Часто во время работы программы нам бывает нужна информация о данных — например, какой у них тип или являются ли они экземпляром класса (в ООП). Опираясь на эти знания, нам нужно проводить над данными некоторые операции, или даже изменять их — но необходимого вида данных у нас может и не быть! Если вы ничего не поняли, не расстраивайтесь — мы подробно во всём разберёмся. Всё, что я здесь описал — это иллюстрация целей двух возможностей, присутствующих почти в каждом современном языке программирования: интроспекции и рефлексии.

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

Интроспекция — это способность программы исследовать тип или свойства объекта во время работы программы. Как мы уже упоминали, вы можете поинтересоваться, каков тип объекта, является ли он экземпляром класса. Некоторые языки даже позволяют узнать иерархию наследования объекта. Возможность интроспекции есть в таких языках, как Ruby, Java, PHP, Python, C++ и других. В целом, инстроспекция — это очень простое и очень мощное явление. Вот несколько примеров использования инстроспекции:

В Python самой распространённой формой интроспекции является использование метода dir для вывода списка атрибутов объекта:

В Ruby интроспекция очень полезна — в частности из-за того, как устроен сам язык. В нём всё является объектами — даже класс — и это приводит к интересным возможностям в плане наследования и рефлексии (об этом ниже). Если вы хотите узнать об этом больше, советую прочитать мини-цикл Metaprogramming in Ruby.

Прим. перев. Также не будет лишним прочитать нашу статью, посвящённую интроспекции в Ruby.

Вот несколько простых примеров интроспекции с использованием IRB (Interactive Ruby Shell):

Вы также можете узнать у объекта, экземпляром какого класса он является, и даже «сравнить» классы.

Однако интроспекция — это не рефлексия; рефлексия позволяет нам использовать ключевые принципы интроспекции и делать действительно мощные вещи с нашим кодом.

Рефлексия

Интроспекция позволяет вам изучать атрибуты объекта во время выполнения программы, а рефлексия — манипулировать ими. Рефлексия — это способность компьютерной программы изучать и модифицировать свою структуру и поведение (значения, мета-данные, свойства и функции) во время выполнения. Простым языком: она позволяет вам вызывать методы объектов, создавать новые объекты, модифицировать их, даже не зная имён интерфейсов, полей, методов во время компиляции. Из-за такой природы рефлексии её труднее реализовать в статически типизированных языках, поскольку ошибки типизации возникают во время компиляции, а не исполнения программы (подробнее об этом здесь). Тем не менее, она возможна, ведь такие языки, как Java, C# и другие допускают использование как интроспекции, так и рефлексии (но не C++, он позволяет использовать лишь интроспекцию).

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

Мне кажется, что мы сказали много об определении рефлексии, но смысла это пока несёт мало. Давайте взглянем на примеры кода ниже (с рефлексией и без), каждый из которых создаёт объект класса Foo и вызывает метод hello.

Этот список отнюдь не исчерпывает возможности рефлексии. Это очень мощный принцип, который к тому же является обычной практикой в метапрограммировании. Тем не менее, при использовании рефлексии нужно быть очень внимательным. Хотя у неё и есть свои преимущества, код, использующий рефлексию, значительно менее читаем, он затрудняет отладку, а также открывает двери по-настоящему плохим вещами, например, инъекции кода через выражения eval.

Eval-выражения

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

Рассмотрим следующий пример кода на Python, который принимает данные из стороннего источника в Сети (это одна из причин, по которой люди пользуются eval-выражениями):

Защита программы будет нарушена, если кто-то передаст в метод get_data() такую строку:

Для безопасного использования eval-утверждений нужно сильно ограничивать формат входных данных — и обычно это лишь занимает лишнее время.

Заключение

Интроспекция и рефлексия — это очень мощные инструменты современных языков, и их понимание может позволить вам писать по-настоящему крутой код. Ещё раз отметим: интроспекция — это изучение атрибутов объекта, а рефлексия — это манипуляция ими. Будьте внимательны при использовании рефлексии, поскольку она может сделать ваш код нечитаемым и уязвимым. Чем больше сила, тем больше и ответственность — вот девиз всего, что связано с метапрограммированием.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *