J'ai une fonction qui prend des arguments optionnels sous forme de paires nom/valeur.
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')
Il semble que l'extraction des valeurs appropriées demande beaucoup d'efforts (et que les entrées mal spécifiées ne sont toujours pas robustes). Existe-t-il une meilleure façon de gérer ces paires nom/valeur? Existe-t-il des fonctions d'assistance fournies avec MATLAB?
Je préfère utiliser des structures pour mes options. Cela vous donne un moyen facile de stocker les options et un moyen facile de les définir. En outre, le tout devient plutôt 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
InputParser aide à cela. Voir Analyser les entrées de fonction pour plus d'informations.
Je pourrais crier des heures à ce sujet, mais je n’ai toujours pas une bonne vision gestaltique du traitement général des signatures Matlab. Mais voici quelques conseils.
Premièrement, adoptez une approche de laissez-faire pour valider les types d’entrée. Faites confiance à l'appelant. Si vous voulez vraiment un test de type fort, vous voulez un langage statique comme Java. Essayez de faire respecter la sécurité de type partout dans Matlab, et vous obtiendrez une bonne partie de votre LOC et de votre temps d'exécution consacrés aux tests de type d'exécution et à la coercition en mode utilisateur. . Je l'ai appris à la dure.
Pour les signatures API (fonctions destinées à être appelées à partir d'autres fonctions au lieu des lignes de commande), envisagez d'utiliser un seul argument Args au lieu de varargin. Ensuite, il peut être passé entre plusieurs arguments sans avoir à le convertir en une liste séparée par des virgules pour les signatures varargin. Les structures, comme le dit Jonas, sont très pratiques. Il existe également un isomorphisme de Nice entre les structs et les cellules {nom, valeur; ...} n-par-2, et vous pouvez configurer quelques fonctions pour les convertir entre celles-ci à l'intérieur de vos fonctions, selon ce qu'elles souhaitent utiliser en interne.
function example(args)
%EXAMPLE
%
% Where args is a struct or {name,val;...} cell array
Que vous utilisiez inputParser ou analysiez votre propre analyseur name/val comme ces autres exemples, intégrez-le dans une fonction standard distincte que vous appellerez du haut de vos fonctions et portant des signatures name/val. Faites-le accepter la liste de valeurs par défaut dans une structure de données facile à écrire, et vos appels d'analyse des arguments ressembleront à des déclarations de signature de fonction, ce qui améliore la lisibilité et évite le copier/coller de code passe-partout.
Voici à quoi pourraient ressembler les appels d'analyse.
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
Et voici une fonction pour implémenter le nom/valeur en procédant de cette manière. Vous pouvez le vider et le remplacer par inputParser, vos propres conventions de type, etc. Je pense que la convention de cellule n-2 est un code source lisible. envisager de garder cela. Il est généralement plus pratique de traiter les structures dans le code de réception, mais il est plus facile de construire les cellules n-par-2 à l'aide d'expressions et de littéraux. (Les structures requièrent la continuation ", ..." à chaque ligne et les valeurs des cellules ne doivent pas être étendues à des structures non scalaires.)
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
Il est tellement regrettable que les chaînes de caractères et les datenums ne soient pas des types de premier ordre dans Matlab.
Personnellement, j'utilise une fonction personnalisée dérivée d'une méthode privée utilisée par de nombreuses fonctions de Statistics Toolbox (telles que kmeans, pca, svmtrain, ttest2, ...)
Étant une fonction d’utilité interne, elle a changé et a été renommée à plusieurs reprises au fil des versions. Selon votre version de MATLAB, essayez de rechercher l’un des fichiers suivants:
%# 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
Comme pour toute fonction non documentée, il n'y a aucune garantie et il pourrait être supprimé de MATLAB dans les versions ultérieures sans préavis ... Quoi qu'il en soit, je pense que quelqu'un a posté une ancienne version de celui-ci sous la forme getargs sur l'échange de fichiers ..
La fonction traite les paramètres sous forme de paires nom/valeur, en utilisant un ensemble de noms de paramètres valides ainsi que leurs valeurs par défaut. Il renvoie les paramètres analysés sous forme de variables de sortie distinctes. Par défaut, les paires nom/valeur non reconnues génèrent une erreur, mais nous pouvons également les capturer silencieusement dans une sortie supplémentaire. Voici la description de la fonction:
$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
Maintenant, cet exemple de fonction peut être appelé de l'une des manières suivantes:
>> 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
Voici quelques appels non valides et les erreurs renvoyées:
%# 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.
Comme d'autres l'ont mentionné, l'approche officiellement recommandée pour l'analyse des entrées de fonctions consiste à utiliser inputParser
class. Il prend en charge divers schémas, tels que la spécification des entrées requises, des arguments de position facultatifs et des paramètres nom/valeur. Cela permet aussi d’effectuer validation sur les entrées (comme vérifier la classe/type et la taille/forme des arguments)
Lire Article informatif de Loren sur ce sujet. N'oubliez pas de lire la section commentaires ... - Vous verrez qu'il existe différentes approches de ce sujet. Ils fonctionnent tous, alors choisir une méthode préférée est vraiment une question de goût personnel et de facilité de maintenance.
Je suis un plus grand fan de code de plaque de chaudière maison comme celui-ci:
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
De cette façon, je peux simuler l'option, une paire de valeurs presque identique à celle utilisée par la plupart des fonctions Matlab.
J'espère que cela pourra aider,
Volonté
Voici la solution que je teste, basée sur l'idée de Jonas.
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
Il existe une fonction astucieuse appelée parsepvpairs
qui s'en occupe bien, à condition que vous ayez accès à la boîte à outils financière de MATLAB. Il faut trois arguments, les noms de champs attendus, les valeurs de champs par défaut et les arguments réellement reçus.
Par exemple, voici une fonction qui crée une figure HTML dans MATLAB et peut utiliser les paires de valeurs de champ facultatives nommées 'url', 'html' et '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
Depuis des siècles, j'utilise process_options.m
. Il est stable, facile à utiliser et a été inclus dans divers frameworks matlab. Cependant, je ne sais rien sur les performances - il se peut que les implémentations soient plus rapides.
La caractéristique que j'aime le plus avec process_options
est la valeur de retour unused_args
, qui peut être utilisée pour fractionner les arguments d'entrée en groupes d'arguments, par exemple pour des sous-processus.
Et vous pouvez facilement définir les valeurs par défaut.
Plus important encore: utiliser process_options.m
entraîne généralement les définitions des options readable et maintable.
Exemple de code:
function y = func(x, y, varargin)
[u, v] = process_options(varargin,
'u', 0,
'v', 1);
Inspiré par la réponse de Jonas, mais plus 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
function argtest(varargin)
a = 1;
for ii=1:length(varargin)/2
[~] = evalc([varargin{2*ii-1} '=''' num2str(varargin{2*ii}) '''']);
end;
disp(a);
who
Cela ne vérifie bien sûr pas les affectations correctes, mais c’est simple et toute variable inutile sera ignorée de toute façon. De plus, cela ne fonctionne que pour les nombres, les chaînes et les tableaux, mais pas pour les matrices, les cellules ou les structures.
J'ai fini par écrire ceci aujourd'hui, puis j'ai trouvé ces mentions . Mine utilise les options de struct et de superposition de struct. Il reflète essentiellement la fonctionnalité de setstructfields (), sauf que de nouveaux paramètres ne peuvent pas être ajoutés. Il a aussi une option pour récursir, alors que setstructfields () le fait automatiquement . Il peut prendre un tableau de cellules de valeurs appariées en appelant 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
Je dirais que l'utilisation de la convention de nommage 'defaults' et 'new' serait probablement préférable: P
J'ai créé une fonction basée sur Jonas et Richie Cotton. Il implémente les deux fonctionnalités (arguments flexibles ou restreints, ce qui signifie que seules les variables existantes dans les valeurs par défaut sont autorisées), ainsi que quelques autres éléments comme les contrôles syntaxiques du sucre et de la pureté.
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
Aussi disponible en tant que Gist .
Et pour ceux intéressés à avoir des arguments nommés réels (avec une syntaxe similaire à Python, par exemple: myfunction (a = 1, b = 'qwerty'), utilisez InputParser (uniquement pour Matlab, les utilisateurs d’Octave devront attendre la v4.2 à moins ou vous pouvez essayer un wrapper appelé InputParser2 ).
Aussi, en prime, si vous ne voulez pas toujours taper argstruct.yourvar
mais utiliser directement yourvar
, vous pouvez utiliser le fragment suivant de 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