Ric*_*ton 65 matlab arguments function name-value function-parameter
我有一个函数,它将可选参数作为名称/值对.
function example(varargin)
% Lots of set up stuff
vargs = varargin;
nargs = length(vargs);
names = vargs(1:2:nargs);
values = vargs(2:2:nargs);
validnames = {'foo', 'bar', 'baz'};
for name = names
validatestring(name{:}, validnames);
end
% Do something ...
foo = strmatch('foo', names);
disp(values(foo))
end
example('foo', 1:10, 'bar', 'qwerty')
Run Code Online (Sandbox Code Playgroud)
似乎在提取适当的值时需要付出很多努力(并且它仍然不是特别强大的再次严格指定的输入).有没有更好的方法来处理这些名称/值对?是否有MATLAB附带的辅助函数可以提供帮助?
Jon*_*nas 59
我更喜欢使用结构作为我的选择.这为您提供了一种存储选项的简便方法,以及一种简单的方法来定义它们.而且,整个事情变得相当紧凑.
function example(varargin)
%# define defaults at the beginning of the code so that you do not need to
%# scroll way down in case you want to change something or if the help is
%# incomplete
options = struct('firstparameter',1,'secondparameter',magic(3));
%# read the acceptable names
optionNames = fieldnames(options);
%# count arguments
nArgs = length(varargin);
if round(nArgs/2)~=nArgs/2
error('EXAMPLE needs propertyName/propertyValue pairs')
end
for pair = reshape(varargin,2,[]) %# pair is {propName;propValue}
inpName = lower(pair{1}); %# make case insensitive
if any(strcmp(inpName,optionNames))
%# overwrite options. If you want you can test for the right class here
%# Also, if you find out that there is an option you keep getting wrong,
%# you can use "if strcmp(inpName,'problemOption'),testMore,end"-statements
options.(inpName) = pair{2};
else
error('%s is not a recognized parameter name',inpName)
end
end
Run Code Online (Sandbox Code Playgroud)
And*_*nke 12
我可能会花费几个小时关于此,但仍然没有一般的Matlab签名处理的完美格式.但这里有一些建议.
首先,采用自由放任的方法来验证输入类型.相信来电者.如果你真的想要强类型测试,你需要像Java这样的静态语言.尝试在Matlab中的每个位置强制执行类型安全,并且最终会有大部分LOC和执行时间专门用于运行时类型测试和用户域中的强制,这会在Matlab的大量功能和开发速度中进行交易.我经过惨痛的教训才学到这个.
对于API签名(旨在从其他函数调用的函数,而不是从命令行调用的函数),请考虑使用单个Args参数而不是varargin.然后它可以在多个参数之间传递,而不必将其转换为逗号分隔的varargin签名列表.像Jonas说的那样,结构非常方便.结构体和n-by-2 {name,value; ...}单元格之间也有一个很好的同构,你可以设置几个函数在函数内部转换它们想要在内部使用它们.
function example(args)
%EXAMPLE
%
% Where args is a struct or {name,val;...} cell array
Run Code Online (Sandbox Code Playgroud)
无论您是使用inputParser还是像其他精美示例一样滚动自己的名称/ val解析器,请将其打包在一个单独的标准函数中,您可以从具有name/val签名的函数顶部调用它.让它接受一个方便写出的数据结构中的默认值列表,并且你的arg解析调用看起来有点像函数签名声明,这有助于提高可读性,并避免复制和粘贴样板代码.
这是解析调用的样子.
function out = my_example_function(varargin)
%MY_EXAMPLE_FUNCTION Example function
% No type handling
args = parsemyargs(varargin, {
'Stations' {'ORD','SFO','LGA'}
'Reading' 'Min Temp'
'FromDate' '1/1/2000'
'ToDate' today
'Units' 'deg. C'
});
fprintf('\nArgs:\n');
disp(args);
% With type handling
typed_args = parsemyargs(varargin, {
'Stations' {'ORD','SFO','LGA'} 'cellstr'
'Reading' 'Min Temp' []
'FromDate' '1/1/2000' 'datenum'
'ToDate' today 'datenum'
'Units' 'deg. C' []
});
fprintf('\nWith type handling:\n');
disp(typed_args);
% And now in your function body, you just reference stuff like
% args.Stations
% args.FromDate
Run Code Online (Sandbox Code Playgroud)
这是一个以这种方式实现名称/ val解析的函数.您可以将其挖空并用inputParser替换它,您自己的类型约定等.我认为n-by-2单元格约定使得可读性很好的源代码; 考虑保持这一点.在接收代码中,结构通常更方便处理,但是使用表达式和文字构造n-by-2单元更方便.(结构需要每行的",......"延续,并保护单元格值从扩展到非标量结构.)
function out = parsemyargs(args, defaults)
%PARSEMYARGS Arg parser helper
%
% out = parsemyargs(Args, Defaults)
%
% Parses name/value argument pairs.
%
% Args is what you pass your varargin in to. It may be
%
% ArgTypes is a list of argument names, default values, and optionally
% argument types for the inputs. It is an n-by-1, n-by-2 or n-by-3 cell in one
% of these forms forms:
% { Name; ... }
% { Name, DefaultValue; ... }
% { Name, DefaultValue, Type; ... }
% You may also pass a struct, which is converted to the first form, or a
% cell row vector containing name/value pairs as
% { Name,DefaultValue, Name,DefaultValue,... }
% Row vectors are only supported because it's unambiguous when the 2-d form
% has at most 3 columns. If there were more columns possible, I think you'd
% have to require the 2-d form because 4-element long vectors would be
% ambiguous as to whether they were on record, or two records with two
% columns omitted.
%
% Returns struct.
%
% This is slow - don't use name/value signatures functions that will called
% in tight loops.
args = structify(args);
defaults = parse_defaults(defaults);
% You could normalize case if you want to. I recommend you don't; it's a runtime cost
% and just one more potential source of inconsistency.
%[args,defaults] = normalize_case_somehow(args, defaults);
out = merge_args(args, defaults);
%%
function out = parse_defaults(x)
%PARSE_DEFAULTS Parse the default arg spec structure
%
% Returns n-by-3 cellrec in form {Name,DefaultValue,Type;...}.
if isstruct(x)
if ~isscalar(x)
error('struct defaults must be scalar');
end
x = [fieldnames(s) struct2cell(s)];
end
if ~iscell(x)
error('invalid defaults');
end
% Allow {name,val, name,val,...} row vectors
% Does not work for the general case of >3 columns in the 2-d form!
if size(x,1) == 1 && size(x,2) > 3
x = reshape(x, [numel(x)/2 2]);
end
% Fill in omitted columns
if size(x,2) < 2
x(:,2) = {[]}; % Make everything default to value []
end
if size(x,2) < 3
x(:,3) = {[]}; % No default type conversion
end
out = x;
%%
function out = structify(x)
%STRUCTIFY Convert a struct or name/value list or record list to struct
if isempty(x)
out = struct;
elseif iscell(x)
% Cells can be {name,val;...} or {name,val,...}
if (size(x,1) == 1) && size(x,2) > 2
% Reshape {name,val, name,val, ... } list to {name,val; ... }
x = reshape(x, [2 numel(x)/2]);
end
if size(x,2) ~= 2
error('Invalid args: cells must be n-by-2 {name,val;...} or vector {name,val,...} list');
end
% Convert {name,val, name,val, ...} list to struct
if ~iscellstr(x(:,1))
error('Invalid names in name/val argument list');
end
% Little trick for building structs from name/vals
% This protects cellstr arguments from expanding into nonscalar structs
x(:,2) = num2cell(x(:,2));
x = x';
x = x(:);
out = struct(x{:});
elseif isstruct(x)
if ~isscalar(x)
error('struct args must be scalar');
end
out = x;
end
%%
function out = merge_args(args, defaults)
out = structify(defaults(:,[1 2]));
% Apply user arguments
% You could normalize case if you wanted, but I avoid it because it's a
% runtime cost and one more chance for inconsistency.
names = fieldnames(args);
for i = 1:numel(names)
out.(names{i}) = args.(names{i});
end
% Check and convert types
for i = 1:size(defaults,1)
[name,defaultVal,type] = defaults{i,:};
if ~isempty(type)
out.(name) = needa(type, out.(name), type);
end
end
%%
function out = needa(type, value, name)
%NEEDA Check that a value is of a given type, and convert if needed
%
% out = needa(type, value)
% HACK to support common 'pseudotypes' that aren't real Matlab types
switch type
case 'cellstr'
isThatType = iscellstr(value);
case 'datenum'
isThatType = isnumeric(value);
otherwise
isThatType = isa(value, type);
end
if isThatType
out = value;
else
% Here you can auto-convert if you're feeling brave. Assumes that the
% conversion constructor form of all type names works.
% Unfortunately this ends up with bad results if you try converting
% between string and number (you get Unicode encoding/decoding). Use
% at your discretion.
% If you don't want to try autoconverting, just throw an error instead,
% with:
% error('Argument %s must be a %s; got a %s', name, type, class(value));
try
out = feval(type, value);
catch err
error('Failed converting argument %s from %s to %s: %s',...
name, class(value), type, err.message);
end
end
Run Code Online (Sandbox Code Playgroud)
非常不幸的是,字符串和日期不是Matlab中的一流类型.
MathWorks 使这匹老马复活了,但具有非常有用的功能,可以直接满足这一需求。它被称为函数参数验证(一个可以并且应该在文档中搜索的短语)并随 R2019b+ 版本一起提供。MathWorks 还制作了一个关于它的视频。验证的工作方式很像人们多年来提出的“技巧”。下面是一个例子:
function ret = example( inputDir, proj, options )
%EXAMPLE An example.
% Do it like this.
% See THEOTHEREXAMPLE.
arguments
inputDir (1, :) char
proj (1, 1) projector
options.foo char {mustBeMember(options.foo, {'bar' 'baz'})} = 'bar'
options.Angle (1, 1) {double, integer} = 45
options.Plot (1, 1) logical = false
end
% Code always follows 'arguments' block.
ret = [];
switch options.foo
case 'bar'
ret = sind(options.Angle);
case 'baz'
ret = cosd(options.Angle);
end
if options.Plot
plot(proj.x, proj.y)
end
end
Run Code Online (Sandbox Code Playgroud)
下面是开箱:
该arguments
块必须出现在任何代码之前(在帮助块之后确定)并且必须遵循函数定义中定义的位置顺序,我相信每个参数都需要提及。首先是必需参数,然后是可选参数,然后是名称-值对。MathWorks 还建议不要再使用varargin
关键字,但是nargin
和nargout
仍然有用。
projector
,在这种情况下。zeros(3)
不能作为应该是字符向量的参数的默认值。options
这里调用它(暗示我们可以使用结构来传递关键字参数,就像kwargs
在 Python 中一样)。functionSignatures.json
功能)。所以在这个例子中,inputDir
是一个必需的参数,因为它没有给出默认值。它也必须是 1xN 字符向量。好像与该语句相矛盾,请注意 MATLAB 将尝试转换提供的参数以查看转换后的参数是否通过。例如,如果您通过97:122
as inputDir
,它将通过 and inputDir == char(97:122)
(即inputDir == 'abcdefghijklmnopqrstuvwxyz'
)。相反,zeros(3)
由于它不是向量而不起作用。忘记在指定字符时使字符串失败,在需要 uint8 时使双精度失败,等等。这些将被转换。您需要深入挖掘以规避这种“灵活性”。
继续,'foo'
指定一个名称-值对,其值可能仅为'bar'
或'baz'
。
MATLAB 有许多
mustBe...
验证功能(开始输入mustBe
并点击 Tab 以查看可用的功能),并且很容易创建自己的功能。如果您创建自己的,如果输入不匹配,验证函数必须给出错误,而不是,例如,如果用户取消对话框uigetdir
返回0
。就我个人而言,我遵循 MATLAB 的约定并调用我的验证函数mustBe...
,所以我有像mustBeNatural
自然数这样的函数,并mustBeFile
确保我传递了一个实际存在的文件。
'Angle'
指定一个名称-值对,其值必须是双精度标量或整数,因此,例如,example(pwd, 'foo', 'baz', 'Angle', [30 70])
由于您为Angle
参数传递了一个向量,因此将不起作用。
你明白了。块有很大的灵活性arguments
——我认为太多也太少了——但是对于简单的功能来说,它既快速又简单。您仍然可能依赖于inputParser
、validateattributes
、等中的一个或多个assert
来解决更大的验证复杂性,但我总是首先尝试将事物塞入一个arguments
块中。如果它变得难看,也许我会做一个arguments
块和一些断言等。
我个人使用自定义函数派生自许多统计工具箱函数使用的私有方法(如kmeans,pca,svmtrain,ttest2,...)
作为一个内部实用功能,它发生了变化,并在发布版本中多次重命名.根据您的MATLAB版本,尝试查找以下文件之一:
%# old versions
which -all statgetargs
which -all internal.stats.getargs
which -all internal.stats.parseArgs
%# current one, as of R2014a
which -all statslib.internal.parseArgs
Run Code Online (Sandbox Code Playgroud)
与任何未记录的函数一样,没有任何保证,它可以在后续版本中从MATLAB中删除而不需要任何通知......无论如何,我相信有人在文件交换中发布了旧版本的getargs.
该函数使用一组有效参数名称及其默认值将参数作为名称/值对处理.它将解析的参数作为单独的输出变量返回.默认情况下,无法识别的名称/值对会引发错误,但我们也可以在额外的输出中静默捕获它们.这是功能描述:
$MATLABROOT\toolbox\stats\stats\+internal\+stats\parseArgs.m
function varargout = parseArgs(pnames, dflts, varargin)
%
% [A,B,...] = parseArgs(PNAMES, DFLTS, 'NAME1',VAL1, 'NAME2',VAL2, ...)
% PNAMES : cell array of N valid parameter names.
% DFLTS : cell array of N default values for these parameters.
% varargin : Remaining arguments as name/value pairs to be parsed.
% [A,B,...]: N outputs assigned in the same order as the names in PNAMES.
%
% [A,B,...,SETFLAG] = parseArgs(...)
% SETFLAG : structure of N fields for each parameter, indicates whether
% the value was parsed from input, or taken from the defaults.
%
% [A,B,...,SETFLAG,EXTRA] = parseArgs(...)
% EXTRA : cell array containing name/value parameters pairs not
% specified in PNAMES.
Run Code Online (Sandbox Code Playgroud)
function my_plot(x, varargin)
%# valid parameters, and their default values
pnames = {'Color', 'LineWidth', 'LineStyle', 'Title'};
dflts = { 'r', 2, '--', []};
%# parse function arguments
[clr,lw,ls,txt] = internal.stats.parseArgs(pnames, dflts, varargin{:});
%# use the processed values: clr, lw, ls, txt
%# corresponding to the specified parameters
%# ...
end
Run Code Online (Sandbox Code Playgroud)
现在可以通过以下任何方式调用此示例函数:
>> my_plot(data) %# use the defaults
>> my_plot(data, 'linestyle','-', 'Color','b') %# any order, case insensitive
>> my_plot(data, 'Col',[0.5 0.5 0.5]) %# partial name match
Run Code Online (Sandbox Code Playgroud)
以下是一些无效调用和抛出的错误:
%# unrecognized parameter
>> my_plot(x, 'width',0)
Error using [...]
Invalid parameter name: width.
%# bad parameter
>> my_plot(x, 1,2)
Error using [...]
Parameter name must be text.
%# wrong number of arguments
>> my_plot(x, 'invalid')
Error using [...]
Wrong number of arguments.
%# ambiguous partial match
>> my_plot(x, 'line','-')
Error using [...]
Ambiguous parameter name: line.
Run Code Online (Sandbox Code Playgroud)
正如其他人所提到的,官方推荐的解析函数输入的方法是使用inputParser
类.它支持各种方案,例如指定所需的输入,可选的位置参数和名称/值参数.它还允许对输入执行验证(例如检查类/类型和参数的大小/形状)