Jak ustawić domyślne wartości parametrów funkcji w Matlabie?

127

Czy można mieć domyślne argumenty w Matlabie? Na przykład tutaj:

function wave(a, b, n, k, T, f, flag, fTrue=inline('0'))

Chciałbym, aby prawdziwe rozwiązanie było opcjonalnym argumentem funkcji falowej. Jeśli to możliwe, czy ktoś może zademonstrować, jak to zrobić? Obecnie próbuję tego, co zamieściłem powyżej i otrzymuję:

??? Error: File: wave.m Line: 1 Column: 37
The expression to the left of the equals sign is not a valid target for an assignment.
Scott
źródło

Odpowiedzi:

151

Nie ma bezpośredniego sposobu, aby to zrobić, tak jak próbowałeś.

Typowym podejściem jest użycie „varargs” i sprawdzenie liczby argumentów. Coś jak:

function f(arg1, arg2, arg3)

  if nargin < 3
    arg3 =   'some default'
  end

end

Jest kilka bardziej wyszukanych rzeczy, z którymi możesz zrobić isempty, itp., I możesz zajrzeć do Matlab central w poszukiwaniu niektórych pakietów, które zawierają tego rodzaju rzeczy.

Można przyjrzeć się varargin, nargchkitp Są przydatnych funkcji dla tego rodzaju rzeczy. varargs pozwalają na pozostawienie zmiennej liczby końcowych argumentów, ale to nie omija problemu wartości domyślnych dla niektórych / wszystkich z nich.

Szymon
źródło
58

Użyłem inputParserobiektu, aby poradzić sobie z ustawieniem domyślnych opcji. Matlab nie zaakceptuje formatu podobnego do Pythona, który podałeś w pytaniu, ale powinieneś być w stanie wywołać funkcję w następujący sposób:

wave(a,b,n,k,T,f,flag,'fTrue',inline('0'))

Po zdefiniowaniu wavefunkcji w ten sposób:

function wave(a,b,n,k,T,f,flag,varargin)

i_p = inputParser;
i_p.FunctionName = 'WAVE';

i_p.addRequired('a',@isnumeric);
i_p.addRequired('b',@isnumeric);
i_p.addRequired('n',@isnumeric);
i_p.addRequired('k',@isnumeric);
i_p.addRequired('T',@isnumeric);
i_p.addRequired('f',@isnumeric);
i_p.addRequired('flag',@isnumeric); 
i_p.addOptional('ftrue',inline('0'),1);    

i_p.parse(a,b,n,k,T,f,flag,varargin{:});

Teraz wartości przekazywane do funkcji są dostępne za pośrednictwem i_p.Results. Nie byłem też pewien, jak sprawdzić, czy przekazany parametr jest w ftruerzeczywistości inlinefunkcją, więc pozostawiono walidator pusty.

Matt
źródło
7
Jak najlepiej mogę powiedzieć, to jest preferowaną metodą. Jest czysty, samodokumentujący się (bardziej zbiór statystyk if nargin), łatwy w utrzymaniu, kompaktowy i elastyczny.
JnBrymn
19

Innym nieco mniej hakerskim sposobem jest

function output = fun(input)
   if ~exist('input','var'), input='BlahBlahBlah'; end
   ...
end
Piotr
źródło
Ta opcja nie działa, jeśli zamierzasz używać MATLAB Coder do generowania kodu C, ponieważ Coder nie obsługuje funkcji „exist”.
Dave Tillman,
10

Tak, naprawdę fajnie byłoby mieć możliwość robienia tego, co napisałeś. Ale w MATLABie nie jest to możliwe. Wiele moich narzędzi, które zezwalają na wartości domyślne dla argumentów, zwykle jest napisanych z jawnymi kontrolami na początku, jak poniżej:

if (nargin<3) or isempty(myParameterName)
  MyParameterName = defaultValue;
elseif (.... tests for non-validity of the value actually provided ...)
  error('The sky is falling!')
end

Ok, więc generalnie zastosowałbym lepszy, bardziej opisowy komunikat o błędzie. Zobacz, że sprawdzenie pustej zmiennej umożliwia użytkownikowi przekazanie pustej pary nawiasów, [], jako symbolu zastępczego zmiennej, która przyjmie wartość domyślną. Autor musi jednak nadal dostarczyć kod, aby zastąpić ten pusty argument jego wartością domyślną.

Moje narzędzia, które są bardziej wyrafinowane, z WIELU parametrów, z których wszystkie mają domyślne argumenty, często używają interfejsu par właściwość / wartość dla domyślnych argumentów. Ten podstawowy paradygmat jest widoczny w narzędziach graficznych uchwytów w programie Matlab, a także w Optimset, Odeset itp.

Aby móc pracować z tymi parami właściwość / wartość, będziesz musiał nauczyć się o varargin, jako sposobie wprowadzania w pełni zmiennej liczby argumentów do funkcji. Napisałem (i opublikowałem) narzędzie do pracy z takimi parami właściwość / wartość, parse_pv_pairs.m . Pomaga przekształcić pary właściwość / wartość w strukturę Matlab. Umożliwia również podanie wartości domyślnych dla każdego parametru. Przekształcenie nieporęcznej listy parametrów w strukturę jest BARDZO dobrym sposobem na ich przekazanie w MATLAB-ie.


źródło
7

Oto mój prosty sposób na ustawienie domyślnych wartości funkcji za pomocą polecenia „try”:

function z = myfun (a,varargin)

%% Default values
b = 1;
c = 1;
d = 1;
e = 1;

try 
    b = varargin{1};
    c = varargin{2};
    d = varargin{3};
    e = varargin{4};
end

%% Calculation
z = a * b * c * d * e ;
end

Pozdrowienia!

Jonay Cruz
źródło
3

Odkryłem, że funkcja parseArgs może być bardzo pomocna.

Panie Fooz
źródło
3

Istnieje również „hack”, którego można użyć, chociaż w pewnym momencie może zostać usunięty z programu Matlab: Funkcja eval w rzeczywistości przyjmuje dwa argumenty, z których drugi jest uruchamiany, jeśli wystąpił błąd w pierwszym.

W ten sposób możemy użyć

function output = fun(input)
   eval('input;', 'input = 1;');
   ...
end

aby użyć wartości 1 jako domyślnej dla argumentu

vuakko
źródło
3

Wydaje mi się, że znalazłem całkiem sprytny sposób rozwiązania tego problemu, zajmując się tylko trzema liniami kodu (z wyjątkiem zawijania wierszy). Poniższy tekst jest pobierany bezpośrednio z funkcji, którą piszę, i wydaje się, że działa zgodnie z oczekiwaniami:

defaults = {50/6,3,true,false,[375,20,50,0]}; %set all defaults
defaults(1:nargin-numberForcedParameters) = varargin; %overload with function input
[sigma,shifts,applyDifference,loop,weights] = ...
     defaults{:}; %unfold the cell struct

Pomyślałem, że się tym podzielę.

Bonnevie
źródło
3

Jestem zdezorientowany, że nikt nie wskazał na ten wpis na blogu Lorena, jednego z programistów Matlab. Podejście to opiera się na vararginwszystkich tych niekończących się i bolesnych przypadkach if-then-elselub switchprzypadkach z zawiłymi warunkami i unika ich . Gdy istnieje kilka wartości domyślnych, efekt jest dramatyczny . Oto przykład z połączonego bloga:

function y = somefun2Alt(a,b,varargin)
% Some function that requires 2 inputs and has some optional inputs.

% only want 3 optional inputs at most
numvarargs = length(varargin);
if numvarargs > 3
    error('myfuns:somefun2Alt:TooManyInputs', ...
        'requires at most 3 optional inputs');
end

% set defaults for optional inputs
optargs = {eps 17 @magic};

% now put these defaults into the valuesToUse cell array, 
% and overwrite the ones specified in varargin.
optargs(1:numvarargs) = varargin;
% or ...
% [optargs{1:numvarargs}] = varargin{:};

% Place optional args in memorable variable names
[tol, mynum, func] = optargs{:};

Jeśli nadal nie rozumiesz, spróbuj przeczytać cały wpis na blogu Lorena. Napisałem kolejny post na blogu, który dotyczy brakujących domyślnych wartości pozycji. Chodzi mi o to, że mógłbyś napisać coś takiego:

somefun2Alt(a, b, '', 42)

i nadal mają domyślną epswartość tolparametru (i oczywiście @magicwywołanie zwrotne func). Kod Lorena pozwala na to z niewielką, ale trudną modyfikacją.

Na koniec tylko kilka zalet tego podejścia:

  1. Nawet przy wielu ustawieniach domyślnych kod standardowy nie jest ogromny (w przeciwieństwie do rodziny if-then-elsepodejść, które wydłużają się wraz z każdą nową wartością domyślną)
  2. Wszystkie ustawienia domyślne znajdują się w jednym miejscu. Jeśli któryś z nich wymaga zmiany, masz tylko jedno miejsce, na które możesz spojrzeć.

Powiedziano Troothowi, że jest też wada. Kiedy wpiszesz funkcję w powłoce Matlaba i zapomnisz jej parametrów, zobaczysz nieprzydatną vararginwskazówkę. Aby sobie z tym poradzić, radzimy napisać sensowną klauzulę użytkowania.

alisianoi
źródło
Link do Twojego kolejnego wpisu na blogu jest uszkodzony; czy możesz to naprawić?
equaeghe
2

Po zapoznaniu się z ASSIGNIN (dzięki tej odpowiedzi przez b3 ) i EVALIN Napisałem dwie funkcje, aby ostatecznie uzyskać bardzo prostą strukturę telefonicznej:

setParameterDefault('fTrue', inline('0'));

Oto lista:

function setParameterDefault(pname, defval)
% setParameterDefault(pname, defval)
% Author: Tobias Kienzler (https://stackoverflow.com/users/321973)
% sets the parameter NAMED pname to the value defval if it is undefined or
% empty

if ~isParameterDefined('pname')
    error('paramDef:noPname', 'No parameter name defined!');
elseif ~isvarname(pname)
    error('paramDef:pnameNotChar', 'pname is not a valid varname!');
elseif ~isParameterDefined('defval')
    error('paramDef:noDefval', ['No default value for ' pname ' defined!']);
end;

% isParameterNotDefined copy&pasted since evalin can't handle caller's
% caller...
if ~evalin('caller',  ['exist(''' pname ''', ''var'') && ~isempty(' pname ')'])
    callername = evalin('caller', 'mfilename');
    warnMsg = ['Setting ' pname ' to default value'];
    if isscalar(defval) || ischar(defval) || isvector(defval)
        warnMsg = [warnMsg ' (' num2str(defval) ')'];
    end;
    warnMsg = [warnMsg '!'];
    warning([callername ':paramDef:assigning'], warnMsg);
    assignin('caller', pname, defval);
end

i

function b = isParameterDefined(pname)
% b = isParameterDefined(pname)
% Author: Tobias Kienzler (https://stackoverflow.com/users/321973)
% returns true if a parameter NAMED pname exists in the caller's workspace
% and if it is not empty

b = evalin('caller',  ['exist(''' pname ''', ''var'') && ~isempty(' pname ')']) ;
Tobias Kienzler
źródło
1

Jest to mniej więcej zaczerpnięte z podręcznika Matlab ; Mam tylko przelotne doświadczenie ...

function my_output = wave ( a, b, n, k, T, f, flag, varargin )
  optargin = numel(varargin);
  fTrue = inline('0');
  if optargin > 0
    fTrue = varargin{1};
  end
  % code ...
end
kyle
źródło
1
W kodzie, który poprawiłem, było kilka błędów. Najpierw należy zdefiniować „optargin”. Po drugie, „varargin” to tablica komórek, która gromadzi wszystkie kolejne dane wejściowe, więc musisz użyć indeksowania tablicy komórek, aby usunąć z niej wartości.
gnovice
Muszę sprawdzić mój wzrok; Przysięgam, że nie widziałem tego wczoraj w instrukcji :(
kyle
@kyle: Nie martw się, wszyscy popełniamy błędy. Dlatego podoba mi się styl wiki SO: jeśli popełnię jakąś głupią literówkę, zwykle jest ktoś inny, kto może to złapać i szybko to naprawić. =)
gnovice
1

Matlab nie dostarcza mechanizmu do tego, ale możesz skonstruować taki w kodzie przestrzeni użytkownika, który jest terserski niż sekwencje inputParser lub "if nargin <1 ...".

function varargout = getargs(args, defaults)
%GETARGS Parse function arguments, with defaults
%
% args is varargin from the caller. By convention, a [] means "use default".
% defaults (optional) is a cell vector of corresponding default values

if nargin < 2;  defaults = {}; end

varargout = cell(1, nargout);
for i = 1:nargout
    if numel(args) >= i && ~isequal(args{i}, [])
        varargout{i} = args{i};
    elseif numel(defaults) >= i
        varargout{i} = defaults{i};
    end
end

Następnie możesz to nazwać w swoich funkcjach w ten sposób:

function y = foo(varargin)
%FOO 
%
% y = foo(a, b, c, d, e, f, g)

[a, b,  c,       d, e, f, g] = getargs(varargin,...
{1, 14, 'dfltc'});

Formatowanie to konwencja, która umożliwia odczytanie nazw parametrów do ich wartości domyślnych. Możesz rozszerzyć swoją getargs () o opcjonalne specyfikacje typu parametrów (do wykrywania błędów lub niejawnej konwersji) i zakresy liczby argumentów.

Takie podejście ma dwie wady. Po pierwsze, jest powolny, więc nie chcesz go używać do funkcji wywoływanych w pętlach. Po drugie, pomoc funkcji Matlaba - wskazówki dotyczące autouzupełniania w wierszu poleceń - nie działają dla funkcji varargin. Ale jest to całkiem wygodne.

Andrew Janke
źródło
0

możesz chcieć użyć parseparamspolecenia w programie Matlab; użycie wyglądałoby następująco:

function output = wave(varargin);
% comments, etc
[reg, props] = parseparams(varargin);
ctrls = cell2struct(props(2:2:end),props(1:2:end),2);  %yes this is ugly!
a = reg{1};
b = reg{2};
%etc
fTrue = ctrl.fTrue;
shabbychef
źródło
0
function f(arg1, arg2, varargin)

arg3 = default3;
arg4 = default4;
% etc.

for ii = 1:length(varargin)/2
  if ~exist(varargin{2*ii-1})
    error(['unknown parameter: ' varargin{2*ii-1}]);
  end;
  eval([varargin{2*ii-1} '=' varargin{2*ii}]);
end;

np. f(2,4,'c',3)powoduje, że parametr cma wartość 3.

Tobias Kienzler
źródło
0

jeśli użyjesz oktawy, możesz to zrobić w ten sposób - ale niestety Matlab nie obsługuje takiej możliwości

function hello (who = "World")
  printf ("Hello, %s!\n", who);
endfunction

(wzięte z dokumentu )

wuschLOR
źródło
0

Lubię to robić w nieco bardziej zorientowany obiektowo sposób. Przed wywołaniem wave () zapisz niektóre argumenty w strukturze, np. jeden zwany parametrami:

parameters.flag =42;
parameters.fTrue =1;
wave(a,b,n,k,T,f,parameters);

W ramach funkcji falowej sprawdź, czy parametry struktury zawierają pole o nazwie „flaga”, a jeśli tak, to czy jego wartość nie jest pusta. Następnie przypisz mu jeszcze wartość domyślną, którą zdefiniowałeś wcześniej, lub wartość podaną jako argument w parametrach struct:

function output = wave(a,b,n,k,T,f,parameters)
  flagDefault=18;
  fTrueDefault=0;
  if (isfield(parameters,'flag') == 0 || isempty(parameters.flag)),flag=flagDefault;else flag=parameters.flag; end
  if (isfield(parameter,'fTrue') == 0 || isempty(parameters.fTrue)),fTrue=fTrueDefault;else fTrue=parameters.fTrue; end
  ...
end

Ułatwia to obsługę dużej liczby argumentów, ponieważ nie zależy to od kolejności podanych argumentów. To powiedziawszy, jest również pomocne, jeśli musisz później dodać więcej argumentów, ponieważ nie musisz zmieniać sygnatury funkcji, aby to zrobić.

Kot z Cheshire
źródło
Dlaczego nie stosować się do standardu MATLAB par nazwa-wartość? wave(a,b,'flag',42,'fTrue',1)
Cris Luengo
Jest to z pewnością również opcja, zwłaszcza gdy ma się tylko kilka parametrów. Wywołanie metody wave () z dużą liczbą par nazwa-wartość może jednak zmniejszyć czytelność kodu. Dlatego często wolę grupować pewne dane wejściowe w struktury.
CheshireCat