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
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
Here's the solution I'm trialling, based upon Jonas' idea.
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
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
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.
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.
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)
MathWorks has revived this beaten horse, but with very useful functionality that answers this need, directly. It's called Function Argument Validation (a phrase one can and should search for in the documentation) and comes with release R2019b+. MathWorks created a video about it, also. Validation works much like the "tricks" people have come up with over the years. Here is an example:
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
end
Here's the unpacking:
The arguments
block must come before any code (OK after help block) and must follow the positional order defined in the function definition, and I believe every argument requires a mention. Required arguments go first, followed by optional arguments, followed by name-value pairs. MathWorks also recommends to no longer use the varargin
keyword, but nargin
and nargout
are still useful.
projector
, in this case.zeros(3)
won't work as a default value for an argument that's supposed to be a character vector.options
struct (hinting at us that we can use structs to pass keyword arguments, like kwargs
in Python).So in the example, inputDir
is a required argument because it's given no default value. It also must be a 1xN character vector. As if to contradict that statement, note that MATLAB will try to convert the supplied argument to see if the converted argument passes. If you pass 12
as inputDir
, for example, it will pass and inputDir == '12'
. Conversely, zeros(3)
won't work, but on account of its being an array. And forget about making strings fail when you specify characters, and vice versa. MATLAB knows what you want and converts them. You'd need to dig deeper to circumvent this "flexibility."
'foo'
specifies a name-value pair whose value may be only 'bar'
or 'baz'
.
MATLAB has a number of
mustBe...
validation functions (start typingmustBe
and hit tab to see what's available), and it's easy enough to create your own. If you create your own, the validation function must provide an error if the input doesn't match, unlike, say,uigetdir
, which returns a0
if the user cancels the dialog. Personally, I follow MATLAB's convention and call my validation functionsmustBe...
, so I have functions likemustBeNatural
for natural numbers, andmustBeFile
to ensure I passed a file that actually exists.
'Angle'
specifies a name-value pair whose value must be a scalar double or integer, so, for example, example(pwd, 'foo', 'baz', 'Angle' [30 70])
won't work since you passed a vector.
You get the idea. There is a lot of flexibility with the arguments
block -- too much, and too little, I think -- but for simple functions, it's fast and easy. You still might rely on one or more of inputParser
, validateattributes
, assert
, and so on for addressing greater validation complexity, but I always try to stuff things into an arguments
block, first. If it's becoming unsightly, I'll move to the other options.
function argtest(varargin)
a = 1;
for ii=1:length(varargin)/2
[~] = evalc([varargin{2*ii-1} '=''' num2str(varargin{2*ii}) '''']);
end;
disp(a);
who
This does of course not check for correct assignments, but it's simple and any useless variable will be ignored anyway. It also only works for numerics, strings and arrays, but not for matrices, cells or structures.