Вопрос по matlab, reflection – Как я могу получить имена параметров функции в Matlab?

18

Помимо синтаксического анализа файла функции, есть ли способ получить имена входных и выходных аргументов функции в matlab?

Например, дан следующий файл функции:

divide.m

<code>function [value, remain] = divide(left, right)
     value = floor(left / right);
     remain = left / right - value;
end
</code>

Из-за пределов функции я хочу получить массив выходных аргументов здесь:['value', 'remain']и аналогично для входных аргументов:['left', 'right'].

Есть ли простой способ сделать это в Matlab? Матлаб обычно, кажется, хорошо поддерживает рефлексию.

РЕДАКТИРОВАТЬ Фон:

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

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

Разве это не причина, по которой у вас есть команда open? Rasman
@ Расман, хочешь объяснить? Hannesh
@ Hannesh Вы хотите сказать, что хотите, чтобы имена переменных были указаны в самом объявлении функции, как это выглядит в реализации? Eitan T
внутри самой функции или снаружи? Я предполагаю снаружи, так как это делает его тривиальным для использования. Gunther Struyf
Да, снаружи. Hannesh

Ваш Ответ

6   ответов
10

когда вы хотите разобратьстрока объявления функции основной функции в файле (т. е. вы не будете иметь дело сместные функции, вложенные функции, или жеанонимные функции), то вы можете извлечь имена входных и выходных аргументовas they appear in the file используя некоторые стандартные строковые операции ирегулярные выражения, Строка объявления функции имеет стандартный формат, но вам нужно учесть несколько вариантов из-за:

Varying amounts of white space or blank lines, The presence of single-line or block comments, and Having the declaration broken up on more than one line.

(It turns out that accounting for a block comment was the trickiest part...)

Я собрал функциюget_arg_names это будет обрабатывать все вышеизложенное. Если вы дадите ему путь к файлу функции, он вернет два массива ячеек, содержащих ваши строки входных и выходных параметров (или пустые массивы ячеек, если таковых нет). Обратите внимание, что функции с переменными списками ввода или вывода будут просто перечислять'varargin' или же'varargout'соответственно для имен переменных. Вот эта функция:

function [inputNames, outputNames] = get_arg_names(filePath)

    % Open the file:
    fid = fopen(filePath);

    % Skip leading comments and empty lines:
    defLine = '';
    while all(isspace(defLine))
        defLine = strip_comments(fgets(fid));
    end

    % Collect all lines if the definition is on multiple lines:
    index = strfind(defLine, '...');
    while ~isempty(index)
        defLine = [defLine(1:index-1) strip_comments(fgets(fid))];
        index = strfind(defLine, '...');
    end

    % Close the file:
    fclose(fid);

    % Create the regular expression to match:
    matchStr = '\s*function\s+';
    if any(defLine == '=')
        matchStr = strcat(matchStr, '\[?(?<outArgs>[\w, ]*)\]?\s*=\s*');
    end
    matchStr = strcat(matchStr, '\w+\s*\(?(?<inArgs>[\w, ]*)\)?');

    % Parse the definition line (case insensitive):
    argStruct = regexpi(defLine, matchStr, 'names');

    % Format the input argument names:
    if isfield(argStruct, 'inArgs') && ~isempty(argStruct.inArgs)
        inputNames = strtrim(textscan(argStruct.inArgs, '%s', ...
                                      'Delimiter', ','));
    else
        inputNames = {};
    end

    % Format the output argument names:
    if isfield(argStruct, 'outArgs') && ~isempty(argStruct.outArgs)
        outputNames = strtrim(textscan(argStruct.outArgs, '%s', ...
                                       'Delimiter', ','));
    else
        outputNames = {};
    end

% Nested functions:

    function str = strip_comments(str)
        if strcmp(strtrim(str), '%{')
            strip_comment_block;
            str = strip_comments(fgets(fid));
        else
            str = strtok([' ' str], '%');
        end
    end

    function strip_comment_block
        str = strtrim(fgets(fid));
        while ~strcmp(str, '%}')
            if strcmp(str, '%{')
                strip_comment_block;
            end
            str = strtrim(fgets(fid));
        end
    end

end
@GuntherStruyf: я исправил многие ограничения. Он по-прежнему работает только с основной функцией, но я не думаю, что это слишком большое дело, поскольку вы не можете вызывать подфункции или вложенные функции изoutside файл в любом случае (если только вы не начинаете возиться с ручками функций).
Несколько вещей, почему это не сработает в целом: для начала тот, который вы упомянули сами. 2. Также может быть пробел перед заголовком. 3. Функция для анализа может быть функцией из другого файла функции (или даже вложенной). 4. Или анонимная функция, из которой не получены имена выходных аргументов. Но, конечно, хорошие усилия, я делал то же самое, пока не наткнулся на вышеупомянутые проблемы: p
Если OP имеет контроль над функциями, может быть проще иметь пользовательские разделители в комментариях и включать переменные in / out в простой для анализа способ. Что-то вроде%#[email protected] input: foo bar output: baz @!#, где#[email protected] обозначает начало и@!# (перевернутый) обозначает конец. Токены ввода и вывода заканчиваются на: и разделенные пробелом строки между ними являются переменными ...
Что если функция объявлена сvariadic input/output arguments, i.e. сvarargin и / илиvarargout?
@gnovice Я действительно думаю, что это желаемое поведение дляwhich выбрать функцию по умолчанию (если она перегружена). А если нет, вам придется указать более точное имя. Например,conv перегруженgf/convпоэтому, если вы хотите проанализировать последнее, вам нужно будет указатьgf/conf вместо простоconv, ИМХО, это не проблема.
-3
@Ben Voigt Да, вы правы. Я хочу получить имя параметра, как написано в определении функции, из-за пределов функции. Hannesh
Это фактическое имя параметра (в контексте вызывающей стороны), он, кажется, хочет просто формальное имя параметра.
Есть ли что-то подобное, но для выходных аргументов? Чтобы получить имена аргументов от звонящего?
10

ользуяmeta пакет), однако это доступно только для классов ООП, а не для обычных функций.

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

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

classdef SomeTempClassName
    methods
        function [value, remain] = divide(left, right)
            %# ...
        end
    end
end

который затем может быть переданmeta.class.fromName разобрать метаданные ...

Вот быстрая и грязная реализация этого хака:

function [inputNames,outputNames] = getArgNames(functionFile)
    %# get some random file name
    fname = tempname;
    [~,fname] = fileparts(fname);

    %# read input function content as string
    str = fileread(which(functionFile));

    %# build a class containing that function source, and write it to file
    fid = fopen([fname '.m'], 'w');
    fprintf(fid, 'classdef %s; methods;\n %s\n end; end', fname, str);
    fclose(fid);

    %# terminating function definition with an end statement is not
    %# always required, but now becomes required with classdef
    missingEndErrMsg = 'An END might be missing, possibly matching CLASSDEF.';
    c = checkcode([fname '.m']);     %# run mlint code analyzer on file
    if ismember(missingEndErrMsg,{c.message})
        % append "end" keyword to class file
        str = fileread([fname '.m']);
        fid = fopen([fname '.m'], 'w');
        fprintf(fid, '%s \n end', str);
        fclose(fid);
    end

    %# refresh path to force MATLAB to detect new class
    rehash

    %# introspection (deal with cases of nested/sub-function)
    m = meta.class.fromName(fname);
    idx = find(ismember({m.MethodList.Name},functionFile));
    inputNames = m.MethodList(idx).InputNames;
    outputNames = m.MethodList(idx).OutputNames;

    %# delete temp file when done
    delete([fname '.m'])
end

и просто запустите как:

>> [in,out] = getArgNames('divide')
in = 
    'left'
    'right'
out = 
    'value'
    'remain'
@EitanT: он просто вернет varargin и / или varargout. Другой способ - написать «комментарии к документу». в каждой функции, которые легко различимы (что-то вроде javadocs & apos;@param а также@return), и разберите тех, кто использует регулярные выражения, как gnovice показал в своем ответе
Это выглядит интересно. Мне придется немного поиграть с этим.
@Amro Что если функция объявлена с переменными аргументами ввода / вывода,i.e. с варагином и / или вараггоутом?
3

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

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

Теперь, где это поставить? Я бы предложил, чтобы основная функция m-файла возвращала структуру, как своего рода шаг загрузки модуля, с дескриптором функции, который указывает на подфункцию (или вложенную функцию), которая выполняет реальную работу. Это сохраняет настройку с одним файлом, которую я уверен, что вы хотите сохранить, и обеспечивает гораздо более настраиваемую настройку для ваших модулей.

function module = divide_load()
    module.fn = @my_divide;
    module.name = 'Divide';
    module.description = 'Divide two signals';
    module.param(1).name = 'left';
    module.param(1).description = 'left signal';
    module.param(1).required_shape = 'columnvector';
    % Etc, etc.

    function [value, remain] = my_divide(left, right)
         value = floor(left / right);
         remain = left / right - value;
    end
end
1

содержимом (например, «отражение»), вы должны выйти за пределы языка.

Другой автор предложил «регулярные выражения», которые всегда терпят неудачу, когда применяются к анализу реальных программ, потому что регулярные выражения не могут анализировать контекстно-свободные языки.

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

нашDMS Software Reengineering Toolkit имеет для этого доступный синтаксический анализатор языка М и может это делать.

... и если он заинтересован вmethod аргументы, которые он должен будет отслеживать, в каком классе он находится, и перегружен ли метод. Может быть, его проблема определена как простая.
Я согласен, что регулярные выражения, как правило, не будут работать при попытке разобратьentire program, но ОП хочет только разобратьfunction definition line, который имеет стандартный формат и ограниченное количество вариаций. Комбинация стандартных строковых операций и регулярных выражений может достаточно хорошо справиться с этим случаем.
Может быть. Ключевое слово "функция" конечно, маяк, который может помочь, и если он не возражает иногда ошибиться, это может быть все, что он должен искать. Если он хочет быть более осторожным, ему нужно беспокоиться о комментариях, содержащих элементы, похожие на заголовки функций и строки (менее вероятно, но возможно в теории), а также на заголовки вложенных функций (предположительно, он этого не хочет) и вопросы комментариев и разрывов строк застряли в различных случайных местах внутри заголовка функции. Простые регулярные выражения не справятся; он должен построить большую часть лексера, чтобы убедиться, что он не потерян.
0

карта контейнеров?

Вы можете написать свои функции по этим направлениям. , ,

function [outMAP] = divide(inMAP)
     outMAP = containers.Map();
     outMAP('value') = floor(inMAP('left') / inMAP('right'));
     outMAP('remain') = inMAP('left') / inMAP('right') - outMAP('value');
end

... и называть их так ...

inMAP  = containers.Map({'left', 'right'}, {4, 5});
outMAP = divide(inMAP);

... а затем просто изучите имена переменных, используя следующий синтаксис ...

>> keys(inMAP)

ans = 

    'left'    'right'

Похожие вопросы