How to deal with name/value pairs of function argu

2020-01-25 12:43发布

I have a function that takes optional arguments as name/value pairs.

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')

It seems that there is a lot of effort involved in extracting the appropriate values (and it still isn't particularly robust again badly specified inputs). Is there a better way of handling these name/value pairs? Are there any helper functions that come with MATLAB to assist?

13条回答
冷血范
2楼-- · 2020-01-25 13:02

I prefer using structures for my options. This gives you an easy way to store the options and an easy way to define them. Also, the whole thing becomes rather compact.

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
查看更多
疯言疯语
3楼-- · 2020-01-25 13:05

There is a nifty function called parsepvpairs that takes care of this nicely, provided you have access to MATLAB's finance toolbox. It takes three arguments, expected field names, default field values, and the actual arguments received.

For example, here's a function that creates an HTML figure in MATLAB and can take the optional field value pairs named 'url', 'html', and '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
查看更多
\"骚年 ilove
4楼-- · 2020-01-25 13:07

InputParser helps with this. See Parse Function Inputs for more information.

查看更多
Evening l夕情丶
5楼-- · 2020-01-25 13:08

Inspired by Jonas' answer, but more compact:

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
查看更多
甜甜的少女心
6楼-- · 2020-01-25 13:13

Personally I use a custom function derived from a private method used by many Statistics Toolbox functions (like kmeans, pca, svmtrain, ttest2, ...)

Being an internal utility function, it changed and was renamed many times over the releases. Depending on your MATLAB version, try looking for one of the following files:

%# 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

As with any undocumented function, there are no guarantees and it could be removed from MATLAB in subsequent releases without any notice... Anyways, I believe someone posted an old version of it as getargs on the File Exchange..

The function processes parameters as name/value pairs, using a set of valid parameter names along with their default values. It returns the parsed parameters as separate output variables. By default, unrecognized name/value pairs raise an error, but we could also silently capture them in an extra output. Here is the function description:

$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.

Example:

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

Now this example function could be called as any of the following ways:

>> 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

Here are some invalid calls and the errors thrown:

%# 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:

As others have mentioned, the officially recommended approach to parsing functions inputs is to use inputParser class. It supports various schemes such as specifying required inputs, optional positional arguments, and name/value parameters. It also allows to perform validation on the inputs (such as checking the class/type and the size/shape of the arguments)

查看更多
The star\"
7楼-- · 2020-01-25 13:14

I have made a function based on Jonas and Richie Cotton. It implements both functionalities (flexible arguments or restricted, meaning that only variables existing in the defaults are allowed), and a few other things like syntactic sugar and sanity checks.

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

Also available as a Gist.

And for those interested in having real named arguments (with a syntax similar to Python, eg: myfunction(a=1, b='qwerty'), use InputParser (only for Matlab, Octave users will have to wait until v4.2 at least or you can try a wrapper called InputParser2).

Also as a bonus, if you don't want to always have to type argstruct.yourvar but directly use yourvar, you can use the following snippet by 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
查看更多
登录 后发表回答