我遇到了一个我无法理解的eval
命令的奇怪行为。当我尝试使用eval运行一个名称存储在变量中的命令时,我得到了奇怪的结果。
数组mCallBackCont
在::postLayRep1||mainTableView sendToLoads
下的值为insert
,因此 * :
>>set mCallBackCont(insert)
::postLayRep1||mainTableView sendToLoads
::postLayRep1||mainTableView
是具有sendToLoads
公共方法
当我尝试执行以下操作之一时:
eval {::postLayRep1||mainTableView sendToLoads}
eval ::postLayRep1||mainTableView sendToLoads
eval "::postLayRep1||mainTableView sendToLoads"
eval $mCallBackCont(insert)
eval "mCallBackCont(insert)"
我得到了正确的行为,但是当我使用
时eval {$mCallBackCont(insert)}
我收到错误:
invalid command name "::postLayRep1||mainTableView sendToLoads"
当我尝试使用没有参数的常规proc时:
>>proc test_proc {} {return}
>>set a test_proc
>>eval {$a}
一切正常,但是当我添加参数时,同样的事情会发生:
>>proc test_proc {val} {puts $val}
>>set a [list test_proc 1]
test_proc 1
>>eval {$a}
invalid command name "test_proc 1"
由于eval命令是我正在使用的库的代码的一部分,我无法更改它,我唯一能确定的是mCallBackCont(insert)
的值。库中的代码是:
if { [catch {eval {$mCallBackCont(insert) [namespace tail $this] $type $name $n $redraw}} e] } {
error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}
为什么eval {$var}
适用于procs而不适用于类的方法(我猜它在某种程度上与proc是一个单词命令,而一个方法更复杂的事实有关)?
我可以通过何种方式设置mCallBackCont(insert)
的值以使其正常工作?
* - 我试图将mCallBackCont(insert)
中的值放在一个列表中,并作为""
所包围的一个字符串
答案 0 :(得分:4)
首先,Tcl命令名称可以包含空格。或者实际上几乎任何其他字符;唯一需要注意的是:
,因为::
是命名空间分隔符,而前导::
很好,因为它只是意味着它是一个完全限定的名称。
因此,::postLayRep1||mainTableView sendToLoads
是一个合法但不寻常的命令名称。如果将名称放在变量中,则可以使用该变量的读取,就像它是命令名一样:
$theVariableContainingTheCommandName
在这方面使用数组元素没什么特别的。
现在,如果您想将其视为脚本,请将其传递给eval
,如下所示:
eval $theVariableContainingTheScript
你遇到的真正问题是你正在做的事情:
eval {$theVariableContainingTheScript}
这被定义为完全完全相同:
$theVariableContainingTheScript
那不会做你想要的东西。查看导致问题的代码:
if { [catch {eval {$mCallBackCont(insert) [namespace tail $this] $type $name $n $redraw}} e] } {
error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}
在这种情况下,变量中的值必须是命令的名称,而不仅仅是脚本片段。最简单的解决方法是创建一个在额外参数中绑定的别名:
interp alias {} callBackForInsert {} ::postLayRep1||mainTableView sendToLoads
然后您可以使用callBackForInsert
,就好像它是调用::postLayRep1||mainTableView
和第一个参数sendToLoads
的组合一样。它实际上是一个命名的部分应用程序。或者,您可以使用帮助程序:
proc callBackForInsert args {
return [uplevel 1 {::postLayRep1||mainTableView sendToLoads} $args]
}
但在这个简单的案例中,这既丑陋又慢。 8.6中更好的是使用tailcall
:
proc callBackForInsert args {
tailcall ::postLayRep1||mainTableView sendToLoads {*}$args
}
但由于额外堆栈帧操作的开销,这仍然比别名慢。
然而,最好的修复如果你可以改变库,所以它使用这样的回调(假设Tcl 8.5或更高版本):
if { [catch {eval {{*}$mCallBackCont(insert) [namespace tail $this] $type $name $n $redraw}} e] } {
error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}
可以简化为:
if { [catch {{*}$mCallBackCont(insert) [namespace tail $this] $type $name $n $redraw} e] } {
error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}
一个好的经验法则是,在现代Tcl代码中几乎没有理由使用eval
; {*}
- 扩张几乎总是更接近预期。
如果您仍然坚持使用8.4但可以更改库代码,则可以改为:
if { [catch {eval $mCallBackCont(insert) {[namespace tail $this] $type $name $n $redraw}} e] } {
error "Wrong number of arguments for the procedure \"$mCallBackCont(insert)\". Should be \"table type name num redraw\"."
}
这使用了eval
在通过Tcl脚本评估引擎反馈它们之前连接其参数的事实。
别名,扩展,tailcall
和(在这个答案中没有使用)组合的组合可以让你用很少的代码做很棒的事情,允许复杂的参数重新混合,而不必用大量的帮助程序加载你自己