Как иметь дело с парами имя/значение аргументов функции в 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 для помощи?

65
задан Amro 10 August 2012 в 18:54
поделиться

8 ответов

Я предпочитаю использовать структуры для своих вариантов. Это дает вам простой способ сохранить параметры и простой способ их определения. Кроме того, все это становится довольно компактным.

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
59
ответ дан 24 November 2019 в 15:23
поделиться

Вот решение, которое я пробую, основанное на идее Джонаса.

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
1
ответ дан 24 November 2019 в 15:23
поделиться

Прочитайте информативный пост Лорена по этому вопросу. Не забудьте прочитать раздел комментариев... - Вы увидите, что существует довольно много различных подходов к этой теме. Все они работают, так что выбор предпочтительного метода - это вопрос личного вкуса и удобства обслуживания.

5
ответ дан 24 November 2019 в 15:23
поделиться

InputParser помогает в этом. См. Входные данные функции синтаксического анализа для получения дополнительной информации.

43
ответ дан 24 November 2019 в 15:23
поделиться

Я мог бы говорить об этом часами, но у меня все еще нет хорошего гештальт-обзора общей работы с сигнатурами в Matlab. Но вот пара советов.

Во-первых, используйте подход laissez faire к проверке входных типов. Доверяйте вызывающей стороне. Если вам действительно нужна сильная проверка типов, вам нужен статический язык, такой как Java. Попытайтесь обеспечить безопасность типов везде в Matlab, и вы закончите тем, что значительная часть вашего LOC и времени выполнения будет посвящена тестам типов во время выполнения и принуждению в пользовательской среде, что отменяет большую часть мощности и скорости разработки Matlab. Я понял это на собственном опыте.

Для сигнатур API (функций, предназначенных для вызова из других функций, а не из командной строки), рассмотрите возможность использования единственного аргумента Args вместо varargin. Тогда его можно будет передавать между несколькими аргументами без необходимости преобразовывать его в список, разделенный запятыми, и обратно для подписей varargin. Структуры, как говорит Джонас, очень удобны. Существует также хороший изоморфизм между структурами и ячейками n-by-2 {name,value;...}, и вы можете создать пару функций для преобразования между ними внутри ваших функций в ту, которую они хотят использовать внутри.

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

Используете ли вы inputParser или создаете свой собственный парсер имен/значений, как в других прекрасных примерах, упакуйте его в отдельную стандартную функцию, которую вы будете вызывать из верхней части ваших функций, имеющих сигнатуры имен/значений. Пусть она принимает список значений по умолчанию в структуре данных, которую удобно записывать, и ваши вызовы arg-парсера будут выглядеть как объявления сигнатур функций, что улучшит читаемость и позволит избежать копирования и вставки шаблонов.

Вот как могут выглядеть вызовы парсинга.

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

А вот функция для реализации разбора имен/значений таким образом. Вы можете вырезать ее и заменить на inputParser, свои собственные соглашения о типах и т.д. Я думаю, что соглашение о ячейках n-by-2 делает исходный код хорошо читаемым; рассмотрите возможность его сохранения. Со структурами обычно удобнее работать в принимающем коде, но ячейки n-by-2 удобнее строить с помощью выражений и литералов. (Структуры требуют продолжения ",..." в каждой строке и защиты значений ячеек от расширения до нескалярных структур.)

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

Очень жаль, что строки и данные не являются первоклассными типами в Matlab.

12
ответ дан 24 November 2019 в 15:23
поделиться

Мне больше нравится код самодельного котла, например, такой:

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 принимают свои аргументы.

Надеюсь, что это поможет,

Уилл

3
ответ дан 24 November 2019 в 15:23
поделиться

Лично я использую пользовательскую функцию, полученную из частного метода, используемого многими функциями Statistics Toolbox (такими как 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 в последующих выпусках без какого-либо уведомления... В любом случае, я полагаю, что кто-то выложил ее старую версию как getargs на Файлообменнике...

Функция обрабатывает параметры как пары имя/значение, используя набор допустимых имен параметров вместе с их значениями по умолчанию. Она возвращает разобранные параметры в виде отдельных выходных переменных. По умолчанию нераспознанные пары имя/значение вызывают ошибку, но мы могли бы также молча фиксировать их в дополнительном выводе. Вот описание функции:

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

Как уже говорилось, официально рекомендуемый подход к разбору входных данных функций заключается в использовании класса inputParser . Он поддерживает различные схемы, такие как указание обязательных входных данных, необязательных позиционных аргументов и параметров имя/значение. Он также позволяет выполнять валидацию входных данных (например, проверять класс/тип и размер/форму аргументов)

.
6
ответ дан 24 November 2019 в 15:23
поделиться
function argtest(varargin)

a = 1;

for ii=1:length(varargin)/2
    [~] = evalc([varargin{2*ii-1} '=''' num2str(varargin{2*ii}) '''']);
end;

disp(a);
who

Это, конечно, не проверяет правильность присвоения, но это просто, и любая бесполезная переменная будет проигнорирована в любом случае. Он также работает только для чисел, строк и массивов, но не для матриц, ячеек или структур.

0
ответ дан 24 November 2019 в 15:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: