我有一个Tcl实用程序,可以很容易地确保在控制流离开当前范围(proc
)时运行一段代码。它在Tcl 8.6.6中崩溃,所以我想知道是否有一种“更好”的方法来实现Tcl 8.6中的功能?
示例用法是:
proc test {file} {
set fh [open $file]
::Util::Defer [list close $fh]
# ... do a bunch of stuff
# and even if we hit an error
# [close $fh] will be evaluated as we return
# from the proc
}
它在Tcl 8.4中运行良好,我在代码中使用它。
由于我还在快速了解Tcl 8.6中提供的所有功能,我问如何编写::Util::Defer
proc以最好地利用Tcl 8.6?
以下是8.4实现:
namespace eval ::Util {}
proc ::Util::Defer_impl {cmd args} {
uplevel 1 $cmd
}
proc ::Util::Defer {cmd} {
set vname _u_defer_var
# look for a unique variable name
while {[uplevel 1 [list info vars $vname]] != ""} {
set vname ${vname}_
}
uplevel 1 [list set $vname $cmd]
# when the variable is unset, trigger a call to the command
uplevel 1 [list trace add variable $vname unset [list ::Util::Defer_impl $cmd]]
# return a chunk of code enabling the user to cancel this if desired
return [list variable $vname unset [list ::Util::Defer_impl $cmd]]
}
已编辑添加: 我很感激答案。说实话,我已经有了文件句柄的其他语法糖,这个:
proc test {file} {
set fh [::Util::LocalFileHandle $file]
# do stuff
}
我只是希望更多地使用::Util::Defer
的通用解决方案 - 因为我偶尔在同一个proc中有两次或三次使用(在不同的位置)。是的,如果不存在或不可读,我将省略错误处理。
注意:我已向ActiveState报告错误并提交了bug at core.tcl.tk。
编辑添加错误的代码:这是导致我崩溃的Tcl代码,它略微削弱了本质(而不是成为全面的::Util::Defer
)。
# ---------------begin script-------------------
package require Itcl
proc ::try_uplevel {} {
return [uplevel 1 [list ::info vars _u_defer_var]]
}
itcl::class ::test_class {
constructor {} {}
public proc test_via_proc {} {
::try_uplevel
}
}
::test_class::test_via_proc
# ---------------end script-------------------
答案 0 :(得分:1)
您描述的模式是受支持的模式;它不应该崩溃(实际上我无法用8.6.3或8.6支持分支的尖端重现崩溃)。它唯一的问题是,如果您在close
(或任何其他延迟脚本)中出现错误,则无法报告该错误,正如您可以从此代码段中看到的那样(%
是提示):
% apply {{} {
::Util::Defer [list error boo]
puts hi
}}
hi
%
这是为什么我花了很多精力在8.6中提供try
命令的部分原因。有了它,你可以这样做:
proc test {filename} {
set f [open $filename]
try {
# Do stuff with $f
} finally {
close $f
}
}
它还会处理棘手的事情,例如在体内抛出的拼接错误和finally
子句(身体异常信息位于-during
子句的finally
选项中&#39 ; s错误异常信息)所以,如果两个地方都有错误,你可以找到两者。
% catch {
try {
error a
} finally {
error b
}
} x y
1
% puts $x
b
% puts $y
-errorstack {INNER {returnImm b {}}} -errorcode NONE -errorinfo {b
while executing
"error b"} -errorline 5 -during {-code 1 -level 0 -errorstack {INNER {returnImm a {}}} -errorcode NONE -errorinfo {a
while executing
"error a"} -errorline 3} -code 1 -level 0
就个人而言,我更倾向于写这个:
proc withreadfile {varName filename body} {
upvar 1 $varName f
set f [open $filename]
try {
return [uplevel 1 $body]
} finally {
close $f
}
}
proc test {file} {
withreadfile fh $file {
# Do stuff with $fh
}
}
您的里程可能会有所不同。
答案 1 :(得分:0)
未经测试的代码(这个确切的片段,我多次使用过此模式):
proc test file {
try {
open $file
} on ok fh {
# do stuff with fh
# more stuff
} finally {
catch {close $fh}
}
}
应该差不多。无论您是否使用try
结构处理错误,(或者您是否收到错误),finally
子句中的代码都会在结束时运行。如果您希望能够取消操作,请在子句中使用简单的if
。
修改强>
如果有人想看到频道关闭时产生的任何错误,将它包装在catch
中是一个坏主意,如果无法打开文件和频道ID,这是必要的变量未创建。替代方案包括:
if {[info exists fh]} {close $fh}
result
的{{1}}和options
变量名称参数。答案 2 :(得分:0)
在周末,我想到了这个重量级的解决方案。它利用itcl::local
功能来实现相同的效果。它确实依赖于Itcl
- 但由于问题是与Itcl
的交互,这似乎是一个合理的解决方案,即使它不是纯粹的Tcl。
itcl::class Defer_impl {
constructor {cmd} {} {
set _to_eval $cmd
}
destructor {
uplevel 1 $_to_eval
}
private variable _to_eval {}
}
proc ::Util::Defer {cmd} {
uplevel 1 [list itcl::local ::Defer_impl #auto $cmd]
}