如何在MATLAB中处理函数参数的名称/值对
我有一个函数,可选参数作为名称/值对。
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')
似乎有很多努力涉及到提取适当的价值(它仍然不是特别强大的再次严重指定的投入)。 有没有更好的方式来处理这些名称/值对? MATLAB有没有帮助function?
我更喜欢使用结构来select我的选项。 这给你一个简单的方法来存储的选项和一个简单的方法来定义它们。 而且,整个事情变得相当紧凑。
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
InputParser帮助这个。 有关更多信息,请参阅分析函数input 。
我可以在这里讨论几个小时,但是对于一般的Matlab签名处理还是没有一个好的格式塔视图。 但是这里有几条build议。
首先,采取自由放任的方式来validationinputtypes。 相信来电者。 如果你真的想要强typestesting,你需要一个像Java这样的静态语言。 尝试在Matlab中的每个地方强制执行types安全,并且最终将在LOC和执行时间的很大一部分用于在用户空间中运行时间typestesting和强制转换,这在Matlab的许多function和开发速度。 我经过惨痛的教训才学到这个。
对于API签名(函数旨在从其他函数中调用,而不是从命令行中),请考虑使用单个参数而不是varargin。 然后,它可以在多个参数之间传递,而不必将其转换为逗号分隔的可变参数列表的列表。 乔纳斯说,结构非常方便。 在结构体和n-by-2 {name,value; …}单元格之间也有一个很好的同构关系,你可以设置一些函数在你的函数内部将它们转换为内部使用的函数。
function example(args) %EXAMPLE % % Where args is a struct or {name,val;...} cell array
无论是使用inputParser,还是像这些其他优秀的例子一样,使用你自己的名字/ valparsing器,把它打包在一个单独的标准函数中,你将从函数的顶部调用具有name / val签名的标准函数。 让它接受一个方便写出的数据结构的默认值列表,你的argparsing调用看起来就像函数签名声明,这有助于可读性,避免复制和粘贴样板代码。
这是parsing调用的样子。
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
这里有一个函数来实现这个名字/ valparsing。 你可以挖空它,并用inputParser,你自己的types约定等replace它。我认为n-by-2单元约定使得可读的源代码更好, 考虑保持这一点。 在接收代码中,结构通常更方便处理,但使用expression式和文字构造更方便。 (结构要求每行的“……”延续,并且把单元格的值从扩展到非标量结构。)
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
很不幸的是,string和datenums在Matlab中不是一stream的types。
阅读Loren在这个问题上的信息 。 不要忘记阅读评论部分… – 你会看到有不同的方法来讨论这个话题。 他们都工作,所以select一个首选的方法实际上是个人品味和可维护性的问题。
我个人使用一个自定义的函数派生自许多统计工具箱function(如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
与任何未公开的函数一样,没有任何保证,它可以在后续版本中从MATLAB中删除,无需任何通知…不pipe怎样,我相信有人在文件交换中发布了一个老版本的getargs ..
该函数将参数作为名称/值对进行处理,使用一组有效的参数名称及其默认值。 它将分析的参数作为单独的输出variables返回。 默认情况下,无法识别的名称/值对会引发错误,但我们也可以在一个额外的输出中静静地捕获它们。 这里是function描述:
$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.
例:
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
现在可以通过以下任何一种方式调用这个示例函数:
>> 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
这是一些无效的调用和抛出的错误:
%# 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.
inputParser:
正如其他人所提到的,正式推荐的parsing函数input的方法是使用inputParser
类。 它支持各种scheme,如指定所需的input,可选的位置参数和名称/值参数。 它还允许对input执行validation (例如检查参数的类/types和大小/形状)
我是这样的自制锅炉代码更大的粉丝:
function TestExample(req1, req2, varargin) for i = 1:2:length(varargin) if strcmpi(varargin{i}, 'alphabet') ALPHA = varargin{i+1}; elseif strcmpi(varargin{i}, 'cutoff') CUTOFF = varargin{i+1}; %we need to remove these so seqlogo doesn't get confused rm_inds = [rm_inds i, i+1]; %#ok<*AGROW> elseif strcmpi(varargin{i}, 'colors') colors = varargin{i+1}; rm_inds = [rm_inds i, i+1]; elseif strcmpi(varargin{i}, 'axes_handle') handle = varargin{i+1}; rm_inds = [rm_inds i, i+1]; elseif strcmpi(varargin{i}, 'top-n') TOPN = varargin{i+1}; rm_inds = [rm_inds i, i+1]; elseif strcmpi(varargin{i}, 'inds') npos = varargin{i+1}; rm_inds = [rm_inds i, i+1]; elseif strcmpi(varargin{i}, 'letterfile') LETTERFILE = varargin{i+1}; rm_inds = [rm_inds i, i+1]; elseif strcmpi(varargin{i}, 'letterstruct') lo = varargin{i+1}; rm_inds = [rm_inds i, i+1]; end end
这样我就可以模拟“选项”,这个值与Matlab大部分函数的参数几乎相同。
希望有所帮助,
将
根据乔纳斯的想法,这是我正在尝试的解决scheme。
function argStruct = NameValuePairToStruct(defaults, varargin) %NAMEVALUEPAIRTOSTRUCT Converts name/value pairs to a struct. % % ARGSTRUCT = NAMEVALUEPAIRTOSTRUCT(DEFAULTS, VARARGIN) converts % name/value pairs to a struct, with defaults. The function expects an % even number of arguments to VARARGIN, alternating NAME then VALUE. % (Each NAME should be a valid variable name.) % % Examples: % % No defaults % NameValuePairToStruct(struct, ... % 'foo', 123, ... % 'bar', 'qwerty', ... % 'baz', magic(3)) % % With defaults % NameValuePairToStruct( ... % struct('bar', 'dvorak', 'quux', eye(3)), ... % 'foo', 123, ... % 'bar', 'qwerty', ... % 'baz', magic(3)) % % See also: inputParser nArgs = length(varargin); if rem(nArgs, 2) ~= 0 error('NameValuePairToStruct:NotNameValuePairs', ... 'Inputs were not name/value pairs'); end argStruct = defaults; for i = 1:2:nArgs name = varargin{i}; if ~isvarname(name) error('NameValuePairToStruct:InvalidName', ... 'A variable name was not valid'); end argStruct = setfield(argStruct, name, varargin{i + 1}); %#ok<SFLD> end end
受乔纳斯的回答启发,但更加紧凑:
function example(varargin) defaults = struct('A',1, 'B',magic(3)); %define default values params = struct(varargin{:}); for f = fieldnames(defaults)', if ~isfield(params, f{1}), params.(f{1}) = defaults.(f{1}); end end %now just access them as params.A, params.B
function argtest(varargin) a = 1; for ii=1:length(varargin)/2 [~] = evalc([varargin{2*ii-1} '=''' num2str(varargin{2*ii}) '''']); end; disp(a); who
这当然不检查正确的分配,但它很简单,任何无用的variables将被忽略。 它也只适用于数字,string和数组,但不适用于matrix,单元格或结构。
我今天写了这个,然后发现这些提到。 我使用结构的和结构'覆盖'的选项。 它基本上反映了setstructfields()的function,除了不能添加新的参数。 它也有一个recursion的选项,而setstructfields()自动执行。 它可以通过调用struct(args {:})获取配对值的单元数组。
% Overlay default fields with input fields % Good for option management % Arguments % $opts - Default options % $optsIn - Input options % Can be struct(), cell of {name, value, ...}, or empty [] % $recurseStructs - Applies optOverlay to any existing structs, given new % value is a struct too and both are 1x1 structs % Output % $opts - Outputs with optsIn values overlayed function [opts] = optOverlay(opts, optsIn, recurseStructs) if nargin < 3 recurseStructs = false; end isValid = @(o) isstruct(o) && length(o) == 1; assert(isValid(opts), 'Existing options cannot be cell array'); assert(isValid(optsIn), 'Input options cannot be cell array'); if ~isempty(optsIn) if iscell(optsIn) optsIn = struct(optsIn{:}); end assert(isstruct(optsIn)); fields = fieldnames(optsIn); for i = 1:length(fields) field = fields{i}; assert(isfield(opts, field), 'Field does not exist: %s', field); newValue = optsIn.(field); % Apply recursion if recurseStructs curValue = opts.(field); % Both values must be proper option structs if isValid(curValue) && isValid(newValue) newValue = optOverlay(curValue, newValue, true); end end opts.(field) = newValue; end end end
我会说,使用命名约定“默认”和“新”可能会更好:P
我已经根据乔纳斯和里奇棉做了一个function。 它实现了两个function(灵活的参数或限制,意味着只允许默认值中存在的variables)以及一些其他的东西,如语法糖和健康检查。
function argStruct = getnargs(varargin, defaults, restrict_flag) %GETNARGS Converts name/value pairs to a struct (this allows to process named optional arguments). % % ARGSTRUCT = GETNARGS(VARARGIN, DEFAULTS, restrict_flag) converts % name/value pairs to a struct, with defaults. The function expects an % even number of arguments in VARARGIN, alternating NAME then VALUE. % (Each NAME should be a valid variable name and is case sensitive.) % Also VARARGIN should be a cell, and defaults should be a struct(). % Optionally: you can set restrict_flag to true if you want that only arguments names specified in defaults be allowed. Also, if restrict_flag = 2, arguments that aren't in the defaults will just be ignored. % After calling this function, you can access your arguments using: argstruct.your_argument_name % % Examples: % % No defaults % getnargs( {'foo', 123, 'bar', 'qwerty'} ) % % With defaults % getnargs( {'foo', 123, 'bar', 'qwerty'} , ... % struct('foo', 987, 'bar', magic(3)) ) % % See also: inputParser % % Authors: Jonas, Richie Cotton and LRQ3000 % % Extract the arguments if it's inside a sub-struct (happens on Octave), because anyway it's impossible that the number of argument be 1 (you need at least a couple, thus two) if (numel(varargin) == 1) varargin = varargin{:}; end % Sanity check: we need a multiple of couples, if we get an odd number of arguments then that's wrong (probably missing a value somewhere) nArgs = length(varargin); if rem(nArgs, 2) ~= 0 error('NameValuePairToStruct:NotNameValuePairs', ... 'Inputs were not name/value pairs'); end % Sanity check: if defaults is not supplied, it's by default an empty struct if ~exist('defaults', 'var') defaults = struct; end if ~exist('restrict_flag', 'var') restrict_flag = false; end % Syntactic sugar: if defaults is also a cell instead of a struct, we convert it on-the-fly if iscell(defaults) defaults = struct(defaults{:}); end optionNames = fieldnames(defaults); % extract all default arguments names (useful for restrict_flag) argStruct = defaults; % copy over the defaults: by default, all arguments will have the default value.After we will simply overwrite the defaults with the user specified values. for i = 1:2:nArgs % iterate over couples of argument/value varname = varargin{i}; % make case insensitive % check that the supplied name is a valid variable identifier (it does not check if the variable is allowed/declared in defaults, just that it's a possible variable name!) if ~isvarname(varname) error('NameValuePairToStruct:InvalidName', ... 'A variable name was not valid: %s position %i', varname, i); % if options are restricted, check that the argument's name exists in the supplied defaults, else we throw an error. With this we can allow only a restricted range of arguments by specifying in the defaults. elseif restrict_flag && ~isempty(defaults) && ~any(strmatch(varname, optionNames)) if restrict_flag ~= 2 % restrict_flag = 2 means that we just ignore this argument, else we show an error error('%s is not a recognized argument name', varname); end % else alright, we replace the default value for this argument with the user supplied one (or we create the variable if it wasn't in the defaults and there's no restrict_flag) else argStruct = setfield(argStruct, varname, varargin{i + 1}); %#ok<SFLD> end end end
也可以作为一个主要的 。
对于那些对命名参数感兴趣的用户(例如:myfunction(a = 1,b ='qwerty'),使用InputParser(仅用于Matlab,Octave用户必须等到v4.2最less或者你可以尝试一个叫做InputParser2的包装器)。
另外作为一个奖励,如果你不想总是inputargstruct.yourvar
而是直接使用yourvar
,你可以使用Jason S的以下代码片段 :
function varspull(s) % Import variables in a structures into the local namespace/workspace % eg: s = struct('foo', 1, 'bar', 'qwerty'); varspull(s); disp(foo); disp(bar); % Will print: 1 and qwerty % % % Author: Jason S % for n = fieldnames(s)' name = n{1}; value = s.(name); assignin('caller',name,value); end end
自从我使用process_options.m
年龄。 它稳定,易于使用,并已包含在各种matlab框架中。 不知道什么性能,但可能会有更快的实现。
我最喜欢process_options
是unused_args
返回值,它可以用来分割参数组中的input参数,例如子stream程。
您可以轻松定义默认值。
另一件好事:使用process_options.m
通常会导致可读和可维护的选项定义。
有一个叫做parsepvpairs
的漂亮function,可以很好地处理这个问题,前提是你可以访问MATLAB的财务工具箱。 它有三个参数,期望的字段名称,默认的字段值和实际接收的参数。
例如,这里有一个函数可以在MATLAB中创build一个HTMLgraphics,并且可以采用名为“url”,“html”和“title”的可选字段值对。
function htmldlg(varargin) names = {'url','html','title'}; defaults = {[],[],'Padaco Help'}; [url, html,titleStr] = parsepvpairs(names,defaults,varargin{:}); %... code to create figure using the parsed input values end