如何在Tcl中将可变参数从一个函数传递到另一个函数

5
我想要将一个函数中获取的可变参数传递给另一个函数,但我无法做到。该函数获取偶数个可变参数,然后必须将其转换为数组。以下是示例。
过程abc1获取两个参数(k k),而不是从abc1过程中传递这些参数到proc abc,需要进行列表到数组的转换。列表到数组的转换在proc1即abc1中有效,但在第二个proc即abc中无效。
下面是所得到的错误:
proc abc {args} {
    puts "$args"
    array set arg $args
}

proc abc1 {args} {
    puts "$args"
    array set arg $args
    set l2 [array get arg]
    abc $l2
}

abc1 k k
abc k k

输出:

k k
{k k}
list must have an even number of elements
    while executing
"array set arg $l1"
    (procedure "abc" line 8)
    invoked from within
"abc $l2"
    (procedure "abc1" line 5)
    invoked from within
"abc1 k k"
    (file "vfunction.tcl" line 18)

堆栈跟踪中的行号是错误的;这可能是因为我重新格式化的原因,也可能是因为发布前进行的精简处理。无论哪种方式,这对问题并不是非常重要。 - Donal Fellows
3个回答

14

最佳解决方案:扩展替换

正确的方法是确保外部过程(在堆栈术语中)以正确的方式调用内部过程;如果需要多个参数,则应该提供多个参数。随着Tcl 8.5的出现,这可以轻松地通过一种称为扩展替换的小语言语法完成:

proc abc1 {args} {
    puts "$args"
    array set arg $args
    set l2 [array get arg]
    abc {*}$l2
    # Or combine the two lines above into: abc {*}[array get arg]
}

{*} 做的就是告诉Tcl将剩余的单词(使用列表语法规则)分割并用作多个参数,而不是Tcl默认的 “一个视觉单词形成一个单词”的规则。 这是理想的

旧解决方案:Eval命令

如果您仍在出于某种原因使用旧版本的Tcl(即Tcl 8.4或更早版本),则可以使用eval命令代替上述语法:

eval abc $l2

上面的eval有一些稍微更有效率的方法,你可能会在老的代码中看到;例如:

eval [linsert $l2 0 abc]
eval [list abc] [lrange $l2 0 end]
# ... etc ...

但实际上,它们都被abc {*}$l2所取代,这种方法更短、更简单,速度更快。(只是在8.4或之前的版本不可用,许多部署尚未升级。)如果可以,请使用展开语法。事实上,在8.5及以上版本中,成熟的Tcl代码几乎不需要使用eval;这一点甚至令该语言的维护人员感到惊讶。


1
另一种可能更可取的选择是通过引用传递参数,并使用 upvar 访问它们,而不是使用 {*}eval - Eric Melski

1

之间有很大的区别

abc k k

并且

abc [array get arg]

在第一种情况下,您传递了两个参数,每个参数都是k。在第二种情况下,您传递了一个事物的列表 - 在您的示例中,是两个k的列表:k k

Nir的答案有意绕过了这个问题,但更好的解决方案是编写abc1,使其正确调用abc

proc abc1 {args} {
  array set arg $args
  set l2 [array get arg]
  eval abc $l2
  # or just
  # eval abc $args
}

-1
当你传递abc $l2时,你将abc1的args作为单个参数传递给了abc。因此,在abc中,args保存有一个包含单个项({k k})的列表。
你可以像这样做:
proc abc {args} {    
  #the next line assumes that if $args has only a single item, then it is 
  #a list in itself. It's a risky decision to make but should work here.
  if { [llength $args] == 1 } { set args [lindex $args 0] }
  puts "$args"
  array set arg $args
}

这是一个之前由用户“angle”提交并随后被删除的答案的逐字复制。您是从两个不同的帐户回答还是复制了别人的答案? - Bryan Oakley
首先,我将“angle”报告为垃圾邮件,因为他的答案是我的答案的逐字逐句复制(而不是相反的情况,您可以查看时间)。 - Nir Levy
其次,关于“有更好的解决方法”,我不确定:在我看来,“eval”选项并不安全,扩展替换仅从8.5版本开始提供。如果您能想到更好的解决方案,应该发布出来。 - Nir Levy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接