使用 ROUTINE_INFO 实现动态关键字验证
17025 给这篇文章评分:
暂无评分
使用 ROUTINE_INFO 实现动态关键字验证
匿名作者 2014年3月27日,星期四
在 IDL 中,存在三种 _EXTRA 关键字的变体:_EXTRA、_REF_EXTRA 和 _STRICT_EXTRA。_EXTRA 和 _REF_EXTRA 用于定义例程的签名,而在调用任何例程时,可以使用 _EXTRA 或 _STRICT_EXTRA。正如我的同事 Jim Pendleton 在几年前所写的那样,_REF_EXTRA 关键字相较于普通的 _EXTRA 是一个潜在的改进。这是因为它使用了引用传递语义,而不是 _EXTRA 所使用的值传递语义,因此您不会在每次函数调用时复制变量。其缺点是,通过 _REF_EXTRA 传递的值是可变的,所以需要特别注意,但它可以节省大量分配这些副本所需的内存和时间。
_EXTRA 和 _REF_EXTRA 的缺点是,它们都是通用的“收纳袋”,允许调用者使用任何他们想要的、多余的参数调用您的函数,并且无法知道哪些关键字实际上被使用了,哪些是多余的。_STRICT_EXTRA 关键字应运而生,其相关内容在帮助文档“关键字继承”中有所讨论。通常,当您调用一个例程时,您使用 _EXTRA 关键字作为将关键字集合传递给例程的方式。_EXTRA 包中属于例程签名的任何关键字都会被正确映射,其余的关键字如果存在 _EXTRA/_REF_EXTRA 则会被捕获,否则将被丢弃。如果您使用 _STRICT_EXTRA 而不是 _EXTRA 调用同一个例程,那么如果有任何关键字无法映射到例程签名,它将抛出错误。我们可以通过下面这个精心设计的例子来观察其运行情况,该例子使用最简化的过程来演示验证逻辑:
pro callByValue, _EXTRA=extra
help, extra
end
pro callByReference, _REF_EXTRA=refExtra
help, refExtra
end
pro callWithNoExtra, FOO=foo
help, foo
end
pro callWrapper, _REF_EXTRA=extra
callByValue, _EXTRA=extra
callByReference, _EXTRA=extra
callWithNoExtra, _EXTRA=extra
end
pro callStrictWrapper, _REF_EXTRA=extra
callByValue, _STRICT_EXTRA=extra
callByReference, _STRICT_EXTRA=extra
callWithNoExtra, _STRICT_EXTRA=extra
end
pro extra_tests, _REF_EXTRA=extra
callWrapper, PI=!pi
callStrictWrapper, PI=!pi
end
运行 extra_tests 的输出是:
** Structure <1307e5a0>, 1 tags, length=4, data length=4, refs=1:
PI FLOAT 3.14159
REFEXTRA STRING = Array[1]
FOO UNDEFINED = <Undefined>
** Structure <1307e650>, 1 tags, length=4, data length=4, refs=1:
PI FLOAT 3.14159
REFEXTRA STRING = Array[1]
% Keyword PI not allowed in call to: CALLWITHNOEXTRA
% Execution halted at: CALLSTRICTWRAPPER 32 C:\Users\brian\IDLWorkspace83\Default\extra_tests.pro
% EXTRA_TESTS 43 C:\Users\brian\IDLWorkspace83\Default\extra_tests.pro
% $MAIN$
_EXTRA 和 _REF_EXTRA 关键字能够毫无意外地接受 PI 关键字。当 callWrapper 调用 callWithNoExtra 时,PI 关键字被丢弃,而 FOO 保持未定义状态。但是当 callStrictWrapper 调用 callWithNoExtra 时,PI 关键字与例程签名不匹配,从而抛出错误。需要注意的是,callByValue 和 callByReference 与 _STRICT_EXTRA 配合使用完全正常,因为它们的 _EXTRA 和 _REF_EXTRA 关键字(分别)会毫无问题地“吸收”掉 PI 关键字。
因此 _STRICT_EXTRA 有其局限性,特别是在您想要调用多个不包含某种形式 _EXTRA 的例程时。为了实现这一点,我们需要查询 IDL 运行时环境,使用强大的 ROUTINE_INFO 函数来获取有关例程签名的信息。当您将此函数与它的 /PARAMETERS 关键字一起使用时,它将返回一个结构,使您可以获取给定例程签名中的所有关键字列表。让我们看一个简单的例子,了解 ROUTINE_INFO 返回的内容:
pro myPro, PARAM1=p1, PARAM2=p2, PARAM3=p3
print, 'in myPro'
help, p1, p2, p3
end
function myFunc, PARAM2=p2, PARAM4=p4
print, 'in myFunc'
help, p2, p4
return, 0
end
IDL> info1 = Routine_Info('myPro', /PARAMETERS)
IDL> info2 = Routine_Info('myFunc', /PARAMETERS, /FUNCTIONS)
IDL> info1
{
NUM_ARGS: 0,
NUM_KW_ARGS: 3,
KW_ARGS: ["PARAM1" "PARAM2" "PARAM3"]
}
IDL> info2
{
NUM_ARGS: 0,
NUM_KW_ARGS: 2,
KW_ARGS: ["PARAM2" "PARAM4"]
}
结构的隐含打印输出很方便,因为它显示了标签,并且还展开了数组,而 help 或 print 命令对于结构体都不会这样做。您会注意到,当我请求关于函数(而非过程)的信息时,我不得不添加 /FUNCTIONS 关键字。因此,我可以使用 info 结构体的 KW_ARGS 成员来标识要为每次例程调用使用的 _REF_EXTRA 包中的哪些成员。我还可以用它来验证没有多余的参数传入包装器。
一旦验证了传入包装器的关键字是有效的,您就需要构建将要传递到每个被包装例程中的值子集。动态实现此操作的方法是通过手动构建一个结构体传递给 _EXTRA 关键字。我们通过使用 CreateStruct 仅将那些关键字和值增量地追加到结构体来实现。当使用 _REF_EXTRA 时,您会得到一个包含用于调用您方法的关键字的字符串数组。要获取每个关键字的值,您必须使用 Scope_VarFetch 及其 /REF_EXTRA 关键字来获取本地作用域中该值的引用。以下是针对上面定义的 myPro 和 myFunc 例程的包装器示例:
function myDynamicReferenceWrapper, _REF_EXTRA=refExtra
compile_opt idl2
; 首先确保 _REF_EXTRA 已定义,否则退出
if (~ISA(refExtra)) then return, -1
info1 = Routine_Info('myPro', /PARAMETERS)
info2 = Routine_Info('myFunc', /PARAMETERS, /FUNCTION)
; 检查 _REF_EXTRA 中的无效关键字
foreach keyword, refExtra do begin
if ((Total(keyword eq info1.KW_ARGS) eq 0) && $
(Total(keyword eq info2.KW_ARGS) eq 0)) then begin
Message, 'Invalid keyword ' + keyword
endif
endforeach
; 使用 _REF_EXTRA 中的适当关键字调用 myPro
extra1 = {}
foreach keyword, info1.KW_ARGS do begin
w = where(keyword eq refExtra, found)
if (found gt 0) then begin
value = Scope_VarFetch(keyword, /REF_EXTRA)
extra1 = Create_Struct(extra1, keyword, value)
endif
endforeach
myPro, _EXTRA=extra1
; 使用 _REF_EXTRA 中的适当关键字调用 myFunc
extra2 = {}
foreach keyword, info2.KW_ARGS do begin
w = where(keyword eq refExtra, found)
if (found gt 0) then begin
value = Scope_VarFetch(keyword, /REF_EXTRA)
extra2 = Create_Struct(extra2, keyword, value)
endif
endforeach
return, myFunc(_EXTRA=extra2)
end
如果您担心被包装的方法会修改您的变量,并希望使用 _EXTRA 而不是 _REF_EXTRA,那么可以对此包装器进行一些调整:
function myDynamicValueWrapper, _EXTRA=extra
compile_opt idl2
; 首先确保 _EXTRA 已定义,否则退出
if (~ISA(extra)) then return, -1
info1 = Routine_Info('myPro', /PARAMETERS)
info2 = Routine_Info('myFunc', /PARAMETERS, /FUNCTIONS)
; 检查 _EXTRA 中的无效关键字
myExtraKeywords = Tag_Names(extra)
foreach keyword, myExtraKeywords do begin
if ((Total(keyword eq info1.KW_ARGS) eq 0) && $
(Total(keyword eq info2.KW_ARGS) eq 0)) then begin
Message, 'Invalid keyword ' + keyword
endif
endforeach
; 使用 _EXTRA 中的适当关键字调用 myPro
extra1 = {}
foreach keyword, info1.KW_ARGS do begin
w = where(keyword eq myExtraKeywords, found)
if (found gt 0) then begin
extra1 = Create_Struct(extra1, keyword, extra.(w[0]))
endif
endforeach
myPro, _EXTRA=extra1
; 使用 _EXTRA 中的适当关键字调用 myFunc
extra2 = {}
foreach keyword, info2.KW_ARGS do begin
w = where(keyword eq myExtraKeywords, found)
if (found gt 0) then begin
extra2 = Create_Struct(extra2, keyword, extra.(w[0]))
endif
endforeach
return, myFunc(_EXTRA=extra2)
end
通过添加一个字符串数组参数来指定您想要包装的例程名集合,而不是将 myPro 和 myFunc 硬编码,上述任何一个包装器都可以变得真正通用。