【问题标题】:TclOO class inside namespace: calling namespace procs give errors命名空间内的 TclOO 类:调用命名空间过程会出错
【发布时间】:2023-03-25 07:48:01
【问题描述】:

我正在对来自 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

如果我只看代码,它似乎应该按预期工作,但实际上并没有,因为 Rivet 包的微妙行为和选择的特定配置。

在本例中,我使用了一个.rvt 文件,其代码在::request 命名空间内执行,因此dumbproc 过程的完全限定名称是::request::dumbproc。当在bar方法中调用名称解析算法时,它会在::oo::Obj12中搜索dumbproc,然后在::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..."

所以,Tcl 正确在做它所做的事情,一个特性。但是这种行为是不可预测的,因为当你编写一些类代码时,你必须知道它将被使用的上下文。

确实,如果我删除起始的 &lt;? Rivet 魔法,将代码放入 test.tcl 文件并在交互式会话中使用它,我会得到同样的错误:

$ tclsh
% namespace eval ::myns {source test.tcl}
this is dumbproc (::myns)
this is bar (::oo::Obj12)
invalid command name "dumbproc"

我尝试通过将当前命名空间添加到类创建代码来解决问题

::oo::class create [namespace current]::foo { ... }

然后,我也尝试在命名空间内创建obj 对象

::oo::class create [namespace current]::foo { ... }
namespace eval [namespace current] {set obj [[namespace current]::foo new]}

然后,我切换到类的create 方法,为对象提供一个包含命名空间的限定名称

foo create [namespace current]::obj
obj bar

但一切都失败了。每次试验都表明,无论我如何做,TclOO 类中的方法总是在其对象唯一命名空间内执行。我错了吗?

有没有办法得到我想要的? TclOO 是否不打算以这种方式工作,在这种情况下为什么呢?真正让我惊讶的是这种依赖于上下文的行为,我不确定这是不是正确的事情,但也许我完全错了,并且有一些合理的案例,我错过了。

【问题讨论】:

    标签: tcl


    【解决方案1】:

    每个 TclOO 对象的内部实际上是其自己的 命名空间。您可以在方法中使用self namespacenamespace current 来获取命名空间的名称,或者使用info object namespace $theobj 从任何地方获取命名空间。默认情况下放置在命名空间中的唯一命令是my(用于调用私有方法),其他命名空间中的一些命令通过标准Tcl namespace path 机制可用(这就是你获得selfnext 的方式可用)。

    解决此问题的最简单方法可能是将其添加到 foo 类的构造函数中:

    namespace path [list {*}[namespace path] ::request]
    

    在您的具体情况下,您实际上必须添加一个构造函数...

    constructor {} {
        namespace path [list {*}[namespace path] ::request]
        # If you had a non-trivial constructor in a superclass, you'd need to call
        # [next] too.
    }
    

    从长远来看,要求一种机制来添加名称空间列表可能是合理的,这些名称空间用于为类的对象设置默认值。如果您愿意,请提交feature request...


    [编辑]:如果您只是在将父命名空间添加到当前对象的命令解析路径之后,您可以通过添加更多魔法来做到这一点:

    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
        }
    }
    

    然后会自动将创建时的当前命名空间放在实例的路径上。如果您在许多类中执行此操作,您可能希望创建一个包含大部分机器的元类,但上述技术(self foo 上某些方法的声明类对象本身)适用于简单的情况。

    【讨论】:

    • 以上是因为类是普通对象(知道是类)和newcreate 是普通方法(但不是在 Tcl 中实现,而是在通常情况下) — 在 C 中,就像 puts 是在 C 中实现的命令一样。
    • 我必须尝试元类方法,让我们看看会发生什么。关于功能请求,我不确定要问什么,因为我真的不知道我需要什么。更多的实验是必须的。谢谢。
    • @Marco 一般来说,它最终会出现在我的 TODO 列表中,而且我非常擅长将半成品变成完整的计划。 ;-)
    猜你喜欢
    • 2021-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-17
    • 1970-01-01
    • 2020-02-19
    • 2010-10-20
    相关资源
    最近更新 更多