【问题标题】:How do I retrieve the names of function parameters in matlab?如何在matlab中检索函数参数的名称?
【发布时间】:2012-05-12 23:30:51
【问题描述】:

除了解析函数文件,有没有办法在matlab中获取函数的输入和输出参数的名称?

例如,给定以下函数文件:

divide.m

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

从函数外部,我想得到一个输出参数数组,这里是:['value', 'remain'],输入参数也类似:['left', 'right']

在 matlab 中是否有一种简单的方法可以做到这一点? Matlab 通常似乎很好地支持反射。

编辑背景:

这样做的目的是在一个窗口中显示函数参数供用户输入。我正在编写一种信号处理程序,对这些信号执行操作的函数存储在一个子文件夹中。我已经有一个列表和用户可以从中选择的每个函数的名称,但有些函数需要额外的参数(例如,平滑函数可能将窗口大小作为参数)。

目前,我可以在程序将找到的子文件夹中添加一个新功能,用户可以选择它来执行操作。我缺少的是让用户指定输入和输出参数,在这里我遇到了障碍,因为我找不到函数的名称。

【问题讨论】:

  • 这不是你有function open命令的原因吗?
  • 函数内部还是外部?我假设在外面,因为这样使用起来很简单。
  • @Hannesh 你的意思是你想要函数声明本身的变量名,因为它出现在实现中?
  • @EitanT 是的。运行时必须知道名称才能在函数调用时创建变量,所以我认为必须有一种访问它们的方法。
  • 可能唯一的方法是解析文件。试试看你能不能用校验码得到任何东西。

标签: matlab reflection


【解决方案1】:

如果您的问题仅限于您想要解析文件中主要函数的function declaration line 的简单情况(即您不会处理local functionsnested functionsanonymous functions) ,然后您可以使用一些标准字符串操作和regular expressions 提取输入和输出参数名称​​它们出现在文件中。函数声明行具有标准格式,但由于以下原因,您必须考虑一些变化:

(事实证明,解释块评论是最棘手的部分......)

我已经组合了一个函数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

【讨论】:

  • 这通常不起作用的一些原因:首先是您自己提到的那个。 2. 标题前也可以有空格。 3.要解析的函数可以是另一个函数文件中的函数(甚至是嵌套的)。 4. 或者一个匿名函数,没有从中得到输出参数名称。但是确实很努力,我一直在做同样的事情,直到遇到上述问题:p
  • @GuntherStruyf:我纠正了许多限制。它仍然只对主要函数起作用,但我认为这没什么大不了的,因为无论如何你都不能从文件 outside 调用子函数或嵌套函数(除非你开始搞乱函数句柄)。
  • 如果函数用variadic input/output arguments声明,varargin和/或varargout声明怎么办?
  • @gnovice 我实际上认为which 选择默认函数(如果它重载)是一种理想的行为。如果没有,您将不得不指定一个更准确的名称。例如,convgf/conv 重载,因此如果要解析后者,则必须指定gf/conf 而不仅仅是conv。恕我直言,这不是问题。
  • 如果 OP 可以控制函数,那么在 cmets 中使用自定义分隔符并以易于解析的方式包含输入/输出变量可能会更简单。类似于%#!@ input: foo bar output: baz @!#,其中#!@ 表示开始,@!#(反转)表示结束。输入和输出标记以: 结尾,中间的空格分隔字符串是变量...
【解决方案2】:

MATLAB 提供了一种获取类元数据信息的方法(使用 meta 包),但这仅适用于 OOP 类而非常规函数。

一个技巧是动态编写一个类定义,其中包含您要处理的函数的源代码,然后让 MATLAB 处理源代码的解析(这可能很棘手,正如您想象的那样:函数定义行跨越多行,实际定义之前的cmets等...)

因此在您的案例中创建的临时文件如下所示:

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

然后可以将其传递给meta.class.fromName 以解析元数据...


这里是这个 hack 的快速而简单的实现:

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'

【讨论】:

  • 这看起来很有趣。我得试一试。
  • @Amro 如果函数使用可变输入/输出参数声明,使用可变参数和/或可变参数输出怎么办?
  • @EitanT:它只会返回 varargin 和/或 varargout。另一种方法是在每个易于区分的函数中编写“doc cmets”(类似于 javadocs 的 @param@return),并使用 regexp 解析那些函数,如 gnovice 在他的回答中所示
【解决方案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

【讨论】:

    【解决方案4】:

    当您无法从编程语言中获取有关其内容的信息(例如“反射”)时,您必须跳出该语言。

    另一位发帖人建议使用“正则表达式”,当应用于解析实际程序时总是失败,因为正则表达式无法解析上下文无关语言。

    要可靠地做到这一点,您需要一个真正的 M 语言解析器,它可以让您访问解析树。那么这很容易。

    我们的DMS Software Reengineering Toolkit 有一个可用的 M 语言解析器,并且可以做到这一点。

    【讨论】:

    • 我同意正则表达式在尝试解析整个程序时通常会失败,但OP只想解析函数定义行,它具有标准格式和有限数量的变化。标准字符串操作和正则表达式的组合可以很好地处理这种情况。
    • 也许吧。关键字“功能”当然是一个可以提供帮助的灯塔,如果他不介意有时会出错,那可能就是他所要搜索的全部内容。如果他想更加小心,他必须担心 cmets 包含看起来像函数头的东西,字符串(不太可能但理论上可能),以及嵌套的函数头(大概他不想要这些)和cmets 和换行的问题卡在函数头内的各个随机位置。简单的正则表达式不会解决问题;他必须构建大部分的词法分析器以确保他不会迷路。
    • ... 如果他对 method 参数感兴趣,他必须跟踪他所在的类,以及方法是否重载。也许他的问题被定义为简单。
    【解决方案5】:

    您是否考虑过使用map containers?

    您可以按照这些思路编写函数。 . .

    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'
    

    【讨论】:

      【解决方案6】:

      【讨论】:

      • 那是实际参数名称(在调用者的上下文中),他似乎只想要形式参数名称。
      • @Ben Voigt 是的,你是对的。我想从函数外部获取函数定义中写入的参数名称。
      • 除了输出参数之外还有类似的东西吗?从调用者那里获取参数的名称?
      猜你喜欢
      • 1970-01-01
      • 2011-04-23
      • 2021-05-25
      • 2014-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-14
      • 1970-01-01
      相关资源
      最近更新 更多