如果没有请求输出,如何提前退出功能?

How to exit function early if outputs are not requested?

提问人:magnesium 提问时间:9/19/2023 最后编辑:Luis Mendomagnesium 更新时间:9/20/2023 访问量:128

问:

我正在编写一个具有多个输入和输出的函数。只有在极少数情况下才需要所有输出。 此外,一些输入仅用于生成一些不频繁的输出。

一个简化的例子可以说明:

function [out1, out2] = foo(inp1, inp2, inp3)
out1 = inp1 + inp2;          % only requires inp1 and inp2
out2 = inp1 + inp2 + inp3;   % requires all inputs
end

如果我运行,我会天真地期望这能起作用:因为没有请求,不应该是必需的(我希望程序在第一行之后终止)。但是,这会导致错误并停止代码的进度。a = foo(1, 2);out2inp3

我的问题是:规避这个问题的最简单方法是什么?是否有内置功能或已知的设计模式用于缓解此问题?理想情况下,它不需要任何嵌套。类似的示例发生在整个代码中的多个位置,因此理想情况下,该解决方案也可以重复。


可能的解决方案:

检查输入/输出是可行的,但不能重复。例如

function [out1, out2] = foo(inp1, inp2, inp3)
out1 = inp1 + inp2;          % only requires inp1 and inp2
if nargout > 1
  out2 = inp1 + inp2 + inp3; % requires all inputs
end
end

如果以这种方式测试多个条件,将导致大量嵌套。

使用可能是解决方案的开始,但并不理想:return

function [out1, out2] = foo(inp1, inp2, inp3)
out1 = inp1 + inp2;          % only requires inp1 and inp2
if nargout < 2; return; end
out2 = inp1 + inp2 + inp3;   % requires all inputs
end

(这是最好的方法吗?

理想的解决方案也与参数的位置不变。例如:

[~,b] = baz(1, 2);

function [out1, out2] = baz(inp1, inp2, inp3)
out2 = inp1 + inp2;          % only requires inp1 and inp2
out1 = inp1 + inp2 + inp3;   % requires all inputs
end

也应该运行而不会出错。

函数 MATLAB 返回 输出

评论

1赞 Cris Luengo 9/20/2023
“最好的方法是什么?”——无论你认为哪个选项更易读。—— 至于你的最后一个问题,只需将输出从最少工作到最多工作排序即可。返回任意可能输出子集的典型解决方案是将按名称列出的所需输出列表作为输入参数包括:。[out1, out4] = func(x, y, ['out1','out4'])

答:

2赞 Ferro Luca 9/19/2023 #1

您应该将内置的“变量参数输入”(文档在这里)与(文档在这里)和“变量参数输出”(文档在这里)结合使用vararginnarginvarargout)

function [out1, varargout] = foo(inp1, inp2, varargin)
if nargin < 3
out1 = inp1 + inp2;
else        % only requires inp1 and inp2
out1 = inp1 + inp2;
varargout{1}= inp1 + inp2 + varargin{1};   
end
end

和他们一起玩,看看你是否能找到你想要的解决方案。 如果您有很多输入,则需要一些级联。switch-caseif-else


对于与位置无关的主题,可以实现与此类似的想法:

假设执行的操作绑定到 I/O 编号,使得输出 n = 从 1 到 n+1 的总和输入

function varargout=foo(varargin)
if nargin == nargout+1
if nargout >1
historypath = com.mathworks.mlservices.MLCommandHistoryServices.getSessionHistory;
cmd= historypath(end);
otps=extractBetween(cmd.toCharArray','[',']');
otps=strsplit(otps{1},',');
else
    otps={'pass'};
end
for tt=1:nargout
        if strcmp(otps{tt},'~')
            varargout{tt}=NaN;
        else
            varargout{tt}=sum([varargin{1:tt+1}]);
        end
   
end
else
    error('Mismatched I/Os')
end
end

此函数分析函数的命令窗口调用以绕过“~”输出。

评论

0赞 magnesium 9/19/2023
谢谢。有没有办法以与位置无关的方式使用这种方法(例如,我问题中的最后一个代码块)?
0赞 Ferro Luca 9/19/2023
我理解你想做什么,但我认为这样做没有意义,因为你可以控制它的调用方式并从源头上避免问题。你能详细说明一个有意义的案例吗?(这是相当的设计泡菜,可能需要一些复杂的代码和一些时间来编写它,这就是我问的原因)
0赞 magnesium 9/19/2023
在这种情况下,有一个大型计算,它有几个步骤。通常,此计算是最重要的 - 因此将其作为默认输出是有意义的。但是,有时您可能只想获得中间步骤的结果(而不考虑整个计算)。在这种情况下,您可能希望尽早返回第二个参数,而无需执行整个计算。一种解决方案是使中间步骤成为它们自己的函数,但我正在尝试这样做,以便您每次都可以调用相同的函数,并且计算效率很高。
1赞 Ferro Luca 9/19/2023
哦,我明白了,有趣的方法。问题是,为了能够开始工作,无论如何都必须计算第一个输出。不过,您可以为其分配一个占位符值。我将编辑答案以包含我的想法[~,b]NaN
1赞 Edric 9/19/2023
这种不计算忽略输出的方法仅在命令行上起作用,并且使用未记录的功能 - 可能会在零通知的情况下被删除。我不会依赖这个!
4赞 Edric 9/19/2023 #2

如果要避免昂贵的计算来生成未请求的输出,则必须包含对 的检查,没有其他方法可以避免计算。nargout

某些函数产生的输出与输入一样多 - 在这些情况下,使用 和 。但总的来说,如果只能根据提供的输入数量计算特定输出,那么这可能是一个尴尬的设计。vararginvarargout

另一种方法是使用 arguments来指定输入参数的默认值。

function [out1, out2] = foo(inp1, inp2, inp3)
arguments
    inp1
    inp2
    inp3 = 0
end
out1 = inp1 + inp2;
out2 = inp1 + inp2 + inp3;
end

评论

0赞 Ferro Luca 9/19/2023
我挖掘了用法,如果您有固定的数字或输入,这是非常干净的解决方案。arguments
0赞 Edric 9/19/2023
您可以与重复输入一起使用,并且名称-值对也非常简洁。(我仍然习惯它,但它非常适合摆脱处理可选输入的所有不同丑陋的方式)arguments
0赞 magnesium 9/19/2023
谢谢。有没有办法以与位置无关的方式使用这种方法(例如,我问题中的最后一个代码块)?
0赞 magnesium 9/19/2023
我知道它有点旧,但我是它的忠实粉丝 - 运行良好,并且与旧版本的 MATLAB 兼容。inputParser
0赞 Edric 9/19/2023
如果我理解正确,您希望通过指定 .没有办法做到这一点。最好重构代码,以便客户端可以自己编写所需的计算。或者,使用方法来实现和对象来提取不同的结果 - 这样可以确保计算不会重复。~
3赞 Wolfie 9/19/2023 #3

您只需检查请求的输出参数的数量,并在需要其他输入以形成未请求的输出之前从函数中检查return

function [out1, out2] = foo(inp1, inp2, inp3)
out1 = inp1 + inp2;          % only requires inp1 and inp2
% If only one output is requested then stop here
if nargout < 2
    return
end
% More than one output requested, calculate further outputs (requires further inputs)
out2 = inp1 + inp2 + inp3;   % requires all inputs
end

这如描述的那样工作,如果你打电话给你得到并且没有错误。a = foo(1,2)a=3

编辑:从您的评论中,听起来更像是您想为给定的输入填充尽可能多的输出。如果不对调用线路进行某种解析,就无法检测出哪些输出被丢弃(请参阅 MathWorks Answers ref detectoutputsuppression 上的此答案)。因此,一种方法不足以处理这种情况。~nargout[~,b] = foo(1,2,3)

因此,在这种情况下,我可能只使用元胞数组作为单个输出:

function out = foo(inp1, inp2, inp3)
out = cell(1,2); % Initialise output with two (empty) elements

out{1} = inp1 + inp2;
if nargin > 2
    out{2} = inp1 + inp2 + inp3; 
end
end

然后,如果您只需要第二个输出,则可以使用out=foo(1,2,3); out{2}

最后一种选择可能是让第一个输入决定函数的行为,即

function [out1, out2] = foo(nout, inp1, inp2, inp3)
% nout should be 1, 2, or [1,2] depending on the requested outputs to save
% on computation time for undesired outputs
if any(nout == 1)
   out1 = inp1 + inp2;
end
if any(nout == 2)
    out2 = inp1 + inp2 + inp3; 
end
end

这将节省计算不需要的输出,并且相当明确。显然,它需要对调用的行有更多的意识,因为它会像这样使用foo

a = foo( 1, 1, 2 ); % only want output 1
                    % equivalent to a = foo( 1, 1, 2, 3 )
[~,b] = foo( 2, 1, 2, 3 ); % only want output 2
                    % No computation time spend on output 1
[a,b] = foo( [1,2], 1, 2 ,3 ); % compute both outputs
[a,b] = foo( [1,2], 1, 2 ); % errors because not enough inputs to compute b

评论

0赞 magnesium 9/19/2023
谢谢。有没有办法以与位置无关的方式使用这种方法(例如,我问题中的最后一个代码块)?
0赞 Wolfie 9/19/2023
@magnesium我已经详细阐述了我的答案
0赞 Ferro Luca 9/19/2023
如果输入很多,则此解决方案可能会导致大量令人讨厌的 if-else 语句 NO?
1赞 Wolfie 9/20/2023
@cris该选项专门用于解决 OP 关于在调用函数时仅计算第二个输出的查询,这无法实现nout[~,b] = ...nargout
1赞 Cris Luengo 9/20/2023
对不起,我误解了这个例子。它列出了请求的输出。那行得通。虽然通常我已经看到使用字符串而不是数字,因为这使得使用该函数的代码更易于阅读。nout
1赞 rahnema1 9/19/2023 #4

对于每个输出,创建一个匿名函数,并将它们放在元胞数组中,并用于生成输出:arrayfun

function varargout = foo(varargin)
    fcn = {                                    ...
        @(inp1, inp2) inp1 + inp2,             ...
        @(inp1, inp2, inp3) inp1 + inp2 + inp3 ...
        };
    varargout = arrayfun(1:nargout, @(n){fcn{n}(varargin{:})});
end

如果请求输出,则执行第一个函数。如果请求两个输出,则执行两个函数:

a = foo(1, 2);        % only the first function is executed
[a,b] = foo(1, 2, 3);  % both functions are executed

请注意,您可以定义本地函数并将其句柄放在元胞数组中,而不是匿名函数:

function out1 = foo2(inp1, inp2)
    out1 = inp1 + inp2;
end

function out2 = foo3(inp1, inp2, inp3)
    out2 = inp1 + inp2 + inp3;
end

function varargout = foo(varargin)
    fcn = {@foo2, @foo3};
    varargout = arrayfun(1:nargout, @(n){fcn{n}(varargin{:})});
end

评论

1赞 Cris Luengo 9/20/2023
这只有在输出是独立的时才有效,这不太可能,因为它是计算它们的单个函数。它在句法上也比简单的“如果 nargin < 2 然后返回”更晦涩难懂(更难阅读)。
0赞 rahnema1 9/20/2023
@CrisLuengo我个人更喜欢.但是,此解决方案可能对那些反对 .if statementif
0赞 magnesium 9/20/2023 #5

受 Wolfie、Ferro Luca 和 Edric 的启发:我认为另一种解决方案是检查输入参数的存在/空,如果某些值不存在,则提前返回。这样做的一个优点是:(i)它允许检查每个变量;(ii) 如果需要进行多次检查,则不需要嵌套。

function [out1, out2] = baz(inp1, inp2, inp3)
out1 = NaN;
out2 = inp1 + inp2;                      % only requires inp1 and inp2
if ~exist('inp3', 'var'); return; end    % check if data are present for next steps 
out1 = inp1 + inp2 + inp3;               % requires all inputs
end