Skip to main content
 首页 » 编程设计

python之我可以使用 Tkinter 创建 Tcl 交互式 shell 吗

2024年08月12日3498°冷暖

我目前正在使用 Python 3 中的 ctypes 制作一个 Tcl 应用程序,它包装了一个用 Python 编写的库。

from ctypes import * 
import sys 
 
tcl = cdll.LoadLibrary("libtcl.so") 
 
TCL_OK = 0 
TCL_ERROR = 1 
 
def say_hello(cdata: c_void_p, interp: c_void_p, argc: c_int, argv: POINTER(c_char_p)) -> int: 
    "This function wraps some functionality that is written in Python" 
    print("hello, world") 
    return TCL_OK 
 
# useful type definitions 
Tcl_AppInitProc = CFUNCTYPE(c_int, c_void_p) 
Tcl_CmdProc = CFUNCTYPE(c_int, c_void_p, c_void_p, c_int, POINTER(c_char_p)) 
Tcl_CmdDeleteProc = CFUNCTYPE(None, c_int) 
 
def app_init(interp: c_void_p) -> int: 
    # initialize the interpreter 
    tcl.Tcl_Init.restype = c_int 
    tcl.Tcl_Init.argtypes = [c_void_p] 
    if tcl.Tcl_Init(interp) == TCL_ERROR: 
        return TCL_ERROR 
 
    # create custom commands 
    tcl.Tcl_CreateCommand.restype = c_void_p 
    tcl.Tcl_CreateCommand.argtypes = [c_void_p, c_char_p, Tcl_CmdProc, c_int, Tcl_CmdDeleteProc] 
    tcl.Tcl_CreateCommand(interp, b"say_hello", Tcl_CmdProc(say_hello), 0, Tcl_CmdDeleteProc(0)) 
 
    return TCL_OK 
 
if __name__ == "__main__": 
    # initialize argv 
    Argv = c_char_p * (1 + len(sys.argv)) 
    argv = Argv(*(bytes(arg, "utf-8") for arg in sys.argv), 0) 
 
    # summon the chief interpreter 
    tcl.Tcl_Main.argtypes = [c_int, POINTER(c_char_p), Tcl_AppInitProc] 
    tcl.Tcl_Main(len(sys.argv), argv, Tcl_AppInitProc(app_init)) 

在命令行中,这就像一个带有额外命令的 Tcl 解释器,这正是我想要的。它解析 sys.argv 并作为交互式 shell 和运行 Tcl 脚本工作。

bash$ python3 hello.py 
% say_hello 
hello, world 
% ls  
foo.tcl  hello.py 
% exit 
bash$ cat foo.tcl 
say_hello 
bash$ python3 hello.py foo.tcl 
hello, world 
bash$ 

但是,我知道 Python 已经在 tkinter 模块中附带了一个 Tcl 解释器。相反,我想使用它,因为它已经有一个很好的 Python 包装的 API,可以让我省去一些关于 ctypes 的麻烦。

我可以很容易地创建一个解释器并添加命令。

from tkinter import * 
 
def say_hello(): 
    print("hello, world") 
 
if __name__ == "__main__": 
    tcl = Tcl() 
    tcl.createcommand("say_hello", say_hello) 
    tcl.eval("say_hello") 

但是我找不到任何方法来调用 Tcl_Init 或 Tcl_Main,没有它们我就无法交互式运行它。虽然我不太关心命令行解析器,但尝试在 Python 中复制 Tcl 交互式 shell 及其所有功能将是很多工作,比如运行外部程序就好像它们是 Tcl 命令(例如 ls 在上面的例子中)。如果那是我唯一的选择,我会坚持使用 ctypes。

是否有任何方法,即使是 hacky 或不受支持的方法,也可以将 Tkinter 附带的 Tcl 解释器作为交互式 shell 运行?

请您参考如下方法:

Tcl_Main() 中实现的 REPL 非常简单;你可以用几行 Tcl 来做一个(稍微精简的)版本:

set cmd "" 
set prompt "% " 
while true { 
    # Prompt for input 
    puts -nonewline $prompt 
    flush stdout 
    if {[gets stdin line] < 0} break 
 
    # Handle multiline commands 
    append cmd $line "\n" 
    if {![info complete $cmd]} { 
        set prompt "" 
        continue 
    } 
    set prompt "% " 
 
    # Evaluate the command and print error/non-empty results 
    if {[catch $cmd msg]} { 
        puts stderr "Error: $msg" 
    } elseif {$msg ne ""} { 
        puts $msg 
    } 
    set cmd "" 
} 

您需要做的就是在 Python 代码内的 Tcl 解释器中运行它。您还可以用 Python 重新实现大部分 REPL;你真正需要 Tcl 的唯一部分将是 info complete $cmd(测试输入缓冲区中是否有完整的命令)和 catch $cmd msg(评估输入Tcl 解释器中的缓冲区并捕获结果和错误)。