我正在用Tcl8.6和Rivet对TclOO进行一些实验,但是我遇到了麻烦,因为我无法按照自己的意愿行事.
问题可以通过.rvt
文件中的以下代码简单地重现:
<?
proc dumbproc {} {
puts "this is dumbproc ([namespace current])"
}
oo::class create foo {
method bar {} {
puts "this is bar ([namespace current])"
dumbproc
}
}
set obj [foo new]
dumbproc
$obj bar
Run Code Online (Sandbox Code Playgroud)
如果我只是查看代码,它似乎应该按预期工作,但它确实没有,因为Rivet包的细微行为和特定配置选择.
在这个例子中,我使用的是一个.rvt
代码在::request
命名空间内执行的文件,因此该dumbproc
过程的完全限定名称是::request::dumbproc
.当在bar
方法内部调用名称解析算法时,它会搜索dumbproc
内部::oo::Obj12
,然后进入::oo
,最后进入::
,而不会找到它并给出以下错误.
this is dumbproc (::request) this is bar (::oo::Obj16)
invalid command name "dumbproc"
while executing
"dumbproc"
(class "::request::foo" method "bar" line 3)
invoked from within
"$obj bar"
(in namespace eval "::request" script line 21)
invoked from within
"namespace eval request {
puts -nonewline ""
proc dumbproc {} {
puts "this is dumbproc ([namespace current])"
}
oo::class create..."
Run Code Online (Sandbox Code Playgroud)
因此,Tcl 正确地做它的功能,然后是一个功能.但是这种行为是不可预测的,因为当你编写一些类代码时,你必须知道它将被使用的上下文.
实际上,如果我删除起始<?
Rivet魔法,将代码放在test.tcl
文件中并在交互式会话中使用它,我会得到相同的错误:
$ tclsh
% namespace eval ::myns {source test.tcl}
this is dumbproc (::myns)
this is bar (::oo::Obj12)
invalid command name "dumbproc"
Run Code Online (Sandbox Code Playgroud)
我试图通过将当前命名空间添加到类创建代码来解决该问题
::oo::class create [namespace current]::foo { ... }
Run Code Online (Sandbox Code Playgroud)
然后,我也尝试obj
在命名空间内创建对象
::oo::class create [namespace current]::foo { ... }
namespace eval [namespace current] {set obj [[namespace current]::foo new]}
Run Code Online (Sandbox Code Playgroud)
然后,我切换到create
类的方法,为对象提供包含命名空间的限定名称
foo create [namespace current]::obj
obj bar
Run Code Online (Sandbox Code Playgroud)
但一切都没有成功.每个试验表明,无论我如何操作,TclOO类中的方法总是在其对象唯一命名空间内执行.我错了吗?
有没有办法得到我想要的东西?TclOO是不打算以这种方式工作,在这种情况下为什么?让我感到惊讶的是这种依赖于上下文的行为,我不确定这是正确的,但也许我完全错了,并且有一些合理的案例,我很遗憾.
每个TclOO对象的内部实际上是它自己的命名空间.您可以在方法中使用self namespace
或namespace current
在其中获取命名空间的名称,或info object namespace $theobj
从任何位置获取命名空间.默认情况下,命名空间中唯一的命令是my
(用于调用私有方法),其他命名空间中的某些命令可通过标准Tcl namespace path
机制获得(这是您获取self
和next
使用的方式).
解决这个问题的最简单方法可能是将其添加到foo
类的构造函数中:
namespace path [list {*}[namespace path] ::request]
Run Code Online (Sandbox Code Playgroud)
在您的特定情况下,您必须实际添加构造函数...
constructor {} {
namespace path [list {*}[namespace path] ::request]
# If you had a non-trivial constructor in a superclass, you'd need to call
# [next] too.
}
Run Code Online (Sandbox Code Playgroud)
从长远来看,要求一种机制可以合理地添加到用于为类的对象设置默认值的命名空间列表中.如果您需要,请提交功能请求 ...
[编辑]:如果您刚刚将父命名空间添加到当前对象的命令解析路径后,您可以通过添加更多魔法来实现:
oo::class create foo {
self {
method create args {
set ns [uplevel 1 {namespace current}]
next {*}[linsert $args 1 $ns]
}
method new args {
set ns [uplevel 1 {namespace current}]
next {*}[linsert $args 0 $ns]
}
}
constructor {creatorNS args} {
namespace path [list {*}[namespace path] $creatorNS]
}
method bar {} {
puts "this is bar ([namespace current])"
dumbproc
}
}
Run Code Online (Sandbox Code Playgroud)
然后,这将自动将当前命名空间置于实例的路径上.如果你在很多类中这样做,你可能想要创建一个包含大多数机器的元类,但上面的技术(类对象本身的一些方法的self
声明foo
)适用于简单的情况.