我有一个Tcl实用程序,它可以很容易地确保在控制流离开当前范围( proc)时运行的代码片段。它在TCL8.6.6中崩溃,所以我想知道是否有一种“更好”的方法来实现TCL8.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
}它在TCL8.4中运行得很好,我在代码中都使用了它。
由于我仍然在提高TCL8.6中所有可用功能的速度,我在问如何编写::Util::Defer proc以最好地利用TCL8.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,并提交了一个core.tcl.tk上的bug。
编辑以添加buggy代码:这是对我造成崩溃的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-------------------发布于 2017-04-17 15:19:22
周末,我想到了这个重量级的解决方案。它利用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]
}发布于 2017-04-15 08:20:59
您描述的模式是受支持的;它不应该崩溃(实际上,我无法用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子句放在一起( body异常信息在finally子句的错误异常信息的-during选项中),这样如果两者都出现错误,您就可以发现这两个错误。
% 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
}
}你的里程可能会不同。
发布于 2017-04-15 03:39:33
未经测试的代码(确切的代码段,我多次使用此模式):
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和options变量名称参数到catch。https://stackoverflow.com/questions/43417795
复制相似问题