72 votes

La façon de traiter avec des paires nom/valeur des arguments d'une fonction sous MATLAB

J'ai une fonction qui prend un argument optionnel comme des 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 qu'il y a beaucoup d'efforts nécessaires pour extraire les valeurs appropriées (et il n'est toujours pas particulièrement robuste de nouveau mal des intrants spécifiques). Est-il une meilleure façon de traiter ces paires nom/valeur? Existe-il des fonctions d'assistance qui viennent avec MATLAB pour l'aider?

66voto

Jonas Points 54073

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. Aussi, toute chose 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(strmatch(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

45voto

Matthew Simoneau Points 2498

InputParser aide avec cela. Voir la Fonction d'analyse des Entrées pour plus d'informations.

13voto

Andrew Janke Points 11942

Je pourrais yack pendant des heures à ce sujet, mais n'ai pas encore une bonne gestalt vue générale Matlab signature de la manipulation. Mais voici quelques conseils.

Tout d'abord, une politique de laissez-faire à valider les types d'entrée. La confiance de l'appelant. Si vous voulez vraiment fort les essais de type, vous voulez un langage statique comme Java. Essayez de faire respecter la sécurité de type, en tous lieux, en Matlab, et vous vous retrouverez avec une bonne partie de votre LOC exécution et de temps consacré à l'exécution des essais de type et de la coercition dans l'espace utilisateur, qui se négocie dans un grand nombre de la puissance et de la vitesse de développement de Matlab. J'ai appris cela à la dure.

Pour l'API de signatures (fonctions destinées à être appelé à d'autres fonctions, au lieu de partir de la ligne de commande), pensez à utiliser un seul argument Args au lieu de mots clé varargin. Ensuite, il peut être passé entre plusieurs arguments sans avoir à le convertir et à partir d'une liste séparée par des virgules pour les mots clé varargin signatures. Les structures, comme Jonas dit, sont très confortables. Il y a aussi une belle isomorphisme entre les structures et n-par-2 {nom,valeur;...} cellules, et vous pourriez mettre en place un couple de fonctions pour convertir entre eux à l'intérieur de vos fonctions à celui qu'on veut l'utiliser en interne.

function example(args)
%EXAMPLE
%
% Where args is a struct or {name,val;...} cell array

Si vous utilisez inputParser ou rouler votre propre nom/val de l'analyseur comme ces autres exemples, l'emballer dans une norme distincte de la fonction que vous allez l'appeler à partir du haut de votre fonctions qui ont nom/val de signatures. Ont-il accepter la valeur par défaut de la liste dans une structure de données qui est pratique pour écrire, et votre arg-analyse des appels ressemble en quelque sorte à la fonction de la signature des déclarations, ce qui contribue à la lisibilité, et d'éviter les copier-coller de code réutilisable.

Voici ce que l'analyse des appels pourrait ressembler.

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 mettre en œuvre le nom/val d'analyse de cette façon. Vous pourriez creuser et de le remplacer avec inputParser, votre propre type de conventions, etc. Je pense que les n-cellules by-2 de la convention rend bien lisible du code source; envisager de garder. Les structures sont généralement plus pratique de travailler avec des dans le code, mais les n-cellules by-2 sont plus faciles à construire en utilisant des expressions et des littéraux. (Les structures exigent le ",..." suite à chaque ligne, et de garder les valeurs de cellule de l'expansion de non scalaires des structures.)

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 donc regrettable que les chaînes et datenums ne sont pas de première classe de types de Matlab.

6voto

Amro Points 72743

Personnellement, j'utilise une fonction personnalisée dérivée à partir d'une méthode utilisée par de nombreuses boîte à outils de Statistiques fonctions (comme kmeans, pca, svmtrain, ttest2, ...)

Être interne d'une fonction d'utilité, il a changé et a été renommé de nombreuses fois au fil des versions. En fonction de vos MATLAB version, essayez de regarder pour 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 avec tous les sans-papiers de fonction, il n'y a pas de garanties et il pourrait être retiré de MATLAB dans les versions ultérieures, sans préavis... de toute façon, je crois que quelqu'un a posté une ancienne version de comme getargs sur l'Échange de Fichiers..

La fonction de processus paramètres de paires nom/valeur, à l'aide d'un ensemble de paramètres valides noms ainsi que leurs valeurs par défaut. Il renvoie les paramètres analysés en tant que distincts des variables de sortie. Par défaut, méconnue des paires nom/valeur générer une erreur, mais on pourrait aussi silencieusement les saisir dans une sortie supplémentaire. Ici est 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.

Exemple:

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

Désormais cet exemple, la fonction pourrait être appelé comme l'un des moyens suivants:

>> 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 invalide les appels et les erreurs de levée:

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

Comme d'autres l'ont mentionné, officiellement approche recommandée pour l' analyse des fonctions des entrées est d'utiliser inputParser classe. Il prend en charge divers projets tels que la spécification des intrants, en option les arguments de position, et le nom/valeur des paramètres. Il permet également d'effectuer la validation des données (telles que la vérification de la classe, du type et de la taille/forme des arguments)

5voto

Yair Altman Points 4091

Lire Loren informatif de post sur cette question. N'oubliez pas de lire les commentaires de l'article... - Vous verrez qu'il ya tout à fait quelques approches différentes à ce sujet. Ils travaillent tous, de sorte que la sélection d'une méthode préférée est vraiment une question de goût personnel et de maintenabilité.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X