Python-Tcl-Interactions

Andreas Drollinger 2021-05-23 - This page shows how using the Tcl interpreter that is part of Python Tkinter.

The de factor standard GUI library for Python, Tkinter, contains a full-featured Tcl interpreter, together with the Tk GUI toolkit. This allows running Tcl commands from Python, as well as Python commands from Tcl after performing the some setup.

Tkinter is included with standard Linux, Windows and Mac OS X installations of Python.

Call Tcl from Python

A Tcl interpreter instance can be created from the Python interpreter after importing the Tkinter module:

import tkinter
tcl_intrpr = tkinter.Tcl()

The created Tcl interpreter instance exposes the methods eval and call to run Tcl commands from the Python program. Eval takes as argument a single string with the concatenated Tcl command and arguments, and the result evaluated by the Tcl interpreter is returned as string to the Python interpreter:

res = tcl_intrpr.eval('expr 12+23')
res
=> '35'

Call takes separate arguments for the Tcl command and arguments, and the result returned to the Python interpreter corresponds to the value representation in the Tcl interpreter:

res = tcl_intrpr.call('expr', '123')
res
=> 123
type(res)
=> int

Call Python from Tcl

The Python functions that should be accessible from the Tcl interpreter have first to be registered as Tcl commands. For demonstration purposes, lets create a Python function that takes an unspecified number of arguments, and register it then as Tcl command. The registration function returns the Tcl function name:

def my_python_function(*argv):
        return 'my_python_function: ' + (' '.join(argv))

tcl_cmd = tcl_intrpr.register(my_python_function)

The Tcl function name may be different from the Python function, so it is important to remember it:

tcl_cmd
=> '2013100028616my_python_function'

The exposed function can now be executed from the Tcl interpreter. Since my_python_function accepts an arbitrary number of arguments, various options are shown:

tcl_intrpr.call(tcl_cmd)
=> 'my_python_function: '
tcl_intrpr.call(tcl_cmd, '1')
=> 'my_python_function: 1'
tcl_intrpr.call(tcl_cmd, 'Hello', 'I', 'am', 'Jeff')
=> 'my_python_function: Hello I am Jeff'

Call Python from Tcl, use a more elegant registration command

The fact that the created Tcl command name differs from the Python name is a bit nasty. The following proposed custom registration command to register Tcl functions allows defining explicitly the name of the exposed Tcl function. The created Tcl function is simply renamed into the desired name. If the function exists already, it is first deleted. If no Tcl function name is provided, the Python function name is used:

def register(my_python_function, tcl_cmd_name=None):
    if tcl_cmd_name is None:
        tcl_cmd_name = my_python_function.__name__
    tcl_cmd = tcl_intrpr.register(my_python_function)
    tcl_intrpr.eval('catch {rename ' + tcl_cmd_name + ' ""}')
    tcl_intrpr.call('rename', tcl_cmd, tcl_cmd_name)
    return tcl_cmd_name

Next, the Python function will again be registered, but this time with the custom registration command:

register(my_python_function)
=> 'my_python_function'

And now, the Tcl function can be executed using using the original python function name:

tcl_intrpr.eval('my_python_function')
=> 'my_python_function: '
tcl_intrpr.eval('my_python_function 1')
=> 'my_python_function: 1'
tcl_intrpr.eval('my_python_function Hello I am Jeff')
=> 'my_python_function: Hello I am Jeff'

Variable access

There is no mechanism in place to access from Python directly Tcl variables and vis-versa. Instead, functions have to be used to access variables in the other world. So, Tcl variables can be accessed from the Python interpreter with the set command:

tcl_intrpr.call('set', 'my_var', 123)
=> 123
tcl_intrpr.call('set', 'my_var')
=> 123

Python variables can be accessed from the Tcl interpreter by defining specific variable access functions:

# Python variable access function definitions
def set_var(var_name, var_value):
         globals()[var_name] = var_value

def get_var(var_name):
    return globals()[var_name]

# Register the access functions
register(set_var)
register(get_var)

# Define a Python variable from Tcl and read it then from Python
tcl_intrpr.call('set_var', 'my_python_var', 234)
my_python_var
=> '234'

# Define a Python variable from Python and read it then from Tcl
my_python_var = 'Hello world'
tcl_intrpr.call('get_var', 'my_python_var')
=> 'Hello world'

Pythonic way to access Tcl variables

A more elegant way to access Tcl variables from the Python side is by exposing the Tcl variables as attributes of a Python class. The __setattr__ and __getattr__ methods will be responsible to define and read the corresponding Tcl variables if a class instance variable is accessed:

class TclVars(object):
    def __init__(self, tcl):
        self.tcl = tcl

    def __setattr__(self, name, value):
        if name == 'tcl':
            object.__setattr__(self, name, value)
        else:
            self.tcl.call('set', '::' + name, value)

    def __getattr__(self, name):
        if name == 'tcl':
            return object.__getattr__(self, name)
        else:
            return self.tcl.call('set', '::' + name)

tcl_vars = TclVars(tcl_intrpr)

Tcl variables can now be written and read in the following way:

tcl_vars.my_tcl_var = 123
tcl_vars.my_tcl_var
=> 123

The following lines demonstrate that the defined variable is well known to the Tcl interpreter:

tcl_intrpr.call('set', 'my_tcl_var')
=> 123

Manipulations of the Tcl variable performed by the Tcl interpreter are transparent to the Python interpreter:

tcl_intrpr.call('incr', 'my_tcl_var', 2)
tcl_vars.my_tcl_var
=> 125

And manipulations on the Python side are again visible on the Tcl side:

tcl_vars.my_tcl_var += 3
tcl_intrpr.call('set', 'my_tcl_var')
=> 128

Discussions

Don't hesitate to launch some discussions here ...


See also