原网址:http://www.ilovematlab.cn/article-58-1.html?s_tid=adminrecommedation#subsec:inputparserbascis感谢作者 validateattributes的基本使用先介绍validateattributes的基本使用。假设在图像处理计算中,我们设计了一个函数叫做processImg ,用来对一张大小是500 x $500 的灰值图像进行处理,计算之前我们需要检查输入是否符合规定,这可以使用validateattributes函数来完成:
validateattributes的额外提示信息在 为什么要对函数的输入进行检查中我们提到,一个友好的API在用户输入出错时,应该提供清晰的诊断信息。以下面这个计算面积的API为例,它接受两个输入,分别是宽和高,计算就是把两者相乘返回:
validateattributes支持的检查类型和属性validateattributes可以检查的数据类型:'single','double','int8','int16','int32','int64','uint8','uint16','uint32','uint64','logical','char','struct','cell','function handle','numeric','class name'.
validateattributes支持检查的数据的大小范围属性如下:
validateattributes还支持检查的数据其它属性如下:
validatestring如果要检查的变量恰好是字符串类型,我们可以使用专门做字符串检查的validatestring函数,它接受一个字符串,然后检查该字符串的值是给定的几个可取的值之一。 比如在分析化学计算中,给浓度变量赋值时,我们除了要指定浓度的大小,还要指定单位,我们暂时用字符concentrationUnit来代表浓度(以后还会提到,利用面向对象编程,我们有其它的方式来模拟数值计算中的单位甚至量纲) ,如果我们要限制字符串变量concentrationUnit只取ppm (Parts Per Million)或者ppb (Parts Per Billion), 可以这样使用validatestring:
inputParser的基本使用前节所介绍的validateattributes和validatestring是用来验证单个参数的,当一个函数有多个参数,并且允许取默认值时,各种情况的组合就变得复杂起来了,我们可以使用inputParser类来对输入进行解析和检查。下面的几节中,我们将通过不断改进一个求面积的getArea函数,来讲解inputParser的用法。首先,该函数的基本形式是接受宽长两个参数,返回两者的乘积: <
Figure.5, inputParser类图 这节中我们介绍了addRequired成员方法,下面几节中我们将介绍另外两个成员方法addOptional和addParameter.inputParser的可选参数和默认参数值设置在上个版本的函数中,宽和长都是必要的参数,如果只输入一个值,inputParser将提示输入的数目不够
inputParser和validateattributes联合使用inputParser的主要功能是对多个输入参数的解析,其对每个参数的值的检查可以使用匿名函数, 而检查参数的值正是我们前面介绍的validateattributes和validatestring函数的强项,这节中我们把inputParser和validateattributes联合起来使用。
inputParser的参数名参数值对的设置假设我们还要再给getArea函数添加两个可缺省的参数,它们将作为结果的一部分返回
inputParser解析结构体输入最后顺便提一下,inputParser还可以对结构体的输入进行解析和检查。比如我们要给一个优化函数提供一些运行参数,这些信息可以通过一个configStruct结构体变量传给函数,该结构中包括MaxIter,Tol,StepSize。 在优化函数中,这些计算参数都有各自的默认值,但也可以通过外部指定来重置,这个函数可以这样设计:
引子:为什么需要MATLAB的单元测试系统前面几节在介绍inputParser类时,我们通过不断的改进getArea函数,使其最终变得更加的友好和完善,我们之前工作流程大致可以概括如下:Figure.6, 函数更新开发流程1 在这个工作流程中,我们除了改进算法,还在设计完成之后,在命令行中都试了几种典型的调用方式来验证新的函数。这也是实际开发中常见的流程:一边开发一边验证结果。但是随着函数支持越来越多的功能,我们在命令行不但要测试新的调用语法,(包括Positive和Negative的测试)。还要验证以前的调用仍然可以使用,保证新功能的加入没有破坏已有的功能。 这是很重要的一个过程,它保证新的函数或算法是可靠的向后兼容的。所以其实工作流程图还要修改,还要添加对新的函数进行旧的测试,所以更完善可靠的工作流程应该是下图,每次都要把已经有的测试都检验一遍Figure.7, 函数更新开发流程2 总结下来,实践中在改进函数和增加新功能的同时,我们需要在添加新的测试的同时, 不断的重复已有测试。这些测试包括正向测试(Positive Test),也包括错误测试(Negative Test)。显然在命令行中不停的重复这样的工作效率很低,那么很自然的问题就是这些测试该如何的组织。 非常直觉的方法是,我们可以把这些测试放到一个测试脚本文件中,每次给函数或者计算增加新功能的时候就运行一遍这个脚本,保证结果没有变化,如果有变化,按实际情况修改函数或者修改测试。添加新功能的时候也要往这个脚本中添加新的测试。基本的工作流程应该如下:Figure.8 函数和函数测试共生的模式 在这个测试模块中,不但要包括正向测试,还要包括错误测试情况,即要保证函数能够如预期的处理非法输入,抛出错误,一个简单的原始的方法是使用try catch。 测试模块还应该这样的功能:比如测试脚本中有10个测试点,如果第二个测试错误就退出了,那么这个脚本的运行也就结束了,直到我们解决了第二个测试点的问题,脚本才能继续向下运行,最好有这样一个功能,使得一个测试点的错误不影响其它测试点的运行,等到测试结束之后,生成一个报告告诉用户都是哪几个测试通过了,哪几个测试没有通过,这样方便用户一次性解决所有的问题。 最后这节阐释了在一个可靠的科学工程计算中为什么需要一个测试模块,并且一个测试模块该满足哪些基本的要求。其实这里讨论的功能和工作流程,正是MATLAB的单元测试所提供的解决方案。MATLAB的单元测试系统是任何一个大型的MATLAB工程项目中不可缺少的一个组成部分。我们将在后面的章节中详细介绍。 |
原网址:http://www.ilovematlab.cn/article-58-1.html?s_tid=adminrecommedation#subsec:inputparserbascis
感谢作者
validateattributes的基本使用
先介绍validateattributes的基本使用。假设在图像处理计算中,我们设计了一个函数叫做processImg
,用来对一张大小是500 x $500 的灰值图像进行处理,计算之前我们需要检查输入是否符合规定,这可以使用validateattributes函数来完成:
- % 函数一开始检查输入变量的类型和尺寸
- function processImg(img)
- ...
- validateattributes(img,{'numeric'},{'size',[500,500]});
- ... % 函数继续
- end
- validateattributes(A,classes,attributes)
- %元胞数组中可以放置多个要检查的属性
- ...
- validateattributes(img,{'numeric'},{'size',[500,500],'>=',0,'<=',255});
- ...
- % 检查数据的类型是double且单增
- ...
- validateattributes(xgrid,{'double'},{'increasing'})
- ...
- % 第三个属性参数为空
- ...
- validateattributes(iA,{'uint8'},{});
- % MyClass
- classdef MyClass
- properties
- myprop
- end
- end
- % 要求变量obj是MyClass类的对象且非空
- ...
- validateattributes(obj,{'MyClass'},{nonempty});
- ...
validateattributes的额外提示信息
在 为什么要对函数的输入进行检查中我们提到,一个友好的API在用户输入出错时,应该提供清晰的诊断信息。以下面这个计算面积的API为例,它接受两个输入,分别是宽和高,计算就是把两者相乘返回:- % 一个简化的计算面积的函数
- funciton A = getArea(width,height)
- A = width*height;
- end
- function A = getArea(width,height)
- validateattributes(width,{'numeric'},{'positive'});
- validateattributes(height,{'numeric'},{'positive'});
- A = width*height;
- end
- % 命令行测试函数功能
- >> getArea(10,22)
- ans =
- 220
- >> getArea(10,0) % 如预期捕捉到了错误
- Error using getArea (line 3)
- Expected input to be positive.
- >> getArea(0,22)
- Error using getArea (line 2) % 两个错误信息除了行号,都是一样的
- Expected input to be positive.
- % validateattributes支持额外的诊断信息
- function A = getArea(width,height)
- validateattributes(width, {'numeric'},{'positive'},'getArea','width' ,1);
- validateattributes(height,{'numeric'},{'positive'},'getArea','height',2);
- A = width*height; %参数4 参数5 参数6
- end
- >> getArea(10,0)
- Error using getArea
- Expected input number 2, height, to be positive. 清楚的说明getArea函数的
- Error in getArea (line 3) 第2个参数不符合规定
- validateattributes(height,{'numeric'},{'positive'},'getArea','height',2);
- >> getArea(0,22)
- Error using getArea
- Expected input number 1, width, to be positive.
- Error in getArea (line 2)
- validateattributes(width,{'numeric'},{'positive'},'getArea','width',1);
- % 一共5种调用方式
- validateattributes(A,classes,attributes)
- validateattributes(A,classes,attributes,argIndex)
- validateattributes(A,classes,attributes,funcName)
- validateattributes(A,classes,attributes,funcName,varName)
- validateattributes(A,classes,attributes,funcName,varName,argIndex)
validateattributes支持的检查类型和属性
validateattributes可以检查的数据类型:'single','double','int8','int16','int32','int64','uint8','uint16','uint32','uint64','logical','char','struct','cell','function handle','numeric','class name'.
validateattributes可以检查的数据维度属性如下:'2d' | 维度为2的数组,包括标量,矢量,矩阵和空矩阵 |
'3d' | 维度为3的数组 |
'column' | 列向量 即N \(\times\) 1的向量 |
'row' | 行向量,即1$×$N的向量 |
'row' | 行向量,即1$×$N的向量 |
'scalar' | 标量 |
'vector' | 行向量或者列向量 |
'size',[d1,….dN] | 维度为[d1,…dN]的数组 |
'numel',N | 数组中含有的元素个数为numel |
'ncols',N | 数组有N列 |
'nrows',N | 数组有N行 |
'ndims',N | 数组有N个维度 |
'square' | 方阵 |
'diag' | 对角矩阵 |
'nonempty' | 数组任意维度不为0 |
'nonsparse' | 非稀疏矩阵 |
validateattributes支持检查的数据的大小范围属性如下:
'>',N | 所有值大于N |
'>=',N | 所有值大于等于N |
'<',N | 所有值小于N |
'<=',N | 所有值小于等于N |
validateattributes还支持检查的数据其它属性如下:
'binary' | 数组中元素只包括0和1 |
'even' | 数组中元素都是偶数 |
'odd' | 数组中元素都是奇数 |
'integer' | 数组中元素都是整数 |
'real' | 数组中元素都是实数 |
'finite' | 数组中没有元素为Inf |
'nonnan' | 数组中没有元素为NaN |
'nonnegative' | 数组中没有元素为负 |
'nonzero' | 数组中没有元素为零 |
'positive' | 数组中每个元素都大于等于零 |
'decreasing' | 单调递减 |
'increasing' | 单调递增 |
'nondescreasing' | 非递减 |
'nonincreasing' | 非递增 |
validatestring
如果要检查的变量恰好是字符串类型,我们可以使用专门做字符串检查的validatestring函数,它接受一个字符串,然后检查该字符串的值是给定的几个可取的值之一。 比如在分析化学计算中,给浓度变量赋值时,我们除了要指定浓度的大小,还要指定单位,我们暂时用字符concentrationUnit来代表浓度(以后还会提到,利用面向对象编程,我们有其它的方式来模拟数值计算中的单位甚至量纲) ,如果我们要限制字符串变量concentrationUnit只取ppm
(Parts Per Million)或者
ppb
(Parts Per Billion), 可以这样使用validatestring:
- % validatestring基本用法
- ...
- str = validatestring(concentrationUnit,{'ppm','ppb'});
- ...
- % command line
- >> concentrationUnit= 'ppm';
- >> str = validatestring(concentrationUnit,{'ppm','ppb'});
- str =
- ppm % concentrationUnit匹配了ppm
- % command line
- >> concentrationUnit= 'pp';
- >> str = validatestring(concentrationUnit,{'ppm','ppb'});
- Error
- Expected input to match one of these strings:
- 'ppm', 'ppb'
- The input, pp, matched more than one valid string.
- % 输入是全名
- >> colorValue = 'green';
- >> str = validatestring(colorValue,{'red','green','blue','cyan','yellow','magenta'})
- str =
- green
- % 输入的名字是Inexact Name
- >> colorValue = 'G';
- >> str = validatestring(colorValue,{'red','green','blue','cyan','yellow','magenta'})
- str =
- green % G 匹配了green
- % 匹配必须是独一无二的
- >> in = 'color';
- >> str = validatestring(in,{'ColorMap','ColorSpace'})
- Expected input to match one of these strings:
- 'ColorMap', 'ColorSpace' %color两个都可以匹配
- The input, color, matched more than one valid string.
inputParser的基本使用
前节所介绍的validateattributes和validatestring是用来验证单个参数的,当一个函数有多个参数,并且允许取默认值时,各种情况的组合就变得复杂起来了,我们可以使用inputParser类来对输入进行解析和检查。下面的几节中,我们将通过不断改进一个求面积的getArea函数,来讲解inputParser的用法。首先,该函数的基本形式是接受宽长两个参数,返回两者的乘积: <- % getArea的基本形式
- function a = getArea(wd,ht)
- a = wd*ht;
- end
- function a = getArea(wd,ht)
- p = inputParser;
- p.addRequired('width', @isnumeric); % 检查输入必须是数值型的
- p.addRequired('height',@isnumeric);
- p.parse(wd,ht);
- a = p.Results.width*p.Results.height; % 从Results处取结果
- end
- % 命令行验证
- >> getArea(10,22)
- ans =
- 220
- >> getArea(10) % 如预期报错 调用少一个参数
- Error using getArea
- Not enough input arguments.
- >> getArea('10',22) % 如预期报错 参数width类型错误
- Error using getArea (line 8)
- The value of 'width' is invalid. It must satisfy the function: isnumeric.
- 首先第3行声明一个inputParser的对象,等式右边是inputParser的类名称,也是该类的构造函数。
- 第5,6行给Parser对象添加要解析的参数,其中
addRequired
是inputParser的一个成员函数。 这里我们添加了两个要解析的参数,名称分别叫做width和height。这些名称和getArea的输入的实参有顺序上的对应关系,但是名称并不一定要完全一样。 - 第8行把函数的实参wd,ht提供给inputParser对象,并且进行解析,解析的内容将存放在p.Results中。
- 第10行从p.Results中取出解析的结果,计算面积并返回。
Figure.5, inputParser类图
这节中我们介绍了addRequired成员方法,下面几节中我们将介绍另外两个成员方法addOptional和addParameter.inputParser的可选参数和默认参数值设置
在上个版本的函数中,宽和长都是必要的参数,如果只输入一个值,inputParser将提示输入的数目不够- >> getArea(10)
- Error using getArea (line 8)
- Not enough input arguments.
- function a = getArea(width,varargin)
- p = inputParser;
- p.addRequired('width',@isnumeric);
- defaultheight = width; %取默认值为输入的width
- p.addOptional('height',defaultheight,@isnumeric) %添加height作为可选参数
- p.parse(width,varargin{:});
- a = p.Results.width*p.Results.height;
- end
- 第1行中的参数被分成了两个部分,第一个输入width和其余的部分,其余部分的参数被包装在了元胞数组中,后面还会看到更多这样的例子。
- 第7行指定了可选参数的默认值。
- 第8行给inputParser添加了height作为可选参数
- % 命令行测试函数功能
- >> getArea(10) % 正确处理的了单个参数的情况
- ans =
- 100
- >> getArea(10,22) % 确保仍然可以处理两个参数的情况
- ans =
- 220
inputParser和validateattributes联合使用
inputParser的主要功能是对多个输入参数的解析,其对每个参数的值的检查可以使用匿名函数, 而检查参数的值正是我们前面介绍的validateattributes和validatestring函数的强项,这节中我们把inputParser和validateattributes联合起来使用。- % getArea版本2
- function a = getArea(width,varargin)
- p = inputParser;
- p.addRequired('width',@(x)validateattributes(x,{'numeric'},...
- {'nonzero'},'getArea','width',1));
- defaultheight = width;
- p.addOptional('height',defaultheight,@(x)validateattributes(x,{'numeric'},...
- {'nonzero'},'getArea','height',2));
- p.parse(width,varargin{:}); % 注意要把varargin元胞中的内容解开提供给parse函数
- a = p.Results.width*p.Results.height;
- end
- % 命令行测试函数功能
- >> getArea(10,0) % 如预期检查出第二个参数的错误,并给出提示
- Error using getArea (line 37)
- The value of 'height' is invalid. Expected input number 2, height, to be nonzero.
- >> getArea(0,22) % 如预期检查出第一个参数的错误,并给出提示
- Error using getArea (line 37)
- The value of 'width' is invalid. Expected input number 1, width, to be nonzero.
inputParser的参数名参数值对的设置
假设我们还要再给getArea函数添加两个可缺省的参数,它们将作为结果的一部分返回- 一个叫做shape,用来表示形状,可取的值是rectangle,square和paralelogram. 其默认值是rectangle。
- 另一个叫做unit,用来表示输入的单位,可取的值是cm,m,inches,其默认值是inches
- % getArea版本3
- function r = getArea(width,varargin)
- p = inputParser;
- p.addRequired('width',@(x)validateattributes(x,{'numeric'},...
- {'nonzero'}));
- defaultheight = width;
- p.addOptional('height',defaultheight,@(x)validateattributes(x,{'numeric'},...
- {'nonzero'}));
- defaultshape = 'rectangle';
- p.addOptional('shape',defaultshape,...
- @(x)any(validatestring(x,{'square','rectangle','paralelogram'})));
- defaultunit = 'inches';
- p.addOptional('units',defaultunit,...
- @(x)any(validatestring(x,{'inches','cm','m'})));
- p.parse(width,varargin{:});
- r.area = p.Results.width*p.Results.height;
- r.shape = p.Results.shape; %简单起见,shape和unit作为结构体的中的一部分返回
- r.units = p.Results.units;
- end
- % 命令行测试函数功能
- >> getArea(10,22,'square') % 只提供shape
- ans =
- area: 220
- units: 'inches' % units取默认值
- shape: 'square'
- >> getArea(10,22,'square','cm')
- ans =
- area: 220
- units: 'cm'
- shape: 'square'
- >> getArea(10,22,'cm','square') % 颠倒了第三和第四个参数
- Error using getArea
- The value of 'shape' is invalid. Expected input to match one of these strings:
- 'square', 'rectangle', 'paralelogram'
- The input, 'cm', did not match any of the valid strings
- >> getArea(10,22,'rectangle','inches')
- ans = %^该值等于默认值
- area: 220
- units: 'inches'
- shape: 'rectangle'
- x = 0:pi/10:pi;
- y = sin(x) ;
- plot(x,y,'color','g', 'LineWidth',2,'MarkerSize',10);
- plot(x,y,'LineWidth',2,'MarkerSize',10,'color','g');
- % getArea版本3:把之前的addOptional都换成addParameter
- function a = getArea(width,varargin)
- .....
- p.addParameter('shape',defaultshape,...
- @(x)any(validatestring(x,{'square','rectangle','paralelogram'})));
- ....
- p.addParameter('units',defaultunit,...
- @(x)any(validatestring(x,{'inches','cm','m'})));
- ....
- end
- % 命令行测试函数功能
- >> getArea(10,22,'shape','square','units','m')
- ans = %--name value --name value
- area: 220
- shape: 'square'
- units: 'm'
- >> getArea(10,22,'units','m','shape','square') % 变化了参数的位置
- ans =
- area: 220
- shape: 'square'
- units: 'm'
- >> getArea(10,22,'units','m') % 仅仅提供unit参数
- ans =
- area: 220
- shape: 'rectangle'
- units: 'm'
inputParser解析结构体输入
最后顺便提一下,inputParser还可以对结构体的输入进行解析和检查。比如我们要给一个优化函数提供一些运行参数,这些信息可以通过一个configStruct结构体变量传给函数,该结构中包括MaxIter,Tol,StepSize。 在优化函数中,这些计算参数都有各自的默认值,但也可以通过外部指定来重置,这个函数可以这样设计:- % inputParser也可以用来解析结构体
- function runProgram(configStruct)
- p = inputParser;
- DefaultMaxIter = 100 ; % 计算参数的默认值
- DefaultTol = 0.001;
- DefaultStepSize = 0.01 ;
- p.addParameter('MaxIter',DefaultMaxIter,
- @(x)validateattributes(x,{'numeric'},{'>',0,'real'}));
- %迭代次数下限
- p.addParameter('Tol',DefaultTol,
- @(x)validateattributes(x,{'numeric'},{'<=',0.01,'real'}));
- %收敛上限
- p.addParameter('StepSize',DefaultStepSize,
- @(x)validateattributes(x,{'numeric'},{'<=',0.01,'real'}));
- %步长上限
- p.parse(configStruct);
- .....
- end
- % 命令行测试函数功能
- >> configStruct.MaxIter = 10;
- >> configStruct.Tol = 0.001;
- >> configStruct.StepSize = 0.01;
- >> runProgram(configStruct);
- >> configStruct.MaxIter = 10;
- >> configStruct.Tol = 0.001;
- >>runProgram(configStruct);
引子:为什么需要MATLAB的单元测试系统
前面几节在介绍inputParser类时,我们通过不断的改进getArea函数,使其最终变得更加的友好和完善,我们之前工作流程大致可以概括如下:Figure.6, 函数更新开发流程1
在这个工作流程中,我们除了改进算法,还在设计完成之后,在命令行中都试了几种典型的调用方式来验证新的函数。这也是实际开发中常见的流程:一边开发一边验证结果。但是随着函数支持越来越多的功能,我们在命令行不但要测试新的调用语法,(包括Positive和Negative的测试)。还要验证以前的调用仍然可以使用,保证新功能的加入没有破坏已有的功能。 这是很重要的一个过程,它保证新的函数或算法是可靠的向后兼容的。所以其实工作流程图还要修改,还要添加对新的函数进行旧的测试,所以更完善可靠的工作流程应该是下图,每次都要把已经有的测试都检验一遍Figure.7, 函数更新开发流程2
总结下来,实践中在改进函数和增加新功能的同时,我们需要在添加新的测试的同时, 不断的重复已有测试。这些测试包括正向测试(Positive Test),也包括错误测试(Negative Test)。显然在命令行中不停的重复这样的工作效率很低,那么很自然的问题就是这些测试该如何的组织。 非常直觉的方法是,我们可以把这些测试放到一个测试脚本文件中,每次给函数或者计算增加新功能的时候就运行一遍这个脚本,保证结果没有变化,如果有变化,按实际情况修改函数或者修改测试。添加新功能的时候也要往这个脚本中添加新的测试。基本的工作流程应该如下:Figure.8 函数和函数测试共生的模式
在这个测试模块中,不但要包括正向测试,还要包括错误测试情况,即要保证函数能够如预期的处理非法输入,抛出错误,一个简单的原始的方法是使用try catch。 测试模块还应该这样的功能:比如测试脚本中有10个测试点,如果第二个测试错误就退出了,那么这个脚本的运行也就结束了,直到我们解决了第二个测试点的问题,脚本才能继续向下运行,最好有这样一个功能,使得一个测试点的错误不影响其它测试点的运行,等到测试结束之后,生成一个报告告诉用户都是哪几个测试通过了,哪几个测试没有通过,这样方便用户一次性解决所有的问题。 最后这节阐释了在一个可靠的科学工程计算中为什么需要一个测试模块,并且一个测试模块该满足哪些基本的要求。其实这里讨论的功能和工作流程,正是MATLAB的单元测试所提供的解决方案。MATLAB的单元测试系统是任何一个大型的MATLAB工程项目中不可缺少的一个组成部分。我们将在后面的章节中详细介绍。