如何明确说明选项的有效设置?以这个例子为例
Options[myFunc] = {opt1 -> "SomeString"};
myFunc[OptionsPattern[]] := Print[OptionValue[opt1]];
myFunc
打印选项的值。如果我们评估myFunc[opt1 -> {1, 2}]
,则会打印{1, 2}
。此功能基本上会打印您设置为opt1
的任何内容。我的问题是,如何确保我的函数只接受opt1
的给定数量的值。我们可以从简单的事情开始,例如String
和Integer
。
为了更好地了解在给出opt1
的错误值时我们期望的行为,我们可以看看当我们在函数PlotRange
中给出错误的值时会发生什么{1}}。
在图片中给出的示例中,我故意为Plot
选项提供了错误的值,并给了我一条消息,指明了特定选项的正确值类型。它似乎PlotRange
最终获取其默认值,因此它返回PlotRange
对象。
在简单的例子中,我们想要获得的是:
Graphics
如何实现这一目标?
答案 0 :(得分:7)
这是一个简单的方法:
In[304]:= ClearAll[myFunc];
Options[myFunc] = {opt1 -> "SomeString"};
myFunc::badopt = "Value of option opt1 -> `1` is not a string or integer.";
myFunc[OptionsPattern[]] :=
With[{val = OptionValue[opt1]},
With[{error = ! MatchQ[val, _String | _Integer]},
If[error, Message[myFunc::badopt , val]];
(Print[val] /; ! error)]];
例如:
In[308]:= myFunc[opt1 -> 1]
During evaluation of In[308]:= 1
In[309]:= myFunc[opt1 -> {1, 2}]
During evaluation of In[309]:= myFunc::badopt:
Value of option opt1 -> {1,2} is not a string or integer.
Out[309]= myFunc[opt1 -> {1, 2}]
我们可以使用函数内的OptionValue
使用单个参数作为选项名称的事实来分解错误检查乏味。这可以通过使用mma元编程工具实现。以下是自定义赋值运算符的代码:
ClearAll[def, OptionSpecs];
SetAttributes[def, HoldAll];
def[f_[args___] :> body_,OptionSpecs[optionSpecs : {(_ -> {_, Fail :> _}) ..}]] :=
f[args] :=
Module[{error = False},
Scan[
With[{optptrn = First[# /. optionSpecs], optval = OptionValue[#]},
If[! MatchQ[optval, optptrn ],
error = True;
Return[(Fail /. Last[# /. optionSpecs])[optval]]]] &,
optionSpecs[[All, 1]]
];
body /; ! error];
它的作用是将函数定义作为规则f_[args___]:>body_
,以及
可接受的选项设置的规范以及在检测到其中一个传递选项中的错误时要执行的操作。然后我们在执行主体之前注入错误测试代码(Scan
)。一旦找到具有不适当设置的第一个选项,错误标志就会设置为True
,并且在该选项的Fail:>code_
部分规范中指定了任何代码。选项规范模式(_ -> {_, Fail :> _})
应为(optname_ -> {optpattern_, Fail :> onerror_})
,其中optname
是选项名称,optpattern
是选项值必须匹配的模式,onerror
是如果检测到错误则执行任意代码。请注意,我们在RuleDelayed
中使用Fail:>onerror_
,以防止过早评估该代码。注意b.t.w.添加OptionSpecs
包装器仅仅是为了便于阅读 - 它是一个完全空闲的符号,没有附加任何规则。
以下是使用此自定义赋值运算符定义的函数示例:
ClearAll[myFunc1];
Options[myFunc1] = {opt1 -> "SomeString", opt2 -> 0};
myFunc1::badopt1 = "Value of option opt1 -> `1` is not a string or integer.";
myFunc1::badopt2 = "Value of option opt2 -> `1` is not an integer.";
def[myFunc1[OptionsPattern[]] :>
Print[{OptionValue[opt1], OptionValue[opt2]}],
OptionSpecs[{
opt1 -> {_Integer | _String,
Fail :> ((Message[myFunc1::badopt1, #]; Return[$Failed]) &)},
opt2 -> {_Integer,
Fail :> ((Message[myFunc1::badopt2, #]; Return[$Failed]) &)}}
]];
以下是使用示例:
In[473]:= myFunc1[]
During evaluation of In[473]:= {SomeString,0}
In[474]:= myFunc1[opt2-> 10]
During evaluation of In[474]:= {SomeString,10}
In[475]:= myFunc1[opt2-> 10,opt1-> "other"]
During evaluation of In[475]:= {other,10}
In[476]:= myFunc1[opt2-> 1/2]
During evaluation of In[476]:= myFunc1::badopt2:
Value of option opt2 -> 1/2 is not an integer.
Out[476]= $Failed
In[477]:= myFunc1[opt2-> 15,opt1->1/2]
During evaluation of In[477]:= myFunc1::badopt1:
Value of option opt1 -> 1/2 is not a string or integer.
Out[477]= $Failed
您可能也对我为测试传递的选项而编写的包感兴趣:CheckOptions
,可用here。该软件包附带一个说明其用途的笔记本。它解析函数的定义并创建其他定义以检查选项。目前的缺点(除了可能并不总是适当的新定义的产生)是它只涵盖了通过OptionQ
谓词定义选项的旧方法(我还没有更新它以涵盖OptionValue - OptionsPattern
。我将在此处复制随附笔记本的一部分,以说明其工作原理:
考虑模型函数:
In[276]:= ClearAll[f];
f[x_, opts___?OptionQ]:= x^2;
f[x_, y_, opts___?OptionQ] := x + y;
f[x_, y_, z_] := x*y*z;
假设我们想在选项FontSize
传递给我们的函数时返回错误消息:
In[280]:=
f::badopt="Inappropriate option";
test[f,heldopts_Hold,heldArgs_Hold]:=(FontSize/.Flatten[List@@heldopts])=!=FontSize;
rhsF[f,__]:=(Message[f::badopt];$Failed);
我们添加选项 - 检查定义:
In[283]:= AddOptionsCheck[f,test,rhsF]
Out[283]= {HoldPattern[f[x_,opts___?OptionQ]/;test[f,Hold[opts],Hold[x,opts]]]:>
rhsF[f,Hold[opts],Hold[x,opts]],
HoldPattern[f[x_,y_,opts___?OptionQ]/;test[f,Hold[opts],Hold[x,y,opts]]]:>
rhsF[f,Hold[opts],Hold[x,y,opts]],
HoldPattern[f[x_,opts___?OptionQ]]:>x^2,
HoldPattern[f[x_,y_,opts___?OptionQ]]:>x+y,
HoldPattern[f[x_,y_,z_]]:>x y z}
如您所见,一旦我们调用AddOptionsCheck
,它就会生成新的定义。它需要函数名称,测试函数和失败时执行的函数。测试函数接受主函数名称,传递给它的选项(包含在Hold
中),以及传递给它的非选项参数(也包含在Hold
中)。从生成的定义中,您可以看到它的作用。
我们现在检查各种输入:
In[284]:= f[3]
Out[284]= 9
In[285]:= f[3,FontWeight->Bold]
Out[285]= 9
In[286]:= f[3,FontWeight->Bold,FontSize->5]
During evaluation of In[286]:= f::badopt: Inappropriate option
Out[286]= $Failed
In[289]:= f[a,b]
Out[289]= a+b
In[290]:= f[a,b,FontWeight->Bold]
Out[290]= a+b
In[291]:= f[a,b,FontWeight->Bold,FontSize->5]
During evaluation of In[291]:= f::badopt: Inappropriate option
Out[291]= $Failed
In[292]:= OptionIsChecked[f,test]
Out[292]= True
请注意,测试函数可以测试涉及函数名,传递参数和传递选项的任意条件。我的另一个包PackageOptionChecks
,在同一页面上可用,它有一个更简单的语法来专门测试r.h.s.选项,也可以应用于整个包。其使用的一个实际示例是另一个包PackageSymbolsDependencies
,其功能选项由PackageOptionChecks
“保护”。另外,PackageOptionChecks
也可以应用于Global'
上下文中的函数,不需要包。
当前实现的另一个限制是我们无法返回未评估的函数。请参阅随附的笔记本中的更详细的讨论。如果对此有足够的兴趣,我会考虑更新软件包以消除我提到的一些限制。