MATLAB 函数参数检查
为了得到健壮的代码,我们非常有必要对函数的参数进行检查,除了最基础的参数个数,还需要关注参数的类型和数据范围等,下面介绍几个MATLAB提供的用于参数检查的内置函数。
assert + isXXX
最简单的做法就是基于 assert
和 isXXX
检查函数的形参是否满足要求,例如
1 | function func(u, t, f, n, b, flag, params) |
注:这里对结构体 params
的检查是要求至少含有指定字段
a
, b
, c
,但是也允许含有更多字段,
如果需要严格检查,则必须通过 fieldnames
函数动态获取
params
的所有字段进行比较,无法在一个 assert
语句中完成。
下面是一些常用的 isXXX
函数。
关于维度和形状
函数 | 说明 | 正面示例 | 反面示例 |
---|---|---|---|
isscalar(A) |
是否为标量 (1×1 ) |
isscalar(5) |
isscalar([1 2]) |
isvector(A) |
是否为(行或列)向量 | isvector([1 2 3]) |
isvector([1 2; 3 4]) |
isrow(A) |
是否为行向量 | isrow([1 2 3]) |
isrow([1; 2; 3]) |
iscolumn(A) |
是否为列向量 | iscolumn([1; 2; 3]) |
iscolumn([1 2 3]) |
ismatrix(A) |
是否为矩阵 | ismatrix(rand(3,4)) |
ismatrix(rand(3,4,2)) |
issparse(A) |
是否为稀疏矩阵 | issparse(speye(3)) |
issparse(eye(3)) |
isempty(A) |
是否为空 | isempty([]) |
isempty([0]) |
注:在 MATLAB 中,空数组是指至少一个维度长度等于零的数组。
关于类型
函数 | 说明 | 正面示例 (true ) |
反面示例 (false ) |
---|---|---|---|
isnumeric(A) |
是否为数值类型 | isnumeric(3.14) |
isnumeric('3.14') |
islogical(A) |
是否为逻辑类型 | islogical(true) |
islogical(0) |
ischar(A) |
是否为字符数组 | ischar('abc') |
ischar(["abc"]) |
isstring(A) |
是否为字符串类型 | isstring("abc") |
isstring('abc') |
iscell(A) |
是否为元胞数组 | iscell({1,2}) |
iscell([1,2]) |
isstruct(A) |
是否为结构体 | isstruct(struct('a',1)) |
isstruct([1 2]) |
isobject(A) |
是否为对象 | isobject(MException) |
isobject(struct()) |
isa(A, 'class') |
是否为指定类(或者派生类)对象 | isa(3, 'double') |
isa('abc', 'double') |
注:在MATLAB中的字符数组和字符串是不一样的,很多情况下的使用都存在细微区别。
关于数值属性
函数 | 说明 | 正面示例 (true ) |
反面示例 (false ) |
---|---|---|---|
isfinite(A) |
是否为有限数 | isfinite(3.14) |
isfinite(Inf) |
isinf(A) |
是否为无穷大 | isinf(Inf) |
isinf(3.14) |
isnan(A) |
是否为 NaN | isnan(NaN) |
isnan(3.14) |
isinteger(A) |
是否为整数类 (int32 等) | isinteger(int8(3)) |
isinteger(3) |
isfloat(A) |
是否为浮点数 | isfloat(3.14) |
isfloat(int8(3)) |
isreal(A) |
是否为实数 | isreal(3) |
isreal(1+2i) |
其它
函数 | 说明 | 正面示例 (true ) |
反面示例 (false ) |
---|---|---|---|
isfile(name) |
是否为文件 | isfile('myfile.txt') |
isfile('myfolder') |
isfolder(name) |
是否为文件夹 | isfolder('myfolder') |
isfolder('myfile.txt') |
isfield(S, 'name') |
结构体是否包含字段 | isfield(struct('a',1), 'a') |
isfield(struct('a',1), 'b') |
iscellstr(C) |
是否为字符数组的元胞数组 | iscellstr({'a','b'}) |
iscellstr({'a', 1}) |
isStringScalar(A) |
是否为包含一个元素的字符串数组 | isStringScalar("A") |
isStringScalar(["A","B"]) |
官方文档提供了一份完整的
isXXX
函数参考表:Use is* Functions to Detect State
千万不要误用isinteger
函数,因为默认的绝大多数数据都是浮点数,只有专门使用int32
、int64
等整数类数据类型创建的数据,才会返回true
。
1
2
3
4
5
6
7
8>> isinteger(1)
ans =
logical
0
>> isinteger(int32(1))
ans =
logical
1
validateattributes
validateattributes
函数可以为我们检查某个具体参数的类型以及是否满足某些要求,不满足要求会抛出错误。
假设我们有一个函数func1
,它接受两个输入参数a
和b
,我们要求:a
是一个非空的标量,b
是一个非空的向量。
1
2
3
4
5
6
7function result = func1(a, b)
validateattributes(a, {'numeric'}, {'nonempty', 'scalar'});
validateattributes(b, {'numeric'}, {'nonempty', 'vector'});
result = a + sum(b);
disp(['Result: ', num2str(result)]);
end
validateattributes
函数可以确保a
是一个非空的标量,b
是一个非空的向量。如果输入参数不符合这些要求,MATLAB
会抛出错误。
validateattributes
函数的基本格式为 1
validateattributes(a, classes, attributes);
其中:
- 第一个参数是要检查的函数参数
- 第二个参数是要求的类型,通常包括:
double
,logical
,char
,struct
- 第三个参数是要求参数满足的条件,可以是多个条件,例如
{'nonempty', 'vector'}
,也可以留空{}
关于参数的类型,MATLAB提供了class
函数来获取,常见的类型包括:
double
浮点数numeric
数值类型(包括各种浮点数和各种整数)char
字符logical
布尔值struct
结构体数组cell
元胞数组function_handle
函数句柄class_name
自定义类型名称
关于参数满足的条件,常见的条件包括
- 维度检查
2d
二维数组;3d
三维数组row
行向量;column
列向量;vector
行向量或列向量scalar
标量'size',[d1,...dN]
指定维数信息的数组'numel',N
指定元素总个数为N
的数组'nrows',N
指定行数为N
的数组;'ncols',N
指定列数为N
的数组square
方阵(每一个维度都相等)nonempty
要求数组的每一个维度都不为0nonsparse
要求数组非稀疏
- 大小范围检查
'>',N
所有值大于N
'>=',N
所有值大于等于N
- ...
- 其它检查
finite
数组中的元素不含有Inf
nonnan
数组中的元素不含有Nan
nonnegative
数组中的元素全部非负nonzero
数组中的元素全部非零decreasing
单调减increasing
单调增
validatestring
对于字符串参数,MATLAB专门提供了检查工具validatestring
,它可以限制字符串参数的所有合法取值,例如
1
2
3
4
5
6
7
8
9
10
11
12function result = selectOption(option)
option = validatestring(option, {'Option1', 'Option2', 'Option3'});
switch option
case 'Option1'
result = 'You selected Option 1';
case 'Option2'
result = 'You selected Option 2';
case 'Option3'
result = 'You selected Option 3';
end
end
但是这种限制不是严格的,正如很多MATLAB内置函数一样,可以忽略大小写进行模糊匹配,也可以只匹配到合法选项的开头部分。 如果这个参数只能匹配到唯一的合法选项,就不会报错,反之则会报错。
成功的模糊匹配例如 1
2
3
4
5
6>> validatestring('G',{'green','red'})
ans =
'green'
>> validatestring('bla',{'black','blue'})
ans =
'black'
下面的匹配则会报错 1
2
3
4
5
6
7
8>> validatestring('u',{'black','blue'})
Expected input to match one of these values:
'black', 'blue'
The input, 'u', did not match any of the valid values.
>> validatestring('ue',{'black','blue'})
Expected input to match one of these values:
'black', 'blue'
The input, 'ue', did not match any of the valid values.
inputParser
inputParser
是输入参数解析器类型,和C++中常见的命令行参数解析器非常类似,只不过在这里是对函数参数进行检查。
假设我们有一个函数func2
,它接受两个必需的输入参数a
和b
,一个可选的输入参数c
,以及一个键值对参数Verbose
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27function result = func2(a, b, varargin)
% 创建一个 inputParser 对象
p = inputParser;
% 配置必需参数
p.addRequired('a', @isnumeric);
p.addRequired('b', @isnumeric);
% 配置可选参数(默认值1)
p.addOptional('c', 1, @isnumeric);
% 配置键值对参数(键值对的值默认取false)
p.addParameter('Verbose', false, @islogical);
% 解析输入参数
p.parse(a, b, varargin{:});
% 重新获取解析后的参数
a = p.Results.a;
b = p.Results.b;
c = p.Results.c;
verbose = p.Results.Verbose;
result = a + sum(b) + c;
if verbose
disp(['a: ', num2str(a)]);
disp(['b: ', num2str(b)]);
disp(['c: ', num2str(c)]);
disp(['Result: ', num2str(result)]);
end
end
在这个示例中,我们首先创建了一个inputParser
对象
p,然后用它对参数进行检查:
- 使用
addRequired
方法添加了必需参数a
和b
,并指定它们必须是数值类型。(必需参数对顺序是敏感的,在调用时必须按照声明的顺序提供。 - 使用
addOptional
方法添加了可选参数c
,并指定默认值为 1。 - 使用
addParameter
方法添加了键值对参数Verbose
,指定键的名称为Verbose
,值必须是布尔类型,默认为false
。 - 使用
parse
方法解析输入参数。 - 从
p.Results
重新获取解析后的参数。
使用例如 1
2
3
4
5
6
7
8
9
10
11% 调用 func2 并传递必需参数 a 和 b
func2(1, [2, 3]);
% 调用 func2 并传递必需参数 a 和 b 以及可选参数 c
func2(1, [2, 3], 4);
% 调用 func2 并传递必需参数 a 和 b,以及名称-值对参数 'Verbose'
func2(1, [2, 3], 'Verbose', true);
% 调用 func2 并传递必需参数 a 和 b, 可选参数 c 和名称-值对参数 'Verbose'
func2(1, [2, 3], 4, 'Verbose', true);
在较新的版本中,还支持对可选参数以键值对的形式传递,例如下面两个语句是等价的
1
2func2(1, [2, 3], 4, Verbose=true);
func2(1, [2, 3], 4, 'Verbose', true);
我们可以使用匿名函数的方式,将validateattributes
函数也组合到inputParser
中使用,用于判断参数的有效性,例如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29function result = myFunction(a, b, varargin)
% 创建一个 inputParser 对象
p = inputParser;
% 设置输入参数保持顺序
p.KeepUnmatched = true;
% 必需参数
addRequired(p, 'a', @(x) validateattributes(x, {'numeric'}, {'scalar', 'nonnegative'}));
addRequired(p, 'b', @(x) validateattributes(x, {'numeric'}, {'vector'}));
% 可选参数
addOptional(p, 'c', 1, @(x) validateattributes(x, {'numeric'}, {'scalar', 'nonnegative'}));
% 名称-值对参数
addParameter(p, 'Verbose', false, @(x) validateattributes(x, {'logical'}, {'scalar'}));
% 解析输入参数
parse(p, a, b, varargin{:});
% 重新获取解析后的参数
a = p.Results.a;
b = p.Results.b;
c = p.Results.c;
verbose = p.Results.Verbose;
result = a + sum(b) + c;
if verbose
disp(['a: ', num2str(a)]);
disp(['b: ', num2str(b)]);
disp(['c: ', num2str(c)]);
disp(['Result: ', num2str(result)]);
end
end
对于 inputParser
对象,我们还可以设置它的一些属性来修改解析规则,例如:
FunctionName
:默认为空。通常设置为 inputParser 所在函数的名字,这样可以在出错是给出是在哪个函数发生的;CaseSensitive
:默认为 false,即对键的大小写不敏感,这样在输入键值对参数是,键的大小写不会影响解析;PartialMatching
:默认为 true,即允许对键的部分匹配。例如如果定义了window
参数,实际使用时只输入w
,依然能够正确匹配;(如果存在歧义则会报错)
1 | p = inputParser; |
arguments
在R2019b推出的arguments块可能是MATLAB目前最推荐的参数检查语法,参考官方文档。(但是我并不觉得它最好,而且对版本要求有点高)
arguments块是一段单独的语法块,通常写在函数的开头部分,例如
1 | function myFunction(a, b, c, option) |
调用例如
1 | myFunction(5, "hello"); |
补充
对于参数检查有几个简单的原则:
- 函数文件只有主函数对外提供接口,剩下的子函数仅在文件内部可以调用,因此只有主函数需要考虑参数检查;(对于类文件来说,同理我们只需要考虑公开方法的参数检查)
- 对于某些对性能非常敏感的函数,可以仅在注释中说明,而不进行实际的参数检查。
除了参数检查,我们还可以为自定义函数提供自定义代码建议和自动填充功能,需要提供对应的
functionSignatures.json
文件,具体细节参考官方文档。