Reputation: 23
I am having one requirement for calling TCL scripts from Python. My requirement is to call a TCL proc from python. The TCL proc returns the result as an array. I need to parse the returned array in Python to form a dictionary. Below is a working code. Is there any more efficient way to do the same?
Python Code
import tkinter
import shlex
r=tkinter.Tk()
r.tk.eval('source proc.tcl')
arr = []
arr = r.tk.eval('processarray arr')
print(arr)
j=0
for i in arr.split(' '):
print(i)
d = {}
ssplit = arr.split()
print(len(ssplit))
print(len(ssplit)/2)
j=int(len(ssplit)/2)
k=0
for i in range (j):
key = ssplit[k]
d[key] = ssplit[k+1]
k += 2
print(d)
TCL Code
proc processarray { $arr } {
array set arr " arrlist 25645"
set arr(0) "11"
set arr(1) "10"
set arr(2) "20"
set arr(3) "30"
set arr(4) "40"
return [array get arr]
}
Expected Result
{'4': '40', '0': '11', '1': '10', 'arrlist': '25645', '2': '20', '3': '30'}
Upvotes: 0
Views: 805
Reputation: 137717
You can simplify things for yourself a lot by using call
instead of eval
.
import tkinter
r = tkinter.Tk()
# Putting the Tcl code I've used inline
r.tk.eval("""
proc processarray { $arr } {
array set arr " arrlist 25645"
set arr(0) "11"
set arr(1) "10"
set arr(2) "20"
set arr(3) "30"
set arr(4) "40"
# CRITICAL! Must return a list, not a dict
return [list {*}[array get arr]]
}
""")
r.tk.wantobjects(True) # This is the default setting...
arr = r.tk.call('processarray', 'arr')
# This is a bit of magic to turn a pair-list into a dict, based on
# https://stackoverflow.com/questions/47202751/splitting-python-string-into-list-of-pairs
c = {k: v for k, v in zip(arr[:-1:2], arr[1::2])}
print(c)
The critical things here are that:
call
(which is a better option for calling single commands and does argument boundary preservation, etc.),wantobjects(True)
(actually on by default), and[list {*}…]
) and we post-process that list on the Python side to get a dictionary (this is because Tkinter doesn't do dict mapping for you for some reason; that's a Tkinter bug/missing feature).When I run this, I get this output:
{4: '40', 0: '11', 1: '10', 'arrlist': '25645', 2: '20', 3: '30'}
That's as expected; Tcl's arrays have never been order-preserving and are documented to not be so. (I've no idea why Tkinter's converted some keys to int
s.)
Upvotes: 0
Reputation: 52529
Python's Tkinter package isn't documented very well, so I had to dig into the source for this...
There is a _splitdict()
function that will, given a string with an even number of tcl words like that returned by Tcl's array get
, return it as a python dictionary. I don't really know Python, but I bet the underscore and clunky interface means it's internal and you're not supposed to use it. But there doesn't seem to be a public alternative:
>>> import tkinter
>>> tcl = tkinter.Tcl()
>>> tkinter._splitdict(tcl, 'foo 1 bar 2 baz {3 4}')
{'bar': '2', 'foo': '1', 'baz': '3 4'}
Alternatively, you can probably just write your function that does what it does internally. The important bit is the tk.splitlist('some string')
, which breaks its argument up into words according to tcl rules - needed to properly handle spaces or anything like that in the individual words of the list that array get
returns (See above for an example).
A (Untested), trimmed down version. Original can be found in the Python source
def splitdict(v):
"""Return a properly formatted dict built from Tcl list pairs.
Tcl list is expected to contain an even number of elements.
"""
t = r.tk.splitlist(v)
if len(t) % 2:
raise RuntimeError('Tcl list representing a dict is expected '
'to contain an even number of elements')
it = iter(t)
dict = {}
for key, value in zip(it, it):
key = str(key)
dict[key] = value
return dict
Upvotes: 3
Reputation: 142814
I never used TCL but I run this code and get from TCL
4 40 0 11 1 10 arrlist 25645 2 20 3 30
I can convert it to dictionary using range(len())
and [i:i+2]
.
Maybe it is not popular to use range(len())
but in this situation it will be very useful.
import tkinter
r = tkinter.Tk()
r.tk.eval('source proc.tcl')
arr = []
arr = r.tk.eval('processarray arr')
#print(arr)
items = arr.split()
data = {}
for i in range(0, len(items), 2):
key, val = items[i:i+2]
data[key] = val
print(data)
Result
{'4': '40', '0': '11', '1': '10', 'arrlist': '25645', '2': '20', '3': '30'}
EDIT: I can do this also with iter()
and zip()
import tkinter
r = tkinter.Tk()
r.tk.eval('source proc.tcl')
arr = []
arr = r.tk.eval('processarray arr')
#print(arr)
items = arr.split()
data = {}
it = iter(items) # create iterator
for key, val in zip(it, it): # use `zip()` to get two elements from list
data[key] = val
print(data)
Upvotes: 0