旧的习惯很难,我意识到我一直在我正在开发的非常大的包中使用opts___Rule
模式匹配和类似thisoption /. {opts} /. Options[myfunction]
的结构。 Sal Manango的“Mathematica Cookbook”提醒我,版本6后的方式是opts:OptionsPattern[]
和OptionValue[thisoption]
。无论如何,该软件包需要版本8,但我多年来从未改变过编写此类代码的方式。
从我的pre-version-6做事方式重构所有这些是否值得?是否有表现或其他好处?
此致
维尔比亚
编辑:摘要
在回答这个问题时提出了很多好处,所以谢谢(当然还有一个)。总而言之,是的,我应该重构使用OptionsPattern
和OptionValue
。 (注意:OptionsPattern
不是OptionPattern
,因为我之前有过这样的原因!)原因有很多:
HoldForm
(@ Leonid)OptionsPattern
会自动检查您是否将有效选项传递给该函数(如果要传递给其他函数,则仍然需要FilterRules
RuleDelayed
(:>
)(@rcollyer)Flatten
(@Andrew)OptionValue /@ list
分配多个局部变量更容易,而不是多次调用someoptions /. {opts} /. Options[thisfunction]
(在@rcollyer和我之间的评论中出现) 编辑:7月25日我最初认为使用/.
语法的一次可能仍然有意义,如果你故意从另一个函数中提取默认选项,而不是实际的那个被召唤。事实证明,这是通过使用OptionsPattern[]
的形式来处理的,其中包含头部列表,例如:OptionsPattern[{myLineGraph, DateListPlot, myDateTicks, GraphNotesGrid}]
(请参阅documentation中的“更多信息”部分)。我最近才解决这个问题。
答案 0 :(得分:13)
似乎依赖模式匹配器比使用PatternTest
产生更快的执行速度,因为后者需要调用赋值器。无论如何,我的时间表明可以实现一些加速,但我不认为它们对于促使重新分解是如此重要。
In[7]:= f[x__, opts : OptionsPattern[NIntegrate]] := {x,
OptionValue[WorkingPrecision]}
In[8]:= f2[x__, opts___?OptionQ] := {x,
WorkingPrecision /. {opts} /. Options[NIntegrate]}
In[9]:= AbsoluteTiming[Do[f[1, 2, PrecisionGoal -> 17], {10^6}];]
Out[9]= {5.0885088, Null}
In[10]:= AbsoluteTiming[Do[f2[1, 2, PrecisionGoal -> 17], {10^6}];]
Out[10]= {8.0908090, Null}
In[11]:= f[1, 2, PrecisionGoal -> 17]
Out[11]= {1, 2, MachinePrecision}
In[12]:= f2[1, 2, PrecisionGoal -> 17]
Out[12]= {1, 2, MachinePrecision}
答案 1 :(得分:11)
一个鲜为人知(但经常有用)的事实是允许选项出现在嵌套列表中:
In[1]:= MatchQ[{{a -> b}, c -> d}, OptionsPattern[]]
Out[1]= True
FilterRules等选项处理函数知道这一点:
In[2]:= FilterRules[{{PlotRange -> 3}, PlotStyle -> Blue,
MaxIterations -> 5}, Options[Plot]]
Out[2]= {PlotRange -> 3, PlotStyle -> RGBColor[0, 0, 1]}
OptionValue将其考虑在内:
In[3]:= OptionValue[{{a -> b}, c -> d}, a]
Out[3]= b
但ReplaceAll (/.)当然没有考虑到这一点:
In[4]:= a /. {{a -> b}, c -> d}
During evaluation of In[4]:= ReplaceAll::rmix: Elements of {{a->b},c->d} are a mixture of lists and nonlists. >>
Out[4]= a /. {{a -> b}, c -> d}
因此,如果您使用OptionsPattern,您可能还应该使用OptionValue来确保您可以使用用户传递的选项集。
另一方面,如果你使用ReplaceAll(/。),你应该坚持opts___Rule
出于同样的原因。
请注意opts___Rule
在某些(公认的模糊)案件中也有点过于宽容:
不是有效选项:
In[5]:= MatchQ[Unevaluated[Rule[a]], OptionsPattern[]]
Out[5]= False
但是___Rule
允许它通过:
In[6]:= MatchQ[Unevaluated[Rule[a]], ___Rule]
Out[6]= True
更新:正如 rcollyer 指出的那样,___Rule
的另一个更严重的问题是它错过了使用RuleDelayed (:>)指定的选项。你可以解决它(参见rcollyer的回答),但这是使用OptionValue的另一个好理由。
答案 2 :(得分:11)
虽然有几个答案强调了旧选项和新选项使用方式的不同方面,但我还是想做一些额外的观察。较新的构造OptionValue
- OptionsPattern
提供比OptionQ
更高的安全性,因为OptionValue
检查全局选项列表以确保函数已知传递的选项。较旧的OptionQ
似乎更容易理解,因为它仅基于标准模式匹配,并且不与任何全局属性直接相关。您是否希望这些构造提供的额外安全性取决于您,但我的猜测是大多数人认为它很有用,特别是对于大型项目。
这些类型检查真正有用的一个原因是,通常选项通过函数以类似链的方式作为参数传递,过滤等等,因此如果没有这样的检查,一些模式匹配错误将很难因为它们会在远离原产地的地方造成伤害。
就核心语言而言,OptionValue
- OptionsPattern
构造是模式匹配器的补充,也许是其所有特征中最“神奇”的。只要人们愿意将选项视为规则的特例,就没有必要在语义上。此外,OptionValue
将模式匹配连接到Options[symbol]
- 一个全局属性。因此,如果一个人坚持语言纯度,那么opts___?OptionQ
中的规则似乎更容易理解 - 除了标准规则替换语义之外,除了标准规则替换语义之外,我们不需要理解这一点:
f[a_, b_, opts___?OptionQ] := Print[someOption/.Flatten[{opts}]/.Options[f]]
(我提醒OptionQ
谓词专门用于识别旧版Mathematica中的选项),同时:
f[a_, b_, opts:OptionsPattern[]] := Print[OptionValue[someOption]]
看起来很神奇。当你使用Trace
并且看到OptionValue
的缩写形式评估为更长的形式时,它会变得更清晰一点,但它自动确定封闭函数名称的事实仍然很显着。
OptionsPattern
作为模式语言的一部分还有一些后果。一个是@Sasha讨论的速度提升。然而,速度问题往往被过分强调(这不会减损他的观察结果),我认为对于有选项的函数尤其如此,因为这些函数往往是更高级别的函数,可能会有非函数琐碎的身体,大部分的计算时间将用在那里。
另一个相当有趣的区别是当需要将选项传递给保存其参数的函数时。考虑以下示例:
ClearAll[f, ff, fff, a, b, c, d];
Options[f] = Options[ff] = {a -> 0, c -> 0};
SetAttributes[{f, ff}, HoldAll];
f[x_, y_, opts___?OptionQ] :=
{{"Parameters:", {HoldForm[x], HoldForm[y]}}, {" options: ", {opts}}};
ff[x_, y_, opts : OptionsPattern[]] :=
{{"Parameters:", {HoldForm[x], HoldForm[y]}}, {" options: ", {opts}}};
没关系:
In[199]:= f[Print["*"],Print["**"],a->b,c->d]
Out[199]= {{Parameters:,{Print[*],Print[**]}},{ options: ,{a->b,c->d}}}
但是这里我们基于OptionQ
的函数泄漏评估作为模式匹配过程的一部分:
In[200]:= f[Print["*"],Print["**"],Print["***"],a->b,c->d]
During evaluation of In[200]:= ***
Out[200]= f[Print[*],Print[**],Print[***],a->b,c->d]
这并非完全无足轻重。为了确定匹配或不匹配的事实,模式匹配器必须评估第三个Print
,作为OptionQ
的评估的一部分,因为OptionQ
没有持有论点。为避免评估泄漏,需要使用Function[opt,OptionQ[Unevaluated[opt]],HoldAll]
代替OptionQ
。使用OptionsPattern
我们没有这个问题,因为匹配的事实可以纯粹在语法上建立:
In[201]:= ff[Print["*"],Print["**"],a->b,c->d]
Out[201]= {{Parameters:,{Print[*],Print[**]}},{ options: ,{a->b,c->d}}}
In[202]:= ff[Print["*"],Print["**"],Print["***"],a->b,c->d]
Out[202]= ff[Print[*],Print[**],Print[***],a->b,c->d]
因此,总结一下:我认为选择一种方法而不是另一种方法主要是品味问题 - 每种方法都可以有效地使用,而且每种方法都可以被滥用。我更倾向于使用更新的方式,因为它提供了更多的安全性,但我不排除存在一些令人惊讶的角落情况 - 而旧的方法在语义上更容易理解。这类似于C-C ++比较(如果这是合适的):自动化和(可能)安全性与简单性和纯度。我的两分钱。
答案 3 :(得分:9)
您的代码本身有一个微妙但可修复的缺陷。模式opts___Rule
与a :> b
形式的选项不匹配,因此如果您需要使用它,则必须更新代码。立即解决方法是将opts___Rule
替换为opts:(___Rule | ___RuleDelayed)
,这需要比OptionsPattern[]
更多的输入。但是,对于我们之间的懒惰,OptionValue[...]
需要比ReplaceAll
的简短形式更多的输入。但是,我认为它使得阅读代码更加清晰。
我发现使用OptionsPattern[]
和OptionValue
更容易阅读,并立即理解正在做的事情。在第一次阅读时,较早形式的opts___ ...
和ReplaceAll
更难以理解。除此之外,还有明确的timing advantages,我会更新您的代码。