Repository: flightaware/tohil (language: python)

Issue:
to= option for tclobj and tcldict object creation, setting "to" as a member of those objects
Allow tclobj and tcldict types to accept an option to=type parameter at object creation type, and provide a "to" member that can be get and set, obj.to=str, etc, to one of our accepted datatypes.  This is cool.


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 9aee3f4..b30a486 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,5 +18,9 @@ config.log
 config.status
 
 build
+dist
 
 venv
+
+pysrc/tohil.egg-info/
+tests/__pycache__/
diff --git a/Makefile.in b/Makefile.in
index 6e8e8a2..c58b384 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -460,4 +460,4 @@ install-python-module:
 	python3 setup.py install
 
 clean-python-module:
-	rm -rf setup.py build dist
+	rm -rf setup.py build/ dist/ pysrc/tohil.egg-info/ tests/__pycache__/
diff --git a/README.MacOSX b/README.MacOSX
index 8448557..ded89a5 100644
--- a/README.MacOSX
+++ b/README.MacOSX
@@ -1,6 +1,6 @@
 
 
 # macports
-#./configure --prefix=/opt/local --exec-prefix=/opt/local --with-python-version=3.9
+#./configure --prefix=/opt/local --exec-prefix=/opt/local --with-python-version=3.9 --with-tcl=/opt/local/lib
 
 ./configure --prefix=/usr/local --with-tcl=/usr/local/lib/ --with-python-version=3.8
diff --git a/TCLOBJECTS.md b/TCLOBJECTS.md
index deff2c2..2ee2ddc 100644
--- a/TCLOBJECTS.md
+++ b/TCLOBJECTS.md
@@ -61,6 +61,9 @@ When a tclobj contains tcl lists, cool stuff comes into play.
 
 You can get the length of the list with obj.llength(), while obj.lindex(i) will return the i'th element.
 
+You can also just ask for `len(t)` or `l[i]` to get the i'th element, although lindex supports the
+to=type conversion as well.
+
 obj.lappend() will append python stuff to the list stored in the tclobj.
 
 obj.lappend_list() will append a tcl object comprising a list, or a python list, to a list, making it flat, i.e. each element of the list is appended to obj's list.
@@ -72,13 +75,13 @@ Thanks to tohil's increasingly thorough tclobj object implementation and python'
 >>> x
 <tohil.tclobj: '1 2 3 4 5 6'>
 >>> x[2]
-<tohil.tclobj: '3'>
+'3'
 >>> x[3:]
-[<tohil.tclobj: '4'>, <tohil.tclobj: '5'>, <tohil.tclobj: '6'>]
+['4', '5', '6']
 >>> x[-2:]
-[<tohil.tclobj: '5'>, <tohil.tclobj: '6'>]
+['5', '6']
 >>> x[-2:-1]
-[<tohil.tclobj: '5'>]
+['5']
 ```
 
 ### comparing tclobjs to each other
@@ -95,28 +98,45 @@ It seems pretty good, but this is new stuff, so be careful and let us know how i
 
 ### find out the tclobj tcl object's type and reference count
 
-t.type() will tell you the tcl object type of the tcl object stored within
+t.tcltype will tell you the tcl object type of the tcl object stored within
 the tclobj.  Note that you may get nothing back even though there is some
 valid thing there, say for instance a dict, but you haven't accessed it as
-a dict, so it's just a string or list or whatever until you do.
+a dict, so it's just a string or list or some other data type until you do.
 
-t.refcount() will tell you the reference count of the tclobj's tcl object.  This is kind of cool.
+t.refcount will tell you the reference count of the tclobj's tcl object.  This is kind of cool.
 
 Note if you're poking around that sometimes you might think the reference
 count is one higher than it should be, but frequently the object you just
 set the value of also happens to be the tcl interpreter result (you used
 the interpreter to make it).  Once the interpreter does something else and
-produces a new result,
+produces a new result, your object's reference count will go down by one.
+
+## tcldict objects
+
+tcldicts have the same internal structure as tclobj... a python object pointing to a tcl
+object.  But it is a distinct data type because it has different implementations of
+sequences and maps and whatnot, to provide pythonic feel to tcl dictionaries, which can
+be hierarchies of key-value data.
+
+You can create a tcldict object similarly to creating a tclobj.
+
+>>> d = tohil.tcldict("a 1 b 2 c 3 d 4 e 5")
+
+### accessing elements in a tcldict
+
+You can access elements using normal python dict access techniques.
 
-## TDs tcl dict access to tclobj objects
+For instance, `d["a"]` returns 1, `d.get('a')` does the same.  With the "get" approach
+you can specify a to=type to control what python type is returned.  Also you
+can set the python type returned by doing `d.to = type`.
 
-To avoid confusion with python dicts, we are calling tcl dicts td's.
+The tcldict can represent a hierarchy of dicts, so you can say stuff like
 
-### td_set()
+### setting values into a tcldict
 
-The td_set method will do "dict set" on a tclobj.  It takes a key and a value.
+Setting values will do "dict set" on a tcldict.  It takes a key and a value.
 The value can be one among a number of different python objects.
-One choice is a tclobj object, in which case tohil will do the right thing
+Two choices are a tclobj or tcldict object, in which case tohil will do the right thing
 and grab a reference to the object rather than copying it.
 
 The key can be a
@@ -125,72 +145,95 @@ dictionary, it will treat it as a nested tree of dictionaries, with inner
 dictionaries stored as values inside outer dictionaries.
 
 ```
-x.td_set('a',1)
-x.td_set(['a','b','c','d'],'bar')
+d[['airport', 'KHOU', 'name']] = 'Houston Hobby'
+d[['airport', 'KHOU', 'lat']] = 29.6459
+d[['airport', 'KHOU', 'lon']] = -95.2769
 ```
 
 The td_get method will do a "dict get" on a tclobj.  It returns the object in the style requested, str by default, but to= can be specified, as in:
 
 ```
->>> x = tohil.eval("list a 1 b 2 c 3", to=tohil.tclobj)
->>> x.td_get('a')
+>>> x = tohil.eval("list a 1 b 2 c 3", to=tohil.tcldict)
+>>> x.get('a')
 '1'
->>> x.td_get('a',to=int)
+>>> x.get('a',to=int)
 1
 ```
 
-### td_get()
 
-Likewise, `td_get` will accept a list of keys, treating the tcl object as a nested tree of dictionaries, with inner dictionaries stored as values inside outer dictionaries.  It is an error to try to get a key that doesn't exist.
+### get()
 
-t.td_get() supports our to=datatype technique to get the contents of the tclobj as one of numbers different datatypes (the same ones supported for tohil.getvar, etc.)
+Likewise, `get` will accept a list of keys, treating the tcl object as
+a nested tree of dictionaries, with inner dictionaries stored as values
+inside outer dictionaries.  It is an error to try to get a key that
+doesn't exist.
 
-One kind of annoyance about tcl dicts is having to use dict exist to traverse the hierarchy of dicts to see if something exists before traversing it a second time to actually get it, or to try the get and catch the error.
+t.get() supports our to=datatype technique to get the contents of the
+tcldict as one of numbers different datatypes (the same ones supported
+for tohil.getvar, etc.)
 
-td_get offers a second approach, where you do the td_get and specify the optional default= argument, where you specify a value that td_get will return if the requested key doesn't exist. 
+One kind of annoyance about tcl dicts is having to use dict exist to
+traverse the hierarchy of dicts to see if something exists before
+traversing it a second time to actually get it, or to try the get
+and catch the error.
 
-If a to=datatype is specified, the default value is coerced to that datatype if possible, or an exception is raised if not.
+get() offers a second approach, where you invoke get() and specify
+the optional default= argument, where you specify a value that get
+will return if the requested key doesn't exist. 
 
-### td_exists()
+If a to=datatype is specified, the default value is coerced to that
+datatype if possible, or an exception is raised if not.
 
-`td_exists` can be used to see if a key exists, and also accepts a list of keys to access a hierarchy.
+### checking for existence
 
-### td_size()
+You can do the usual python `'a' in mydict` check for existence.
 
-t.td_size() returns the size of the tcl dict, or throws an error if the contents of the object can't be treated as a tcl dict.
+The thing being checked for can be a list, in order to navigate
+a hierarchy of tcl dictionaries.  For example, `["airport", "KHOU"] in mydict`
 
-### td_remove()
+### len()
 
-x.td_remove() removes an element from the tcl dict.  It's not an error to remove something that doesn't exist.
+len(t) returns the size of the tcl dict, or throws an error if the contents of the object can't be treated as a tcl dict.
 
-td_remove can also accept a list of elements and in that case it will delete a hierarchy of subordinate namespaces.  In the list case, if more than one element is specified in the list, it is an error if any of the keys don't exist.
+### removing
 
-You can create new tclobjs as the contents of sub-parts of dictionaries and use them as dictionaries, or whatever.
+Standard `del t[key]` python stuff.
 
-Say you have a tclobj t containing a dictionary of elements, one of which, 'a', contains a dictionary of elements, one of which, 'c', contains a dictionary of elements, 'd'.
+td_remove can also accept a list of elements and in that case it will delete a hierarchy of subordinate tcl dictionaries.  In the list case, if more than one element is specified in the list, it is an error if any of the keys don't exist.
+
+You can create new tclobjs or tcldicts as the contents of sub-parts of dictionaries and use them as dictionaries in their own right, or whatever.
+
+Say you have a tcldict t containing a dictionary of elements, one of which, 'a', contains a dictionary of elements, one of which, 'c', contains a dictionary of elements, 'd'.
 
 If you want a dictionary consisting of eveyrthing below c, you might do
 
 ```
-x = t.td_get(['a','b','c'], to=tohil.tclobj)
+x = t[['a', 'b', 'c']]
+```
+
+...or...
+
+```
+x = t.get(['a','b','c'], to=tohil.tclobj)
 ```
 
-Likewise you can compose a more complicated dictionaries by attaching a dictionary to a point within another dictionary, simply by doing a td_set with the value being a tclobj that itself contains a dictionary.
+Likewise you can compose a more complicated dictionaries by attaching a dictionary to a point within another dictionary, simply by assigning a tclobj or tcldict that itself contains a dictionary.
 
-#### TD iterators
+#### iterators
 
-You can iterate over a TD with td_iter. For example, something like:
+You can iterate over a tcldict with normal python semantics.
+For example, something like:
 
 ```
-t = tohil.tclobj("a 1 b 2 c 3 d 4 e 5 f 6")
-for i in t.td_iter():
+t = tohil.tcldict("a 1 b 2 c 3 d 4 e 5 f 6")
+for i in t:
     print(i)
 ```
 
-If you pass a to= conversion to td_iter, the iterator returns tuples comprising the key and the value as well, with the value converted to the to= conversion.
+If you pass a to= conversion to iter, the iterator returns tuples comprising the key and the value as well, with the value converted to the to= conversion.
 
 ```
-for key, value in t.td_iter(to=int):
+for key, value in t.iter(to=int):
     print(f"key {key} value {value}")
 ```
 
@@ -203,24 +246,24 @@ x = tohil.eval("list 1 2 3 4 5", to=tohil.tclobj)
 
 x.lindex(0)
 
-x.refcount()
+x.refcount
 
 x.setvar("foo")
 
-x.refcount()
+x.refcount
 
 x.unset("foo")
 
-x.refcount()
+x.refcount
 
 str(x)
 
 y = x
 
-y.refcount()
+y.refcount
 ```
 
-You can create a tclobj from most python stuff.
+You can create a tclobj or tcldict from most python stuff.
 
 a list:
 
diff --git a/Tcl-From-Python.md b/Tcl-From-Python.md
index 41bc61b..1cee0be 100644
--- a/Tcl-From-Python.md
+++ b/Tcl-From-Python.md
@@ -1,63 +1,92 @@
 
 
-The use case is you have a lot of valuable Tcl code that works that you're
-not crazy enough to try to get rid of, but you want to be able to use python
-for web stuff and/or backend stuff, get access to python and all the libraries
-available for it, the benefits of the much larger user community, greater 
-acceptance among the developers (possibly your employees) and the larger
-talent pool, etc.
-
-It's not going to work to rewrite.  You can't.  THere are too many
-customers that are already using what you've got, and there are bug
-fixes and improvements that they're waiting on.  You're going to retask some
-considerably proportion of your staff to rewrite years to bazillions
-of years of work, and almost for sure you're going to go out of business.
-You're going to go out of business because your rewrite is going to fail.
-Your rewrite will regress, recreate tons of bugs that were already fixed
-in the old stuff, cause trouble tickets, maintenance, tl;dr you're not
-going to like it.
-
-So you have a ton of Tcl code and you want to get to python.  What's it
-going to take to do that?
-
-You're gonna need Python and Tcl to be fluent with each other.
-
-What does "fluent" mean?  From my dictionary... fluent - adjective - ...able
-to express oneself easily and articulately... able to speak or write a
-particular foreign language easily and accurately... spoken accurately
-and with facility... smoothly graceful and easy... abole to flow freely, fluid.
-
-Wow, yeah, that's what you want, even the bit about a being able to
-speak or write a particular foreign language easily and accurately.
-
-How do we interface between Tcl and python in a way that is fluent?
-
-Probably the first answer, to people familiar with the C interfaces
-of python or tcl or people who think in particular about interpreted
-languages, is to provide a way to invoke "eval", some function that
-takes whatever it is given and evaluates it, in this case, evaluate
-tcl code from python.
-
-And indeed that is useful, and helpful, perhaps essential.  And we
-provide that... but it doesn't get you there.  The big problem is how
-data will be expressed as part of the evaluated code, and it is difficult
-to ensure that the data is fully properly quoted, that some data containing
-interpreter metacharacters will inadvertently trigger a malfunction in the
-code or make it vulnerable to an injection attack.
-
-So "eval" is very useful, but it's not enough.
-
-OK, we can take it further, upon examination, by providing a way to call
-one language from another, somehow specifying each argument explicitly,
-where even if the arguments contain metacharacters we can ensure they
-are not evaluated by the other interpreter because when invoked with explicit
-arguments, the arguments are not evaluated.  This can be done both for
-calling tcl from python and for calling python from tcl, as it would need
-to be.
-
-And we provide that...  And it's really handy.  But since you have to
-explicitly invoke this "call" function every time you want to call out to
-tcl from python and to python from tcl, we wouldn't call that fluent.
+
+## I want to get up and running as quickly as possible, accessing my tcl stuff from python.
+
+Great!
+
+Tohil gives you a few options for how to run tcl code and access tcl data from python.
+
+You can pick and choose what you need, and how far you want to go.
+
+After building and installing tohil, from python you should be able
+to `import tohil` from the python command line.
+
+### tohil.eval
+
+The simplest way to do something with tcl from python, is to
+use `tohil.eval`.
+
+```
+import tohil
+tohil.eval("package require Tclx")
+```
+
+You can call tohil.eval as much as you want, and feed it any tcl
+code, and tcl will evaluate it, and return the result.
+If a tcl error occurs and it isn't caught by "try" or "catch",
+tohil will raise a python TclError exception that contains all
+the stuff tcl knows about the error.
+
+Pretty cool.  Very handy.  You'll probably use it some, and for some
+people it may be all that they need.
+
+But if we get to the point where we're trying to embed data into the
+stuff we're passing to "eval", like say with f-strings or something...
+
+```
+tohil.eval(f"""set users({user}) [list name "{name}" address "{address}" phone "{phone}"]""")
+```
+
+^ don't do this!
+
+...we are asking for trouble.  Dollar signs, double quotes, curly brackets,
+square brackets in the data, the tcl interpreter will try to interpret it.
+
+You can do some heavy lifting to try to make sure that the data if properly
+quoted, but that's pretty hard and kind of error prone and if you miss
+running your data through your conditioner anywhere, you've got the risk
+of the problem again.
+
+```
+>>> tohil.package_require("clock::rfc2822")
+'0.1'
+>>> clock = tohil.eval('::clock::rfc2822::parse_date "Wed, 14 Apr 2021 12:04:48 -0500"')
+>>> clock
+'1618419888'
+```
+
+### tohil.call
+
+tohil.call is a way to call tcl with each argument specified explicitly.
+
+This way, even if the arguments contain metacharacters we can ensure they
+are not evaluated by tcl because when invoked this way, tcl will not
+evaluate tohil.call's arguments.
+
+```
+>>> user = "leon"
+>>> name = "Leon Kowalski"
+>>> address = "1187 Unterwasser"
+>>> phone = "+1-415-555-2822"
+>>> tohil.call("set", f"users({user})", tohil.call("list", "name", name, "address", address, "phone", phone))
+'name {Leon Kowalski} address {1187 Unterwasser} phone +1-415-555-2822'
+```
+
+
+```
+>>> clock = tohil.call('::clock::rfc2822::parse_date', "Wed, 14 Apr 2021 12:04:48 -0500")
+>>> clock
+'1618419888'
+```
+
+There are tcl-side equivalents to these things for calling python from
+tcl, by the way.
+
+So that's handy.
+
+But since you have to explicitly invoke this "call" function when
+you want to call, that can get old pretty quick.
 
 "Fluent" would be where the tcl commands look and act like python commands
 to a very high degree.
diff --git a/configure.ac b/configure.ac
index c04f34d..70c55ee 100644
--- a/configure.ac
+++ b/configure.ac
@@ -18,7 +18,7 @@ dnl	to configure the system for the local environment.
 # so that we create the export library with the dll.
 #-----------------------------------------------------------------------
 
-AC_INIT([tohil],[3.0.0])
+AC_INIT([tohil],[3.1.0])
 
 #--------------------------------------------------------------------
 # Call TEA_INIT as the first TEA_ macro to set up initial vars.
diff --git a/generic/tohil.c b/generic/tohil.c
index 9320d03..41e4c33 100644
--- a/generic/tohil.c
+++ b/generic/tohil.c
@@ -30,13 +30,19 @@
 // methods and functions that implement the type.
 typedef struct {
     PyObject_HEAD;
+    PyTypeObject *to;
     Tcl_Obj *tclobj;
 } PyTclObj;
 
 int PyTclObj_Check(PyObject *pyObj);
+int TohilTclDict_Check(PyObject *pyObj);
 static PyTypeObject PyTclObjType;
 
-PyObject *tohil_python_return(Tcl_Interp *, int tcl_result, PyObject *toType, Tcl_Obj *resultObj);
+int TohilTclDict_Check(PyObject *pyObj);
+static PyTypeObject TohilTclDictType;
+static PyObject *TohilTclDict_FromTclObj(Tcl_Obj *obj);
+
+PyObject *tohil_python_return(Tcl_Interp *, int tcl_result, PyTypeObject *toType, Tcl_Obj *resultObj);
 
 // TCL library begins here
 
@@ -332,6 +338,7 @@ _pyObjToTcl(Tcl_Interp *interp, PyObject *pObj)
      * - None -> {}
      * - True -> 1, False -> 0
      * - tclobj -> tclobj
+     * - tcldict -> tcldict
      * - bytes -> tcl byte string
      * - unicode -> tcl unicode string
      * - number protocol -> tcl number
@@ -347,7 +354,7 @@ _pyObjToTcl(Tcl_Interp *interp, PyObject *pObj)
         tObj = Tcl_NewObj();
     } else if (pObj == Py_True || pObj == Py_False) {
         tObj = Tcl_NewBooleanObj(pObj == Py_True);
-    } else if (PyTclObj_Check(pObj)) {
+    } else if (PyTclObj_Check(pObj) || TohilTclDict_Check(pObj)) {
         PyTclObj *pyTclObj = (PyTclObj *)pObj;
         tObj = pyTclObj->tclobj;
     } else if (PyBytes_Check(pObj)) {
@@ -466,6 +473,8 @@ pyObjToTcl(Tcl_Interp *interp, PyObject *pObj)
     // error return or some kind, or, if necessary, do checks
     // everywhere in the code that currently assumes pyObjToTcl can't fail
     assert(ret != NULL);
+    if (ret == NULL)
+        abort();
     return ret;
 }
 
@@ -815,6 +824,7 @@ PyTclObj_FromTclObj(Tcl_Obj *obj)
     PyTclObj *self = (PyTclObj *)PyTclObjType.tp_alloc(&PyTclObjType, 0);
     if (self != NULL) {
         self->tclobj = obj;
+        self->to = NULL;
         Tcl_IncrRefCount(obj);
     }
     return (PyObject *)self;
@@ -823,25 +833,41 @@ PyTclObj_FromTclObj(Tcl_Obj *obj)
 //
 // create a new python tclobj object
 //
-// currently just creates an empty object but could use args
-// and keywords to do other interesting stuff?
+// creates an empty object if no argument is given, but if an
+// argument is provided, performs pyObjToTcl conversion on it
+// and makes the new tclobj object point to that
 //
 static PyObject *
 PyTclObj_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
 {
     PyObject *pSource = NULL;
-    static char *kwlist[] = {"from", NULL};
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &pSource))
+    PyObject *toType = NULL;
+    static char *kwlist[] = {"from", "to", NULL};
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O$O", kwlist, &pSource, &toType)) {
         return NULL;
+    }
+
+    if (toType != NULL) {
+        if (!PyType_Check(toType)) {
+            PyErr_SetString(PyExc_RuntimeError, "to type is not a valid python data type");
+            return NULL;
+        }
+    }
 
     PyTclObj *self = (PyTclObj *)type->tp_alloc(type, 0);
     if (self != NULL) {
         if (pSource == NULL) {
-            self->tclobj = Tcl_NewObj();
+            if (strcmp(type->tp_name, "tohil.tcldict") == 0) {
+                self->tclobj = Tcl_NewDictObj();
+            } else {
+                self->tclobj = Tcl_NewObj();
+            }
         } else {
             self->tclobj = pyObjToTcl(tcl_interp, pSource);
         }
         Tcl_IncrRefCount(self->tclobj);
+        self->to = (PyTypeObject *)toType;
+        Py_XINCREF(toType);
     }
     return (PyObject *)self;
 }
@@ -850,6 +876,7 @@ static void
 PyTclObj_dealloc(PyTclObj *self)
 {
     Tcl_DecrRefCount(self->tclobj);
+    Py_XDECREF(self->to);
     Py_TYPE(self)->tp_free((PyObject *)self);
 }
 
@@ -953,13 +980,15 @@ PyTclObj_richcompare(PyTclObj *self, PyObject *other, int op)
 }
 
 //
-// tclobj.reset()
+// tclobj.reset() - reset a tclobj or tcldict to an empty tcl object
 //
 static PyObject *
 PyTclObj_reset(PyTclObj *self, PyObject *pyobj)
 {
     Tcl_DecrRefCount(self->tclobj);
     self->tclobj = Tcl_NewObj();
+    Py_XDECREF(self->to);
+    self->to = NULL;
     Tcl_IncrRefCount(self->tclobj);
     Py_RETURN_NONE;
 }
@@ -1075,6 +1104,15 @@ PyTclObj_as_tclobj(PyTclObj *self, PyObject *pyobj)
     return PyTclObj_FromTclObj(self->tclobj);
 }
 
+//
+// tclobj.as_tcldict()
+//
+static PyObject *
+PyTclObj_as_tcldict(PyTclObj *self, PyObject *pyobj)
+{
+    return TohilTclDict_FromTclObj(self->tclobj);
+}
+
 //
 // tclobj.as_byte_array()
 //
@@ -1086,6 +1124,20 @@ PyTclObj_as_byte_array(PyTclObj *self, PyObject *pyobj)
     return PyByteArray_FromStringAndSize((const char *)byteArray, size);
 }
 
+void PyTclObj_dup_if_shared(PyTclObj *self)
+{
+    if (!Tcl_IsShared(self->tclobj)) {
+        return;
+    }
+
+    // decrement the old object.  It's safe because refcount
+    // must be 2 or more.  then duplicate and increment
+    // the new, duplicated object's 0 refcount to 1
+    Tcl_DecrRefCount(self->tclobj);
+    self->tclobj = Tcl_DuplicateObj(self->tclobj);
+    Tcl_IncrRefCount(self->tclobj);
+}
+
 //
 // tclobj.incr() - increment a python tclobj object
 //
@@ -1107,135 +1159,11 @@ PyTclObj_incr(PyTclObj *self, PyObject *args, PyObject *kwargs)
 
     longValue += increment;
 
-    if (Tcl_IsShared(self->tclobj)) {
-        self->tclobj = Tcl_DuplicateObj(self->tclobj);
-    }
+    PyTclObj_dup_if_shared(self);
     Tcl_SetLongObj(self->tclobj, longValue);
     return PyLong_FromLong(longValue);
 }
 
-//
-// td - tcl dict stuff
-//
-
-//
-// td_locate(key) - do a dict get on the tclobj object using
-//   either a list of keys or a singleton key, and return
-//   the target value object.
-//
-//   internal routine used by td_get and td_exists
-//
-static Tcl_Obj *
-PyTclObj_td_locate(PyTclObj *self, PyObject *keys)
-{
-    Tcl_Obj *keyObj = NULL;
-    Tcl_Obj *valueObj = NULL;
-
-    if (PyList_Check(keys)) {
-        int i;
-        Tcl_Obj *dictPtrObj = self->tclobj;
-        Py_ssize_t nKeys = PyList_GET_SIZE(keys);
-
-        for (i = 0; i < nKeys; i++) {
-            PyObject *keyPyObj = PyList_GET_ITEM(keys, i);
-            keyObj = pyObjToTcl(tcl_interp, keyPyObj);
-
-            if (Tcl_DictObjGet(tcl_interp, dictPtrObj, keyObj, &valueObj) == TCL_ERROR) {
-                Tcl_DecrRefCount(keyObj);
-                PyErr_SetString(PyExc_TypeError, "tclobj contents cannot be converted into a td");
-                return NULL;
-            }
-            if (valueObj == NULL) {
-                Tcl_DecrRefCount(keyObj);
-                return NULL;
-            }
-            dictPtrObj = valueObj;
-            Tcl_DecrRefCount(keyObj);
-        }
-
-        // at this point if there's been no error valueObj has our guy
-    } else {
-        // it's a singleton
-        Tcl_Obj *keyObj = pyObjToTcl(tcl_interp, keys);
-
-        if (Tcl_DictObjGet(NULL, self->tclobj, keyObj, &valueObj) == TCL_ERROR) {
-            Tcl_DecrRefCount(keyObj);
-            PyErr_SetString(PyExc_TypeError, "tclobj contents cannot be converted into a td");
-            return NULL;
-        }
-        Tcl_DecrRefCount(keyObj);
-    }
-
-    return valueObj;
-}
-
-//
-// td_get(key) - do a dict get on the tcl object
-//
-static PyObject *
-PyTclObj_td_get(PyTclObj *self, PyObject *args, PyObject *kwargs)
-{
-    static char *kwlist[] = {"key", "to", "default", NULL};
-    PyObject *keys = NULL;
-    PyObject *to = NULL;
-    PyObject *pDefault = NULL;
-
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|$OO", kwlist, &keys, &to, &pDefault)) {
-        return NULL;
-    }
-
-    Tcl_Obj *valueObj = PyTclObj_td_locate(self, keys);
-    if (valueObj == NULL) {
-        if (pDefault != NULL) {
-            if (to == NULL) {
-                // not there but they provided a default,
-                // give them their default
-                Py_INCREF(pDefault);
-                return pDefault;
-            } else {
-                valueObj = pyObjToTcl(tcl_interp, pDefault);
-            }
-        } else {
-            // not there, no default.  it's an error.
-            // this is clean and the way python does it.
-            PyErr_SetObject(PyExc_KeyError, keys);
-            return NULL;
-        }
-    }
-
-    return tohil_python_return(tcl_interp, TCL_OK, to, valueObj);
-}
-
-//
-// td_exists(key) - do a dict get on a key or list of keys
-//   against our tclobj object and return true if the key's there
-//   and false if it isn't
-//
-static PyObject *
-PyTclObj_td_exists(PyTclObj *self, PyObject *args, PyObject *kwargs)
-{
-    static char *kwlist[] = {"key", NULL};
-    PyObject *keys = NULL;
-
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &keys)) {
-        return NULL;
-    }
-
-    Tcl_Obj *valueObj = PyTclObj_td_locate(self, keys);
-    if (valueObj == NULL) {
-        // see if an error occurred -- there is a difference between
-        // not finding something (valueObj == NULL) and not finding
-        // something (same) and there having been an error
-        if (PyErr_Occurred() == NULL) {
-            // an error didn't occur
-            Py_RETURN_FALSE;
-        }
-        return NULL;
-    }
-
-    Py_RETURN_TRUE;
-}
-
 //
 // convert a python list into a tcl c-level objv and objc
 //
@@ -1277,240 +1205,108 @@ pyListToObjv_teardown(int objc, Tcl_Obj **objv)
 }
 
 //
-// td_size() - return the dict size of a python tclobj's tcl object
-//   exception thrown if tcl object isn't a proper tcl dict
+// llength - return the length of a python tclobj's tcl object
+//   as a list.  exception thrown if tcl object isn't a list
 //
 static PyObject *
-PyTclObj_td_size(PyTclObj *self, PyObject *pyobj)
+PyTclObj_llength(PyTclObj *self, PyObject *pyobj)
 {
     int length;
-    if (Tcl_DictObjSize(tcl_interp, self->tclobj, &length) == TCL_OK) {
+    if (Tcl_ListObjLength(tcl_interp, self->tclobj, &length) == TCL_OK) {
         return PyLong_FromLong(length);
     }
-    PyErr_SetString(PyExc_TypeError, "tclobj contents cannot be converted into a td");
+    PyErr_SetString(PyExc_RuntimeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
     return NULL;
 }
 
 //
-// td_remove(key) - if key is a python list, do a dict remove keylist on the tcl object,
-//   where the arg is a python list of a hierarchy of names to remove.
-//
-//   if key is not a python list, does a dict remove on the tcl object for that key
+// getvar - set python tclobj to contain the value of a tcl var
 //
 static PyObject *
-PyTclObj_td_remove(PyTclObj *self, PyObject *args, PyObject *kwargs)
+PyTclObj_getvar(PyTclObj *self, PyObject *var)
 {
-    static char *kwlist[] = {"key", NULL};
-    PyObject *keys = NULL;
-
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &keys)) {
+    char *varString = (char *)PyUnicode_1BYTE_DATA(var);
+    Tcl_Obj *newObj = Tcl_GetVar2Ex(tcl_interp, varString, NULL, (TCL_LEAVE_ERR_MSG));
+    if (newObj == NULL) {
+        PyErr_SetString(PyExc_NameError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
         return NULL;
     }
+    Tcl_DecrRefCount(self->tclobj);
+    self->tclobj = newObj;
+    Tcl_IncrRefCount(self->tclobj);
+    Py_RETURN_NONE;
+}
 
-    if (PyList_Check(keys)) {
-        int objc = 0;
-        Tcl_Obj **objv = NULL;
-
-        // build up a tcl objv of the keys
-        pyListToTclObjv((PyListObject *)keys, &objc, &objv);
-
-        // we are about to try to modify the object, so if it's shared we need to copy
-        if (Tcl_IsShared(self->tclobj)) {
-            self->tclobj = Tcl_DuplicateObj(self->tclobj);
-        }
-
-        int status = (Tcl_DictObjRemoveKeyList(tcl_interp, self->tclobj, objc, objv));
-
-        // tear down the objv of the keys we created
-        pyListToObjv_teardown(objc, objv);
-
-        if (status == TCL_ERROR) {
-            PyErr_SetString(PyExc_KeyError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
-            return NULL;
-        }
-    } else {
-        Tcl_Obj *keyObj = _pyObjToTcl(tcl_interp, keys);
-
-        if (keyObj == NULL) {
-            PyErr_SetString(PyExc_RuntimeError, "unable to fashion argument into a string to be used as a dictionary key");
-            return NULL;
-        }
-
-        // we are about to try to modify the object, so if it's shared we need to copy
-        if (Tcl_IsShared(self->tclobj)) {
-            self->tclobj = Tcl_DuplicateObj(self->tclobj);
-        }
+//
+// setvar - set set tcl var to point to the tcl object of a python tclobj
+//
+static PyObject *
+PyTclObj_setvar(PyTclObj *self, PyObject *var)
+{
+    char *varString = (char *)PyUnicode_1BYTE_DATA(var);
+    // setvar handles incrementing the reference count
+    if (Tcl_SetVar2Ex(tcl_interp, varString, NULL, self->tclobj, (TCL_LEAVE_ERR_MSG)) == NULL) {
+        PyErr_SetString(PyExc_RuntimeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
+        return NULL;
+    }
+    Py_RETURN_NONE;
+}
 
-        if (Tcl_DictObjRemove(NULL, self->tclobj, keyObj) == TCL_ERROR) {
-            Tcl_DecrRefCount(keyObj);
-            PyErr_SetString(PyExc_TypeError, "tclobj contents cannot be converted into a td");
-            return NULL;
-        }
-        Tcl_DecrRefCount(keyObj);
+//
+// set - tclobj type set method can set an object to a lot
+// of possible python stuff -- NB there must be a better way
+//
+static PyObject *
+PyTclObj_set(PyTclObj *self, PyObject *pyObject)
+{
+    Tcl_Obj *newObj = pyObjToTcl(tcl_interp, pyObject);
+    if (newObj == NULL) {
+        return NULL;
     }
+    Tcl_DecrRefCount(self->tclobj);
+    self->tclobj = newObj;
+    Tcl_IncrRefCount(self->tclobj);
 
     Py_RETURN_NONE;
 }
 
 //
-// tclobj.td_set(key, value) - do a dict set on the tcl object
-//   if key is a python list, td_set operates on a nested tree
-//   of dictionaries
+// lindex - obtain the n'th element of tclobj as a tcl list.
+//
+// to=type can be used to control what python type is returned.
 //
 static PyObject *
-PyTclObj_td_set(PyTclObj *self, PyObject *args, PyObject *kwargs)
+PyTclObj_lindex(PyTclObj *self, PyObject *args, PyObject *kwargs)
 {
-    static char *kwlist[] = {"key", "value", NULL};
-    PyObject *keys = NULL;
-    PyObject *pValue = NULL;
+    static char *kwlist[] = {"index", "to", NULL};
+    PyTypeObject *to = NULL;
+    int index = 0;
+    int length = 0;
 
-    // remember, "O" sets our pointer to the object without incrementing its reference count
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|$", kwlist, &keys, &pValue)) {
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|$O", kwlist, &index, &to))
         return NULL;
-    }
 
-    Tcl_Obj *valueObj = pyObjToTcl(tcl_interp, pValue);
-    if (valueObj == NULL) {
+    if (Tcl_ListObjLength(tcl_interp, self->tclobj, &length) == TCL_ERROR) {
+        PyErr_SetString(PyExc_TypeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
         return NULL;
     }
 
-    // we are about to try to modify the object, so if it's shared we need to copy
-    if (Tcl_IsShared(self->tclobj)) {
-        self->tclobj = Tcl_DuplicateObj(self->tclobj);
+    if (index < 0 || index >= length) {
+        PyErr_SetString(PyExc_IndexError, "list index out of range");
+        return NULL;
     }
 
-    if (PyList_Check(keys)) {
-        int objc;
-        Tcl_Obj **objv;
+    Tcl_Obj *resultObj = NULL;
+    if (Tcl_ListObjIndex(tcl_interp, self->tclobj, index, &resultObj) == TCL_ERROR) {
+        PyErr_SetString(PyExc_TypeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
+        return NULL;
+    }
 
-        // build up a tcl objv of the keys
-        pyListToTclObjv((PyListObject *)keys, &objc, &objv);
+    if (to == NULL && self->to != NULL)
+        to = self->to;
 
-        int status = (Tcl_DictObjPutKeyList(tcl_interp, self->tclobj, objc, objv, valueObj));
-
-        // tear down the objv of the keys we created
-        pyListToObjv_teardown(objc, objv);
-
-        if (status == TCL_ERROR) {
-            Tcl_DecrRefCount(valueObj);
-            PyErr_SetString(PyExc_RuntimeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
-            return NULL;
-        }
-    } else {
-        Tcl_Obj *keyObj = _pyObjToTcl(tcl_interp, keys);
-
-        if (keyObj == NULL) {
-            PyErr_SetString(PyExc_RuntimeError, "unable to fashion argument into a string to be used as a dictionary key");
-            return NULL;
-        }
-
-        if (Tcl_DictObjPut(tcl_interp, self->tclobj, keyObj, valueObj) == TCL_ERROR) {
-            Tcl_DecrRefCount(keyObj);
-            Tcl_DecrRefCount(valueObj);
-            PyErr_SetString(PyExc_TypeError, "tclobj contents cannot be converted into a td");
-            return NULL;
-        }
-        // something about this is a crash
-        // Tcl_DecrRefCount(keyObj);
-        // Tcl_DecrRefCount(valueObj);
-    }
-
-    Py_RETURN_NONE;
-}
-
-//
-// llength - return the length of a python tclobj's tcl object
-//   as a list.  exception thrown if tcl object isn't a list
-//
-static PyObject *
-PyTclObj_llength(PyTclObj *self, PyObject *pyobj)
-{
-    int length;
-    if (Tcl_ListObjLength(tcl_interp, self->tclobj, &length) == TCL_OK) {
-        return PyLong_FromLong(length);
-    }
-    PyErr_SetString(PyExc_RuntimeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
-    return NULL;
-}
-
-//
-// getvar - set python tclobj to contain the value of a tcl var
-//
-static PyObject *
-PyTclObj_getvar(PyTclObj *self, PyObject *var)
-{
-    char *varString = (char *)PyUnicode_1BYTE_DATA(var);
-    Tcl_Obj *newObj = Tcl_GetVar2Ex(tcl_interp, varString, NULL, (TCL_LEAVE_ERR_MSG));
-    if (newObj == NULL) {
-        PyErr_SetString(PyExc_NameError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
-        return NULL;
-    }
-    Tcl_DecrRefCount(self->tclobj);
-    self->tclobj = newObj;
-    Tcl_IncrRefCount(self->tclobj);
-    Py_RETURN_NONE;
-}
-
-//
-// setvar - set set tcl var to point to the tcl object of a python tclobj
-//
-static PyObject *
-PyTclObj_setvar(PyTclObj *self, PyObject *var)
-{
-    char *varString = (char *)PyUnicode_1BYTE_DATA(var);
-    // setvar handles incrementing the reference count
-    if (Tcl_SetVar2Ex(tcl_interp, varString, NULL, self->tclobj, (TCL_LEAVE_ERR_MSG)) == NULL) {
-        PyErr_SetString(PyExc_RuntimeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
-        return NULL;
-    }
-    Py_RETURN_NONE;
-}
-
-// set - tclobj type set method can set an object to a lot
-// of possible python stuff -- NB there must be a better way
-static PyObject *
-PyTclObj_set(PyTclObj *self, PyObject *pyObject)
-{
-    Tcl_Obj *newObj = pyObjToTcl(tcl_interp, pyObject);
-    if (newObj == NULL) {
-        return NULL;
-    }
-    Tcl_DecrRefCount(self->tclobj);
-    self->tclobj = newObj;
-    Tcl_IncrRefCount(self->tclobj);
-
-    Py_RETURN_NONE;
-}
-
-static PyObject *
-PyTclObj_lindex(PyTclObj *self, PyObject *args, PyObject *kwargs)
-{
-    static char *kwlist[] = {"index", "to", NULL};
-    PyObject *to = NULL;
-    int index = 0;
-    int length = 0;
-
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|$O", kwlist, &index, &to))
-        return NULL;
-
-    if (Tcl_ListObjLength(tcl_interp, self->tclobj, &length) == TCL_ERROR) {
-        PyErr_SetString(PyExc_TypeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
-        return NULL;
-    }
-
-    if (index < 0 || index >= length) {
-        PyErr_SetString(PyExc_IndexError, "list index out of range");
-        return NULL;
-    }
-
-    Tcl_Obj *resultObj = NULL;
-    if (Tcl_ListObjIndex(tcl_interp, self->tclobj, index, &resultObj) == TCL_ERROR) {
-        PyErr_SetString(PyExc_TypeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
-        return NULL;
-    }
-
-    return tohil_python_return(tcl_interp, TCL_OK, to, resultObj);
-}
+    return tohil_python_return(tcl_interp, TCL_OK, to, resultObj);
+}
 
 //
 // lappend something to the tclobj
@@ -1524,7 +1320,10 @@ PyTclObj_lappend(PyTclObj *self, PyObject *pObject)
     }
 
     // we are about to modify the object so if it's shared we need to copy
+    // NB i think we don't increment here because ListObjAppendElement
+    // will do it for us.
     if (Tcl_IsShared(self->tclobj)) {
+        Tcl_DecrRefCount(self->tclobj);
         self->tclobj = Tcl_DuplicateObj(self->tclobj);
     }
 
@@ -1555,7 +1354,10 @@ PyTclObj_lappend_list(PyTclObj *self, PyObject *pObject)
     if (PyTclObj_Check(pObject)) {
         Tcl_Obj *appendListObj = ((PyTclObj *)pObject)->tclobj;
 
+        // NB i think we don't increment here because ListObjAppendList
+        // will do it for us.
         if (Tcl_IsShared(self->tclobj)) {
+            Tcl_DecrRefCount(self->tclobj);
             self->tclobj = Tcl_DuplicateObj(self->tclobj);
         }
 
@@ -1577,6 +1379,7 @@ PyTclObj_lappend_list(PyTclObj *self, PyObject *pObject)
         pyListToObjv_teardown(objc, objv);
 
         if (Tcl_IsShared(self->tclobj)) {
+            Tcl_DecrRefCount(self->tclobj);
             self->tclobj = Tcl_DuplicateObj(self->tclobj);
         }
 
@@ -1594,12 +1397,24 @@ PyTclObj_lappend_list(PyTclObj *self, PyObject *pObject)
     Py_RETURN_NONE;
 }
 
+//
+// PyTclObj_refcount - return the reference count of the
+//   tclobj or tcldict's internal tcl object
+//
 static PyObject *
 PyTclObj_refcount(PyTclObj *self, PyObject *dummy)
 {
     return PyLong_FromLong(self->tclobj->refCount);
 }
 
+//
+// PyTclObj_type - return the internal tcl data type of
+//   the corresponding tclobj/tcldict python type
+//
+//   this can use useful but it's not canonical - the type
+//   may not have been set to anything if the string therein
+//   hasn't been used as a list, dict, etc.
+//
 static PyObject *
 PyTclObj_type(PyTclObj *self, PyObject *dummy)
 {
@@ -1620,8 +1435,12 @@ PyTclObjIter(PyObject *self)
 
 static PyObject *PyTclObj_subscript(PyTclObj *, PyObject *);
 
-// slice stuff significantly cribbed from cpython source for listobjects...
-
+//
+// PyTclObj_slice - return a python list containing a slice
+//   of the referenced tclobj as a list.
+//
+// significantly cribbed from cpython source for listobjects...
+//
 static PyObject *
 PyTclObj_slice(PyTclObj *self, Py_ssize_t ilow, Py_ssize_t ihigh)
 {
@@ -1657,12 +1476,16 @@ PyTclObj_slice(PyTclObj *self, Py_ssize_t ilow, Py_ssize_t ihigh)
     for (i = 0; i < len; i++) {
         // create a new tclobj object and store
         // it into the python list we are making
-        PyObject *v = PyTclObj_FromTclObj(src[i]);
+        PyObject *v = tohil_python_return(tcl_interp, TCL_OK, self->to, src[i]);
         PyList_SET_ITEM(np, i, v);
     }
     return (PyObject *)np;
 }
 
+//
+// PyTclObj_item - return the i'th element of a tclobj containing
+//   a list, or set an error and return NULL if something's wrong
+//
 static PyObject *
 PyTclObj_item(PyTclObj *self, Py_ssize_t i)
 {
@@ -1686,11 +1509,14 @@ PyTclObj_item(PyTclObj *self, Py_ssize_t i)
         return NULL;
     }
 
-    PyObject *ret = PyTclObj_FromTclObj(listObjv[i]);
+    PyObject *ret = tohil_python_return(tcl_interp, TCL_OK, self->to, listObjv[i]);
     Py_INCREF(ret);
     return ret;
 }
 
+//
+// PyTclObj_ass_item - assign an item into a tclobj containing a list
+//
 static int
 PyTclObj_ass_item(PyTclObj *self, Py_ssize_t i, PyObject *v)
 {
@@ -1719,6 +1545,7 @@ PyTclObj_ass_item(PyTclObj *self, Py_ssize_t i, PyObject *v)
 
     // we are about to modify the object so if it's shared we need to copy
     if (Tcl_IsShared(self->tclobj)) {
+        Tcl_DecrRefCount(self->tclobj);
         self->tclobj = Tcl_DuplicateObj(self->tclobj);
     }
 
@@ -1729,6 +1556,13 @@ PyTclObj_ass_item(PyTclObj *self, Py_ssize_t i, PyObject *v)
     return 0;
 }
 
+//
+// PyTclObj_length - return the length of the tclobj, looking at it
+//   as a tcl list.  returns -1 if string cannot be represented as
+//   a list, or 0 or larger, representing the number of elements
+//   in the list.
+//
+//
 static Py_ssize_t
 PyTclObj_length(PyTclObj *self, Py_ssize_t i)
 {
@@ -1736,12 +1570,17 @@ PyTclObj_length(PyTclObj *self, Py_ssize_t i)
 
     if (Tcl_ListObjLength(tcl_interp, self->tclobj, &size) == TCL_ERROR) {
         PyErr_SetString(PyExc_TypeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
-        return 0;
+        return -1;
     }
 
     return size;
 }
 
+//
+// PyTclObj_subscript - subscript a tclobj.  we treat the tclobj
+//   as a list, so item can be an integer (to obtain a single element),
+//   or a slice (to obtain zero or more elements.)
+//
 static PyObject *
 PyTclObj_subscript(PyTclObj *self, PyObject *item)
 {
@@ -1770,7 +1609,7 @@ PyTclObj_subscript(PyTclObj *self, PyObject *item)
             PyErr_SetString(PyExc_TypeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
             return NULL;
         }
-        return PyTclObj_FromTclObj(resultObj);
+        return tohil_python_return(tcl_interp, TCL_OK, self->to, resultObj);
     } else if (PySlice_Check(item)) {
         Py_ssize_t start, stop, step, slicelength, i;
         size_t cur;
@@ -1784,7 +1623,7 @@ PyTclObj_subscript(PyTclObj *self, PyObject *item)
         slicelength = PySlice_AdjustIndices(size, &start, &stop, step);
 
         if (slicelength <= 0) {
-            return PyTclObj_FromTclObj(Tcl_NewObj());
+            return tohil_python_return(tcl_interp, TCL_OK, self->to, Tcl_NewObj());
         } else if (step == 1) {
             return PyTclObj_slice(self, start, stop);
         } else {
@@ -1801,7 +1640,7 @@ PyTclObj_subscript(PyTclObj *self, PyObject *item)
             }
 
             for (cur = start, i = 0; i < slicelength; cur += (size_t)step, i++) {
-                it = PyTclObj_FromTclObj(listObjv[cur]);
+                it = tohil_python_return(tcl_interp, TCL_OK, self->to, listObjv[cur]);
                 PyList_SET_ITEM(result, i, it);
             }
             return result;
@@ -1828,7 +1667,7 @@ typedef struct {
     PyObject_HEAD;
     int started;
     int done;
-    PyObject *to;
+    PyTypeObject *to;
     Tcl_Obj *dictObj;
     Tcl_DictSearch search;
 } PyTohil_TD_IterObj;
@@ -1836,6 +1675,7 @@ typedef struct {
 static PyObject *
 PyTohil_TD_iter(PyTohil_TD_IterObj *self)
 {
+    // printf("PyTohil_TD_iter\n");
     Py_INCREF(self);
 
     self->started = 0;
@@ -1851,6 +1691,7 @@ PyTohil_TD_iter(PyTohil_TD_IterObj *self)
 PyObject *
 PyTohil_TD_iternext(PyTohil_TD_IterObj *self)
 {
+    // printf("PyTohil_TD_iternext\n");
     Tcl_Obj *keyObj = NULL;
     Tcl_Obj *valueObj = NULL;
     int done = 0;
@@ -1927,44 +1768,47 @@ static PyTypeObject PyTohil_TD_IterType = {
 };
 
 //
-// t.td_iter()
+//
+// end of tclobj td_iterator python datatype
+//
+//
+
+//
+// PyTclObj_getto - get "to" value, settable attribute for what
+//   type to convert tclobjs and tcldicts to
 //
 static PyObject *
-PyTohil_TD_td_iter(PyTclObj *self, PyObject *args, PyObject *kwargs)
+PyTclObj_getto(PyTclObj *self, void *closure)
 {
-    PyObject *pTo = NULL;
-    static char *kwlist[] = {"to", NULL};
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|$O", kwlist, &pTo)) {
-        return NULL;
-    }
-
-    int size = 0;
-    if (Tcl_DictObjSize(tcl_interp, self->tclobj, &size) == TCL_ERROR) {
-        PyErr_Format(PyExc_TypeError, "tclobj contents cannot be converted into a td");
-        return NULL;
-    }
-
-    PyTohil_TD_IterObj *pIter = (PyTohil_TD_IterObj *)PyObject_New(PyTohil_TD_IterObj, &PyTohil_TD_IterType);
-
-    pIter->started = 0;
-    pIter->done = 0;
-    memset((void *)&pIter->search, 0, sizeof(Tcl_DictSearch));
-    pIter->to = pTo;
-
-    if (pTo != NULL) {
-        Py_INCREF(pTo);
+    if (self->to == NULL) {
+        Py_RETURN_NONE;
     }
-    pIter->dictObj = ((PyTclObj *)self)->tclobj;
-    Tcl_IncrRefCount(pIter->dictObj);
-
-    return (PyObject *)pIter;
+    Py_INCREF(self->to);
+    return (PyObject *)self->to;
 }
 
 //
+// PyTclObj_getto - get "to" value, attribute for what
+//   type to convert tclobjs and tcldicts to
 //
-// end of tclobj td_iterator python datatype
-//
-//
+static int
+PyTclObj_setto(PyTclObj *self, PyTypeObject *toType, void *closure)
+{
+    if (!PyType_Check(toType)) {
+        return -1;
+    }
+    PyTypeObject *tmp = self->to;
+    self->to = toType;
+    Py_INCREF(toType);
+    Py_XDECREF(tmp);
+    return 0;
+}
+
+static PyGetSetDef PyTclObj_getsetters[] = {
+    {"to", (getter)PyTclObj_getto, (setter)PyTclObj_setto, "python type to default returns to", NULL},
+    {"_refcount", (getter)PyTclObj_refcount, NULL, "reference count of the embedded tcl object", NULL},
+    {"_tcltype", (getter)PyTclObj_type, NULL, "internal tcl data type of the tcl object", NULL},
+                                            {NULL}};
 
 static PyMappingMethods PyTclObj_as_mapping = {(lenfunc)PyTclObj_length, (binaryfunc)PyTclObj_subscript, NULL};
 
@@ -1991,23 +1835,16 @@ static PyMethodDef PyTclObj_methods[] = {
     {"as_tuple", (PyCFunction)PyTclObj_as_tuple, METH_NOARGS, "return tclobj as tuple"},
     {"as_dict", (PyCFunction)PyTclObj_as_dict, METH_NOARGS, "return tclobj as dict"},
     {"as_tclobj", (PyCFunction)PyTclObj_as_tclobj, METH_NOARGS, "return tclobj as tclobj"},
+    {"as_tcldict", (PyCFunction)PyTclObj_as_tcldict, METH_NOARGS, "return tclobj as tcldict"},
     {"as_byte_array", (PyCFunction)PyTclObj_as_byte_array, METH_NOARGS, "return tclobj as a byte array"},
     {"incr", (PyCFunction)PyTclObj_incr, METH_VARARGS | METH_KEYWORDS, "increment tclobj as int"},
     {"llength", (PyCFunction)PyTclObj_llength, METH_NOARGS, "length of tclobj tcl list"},
-    {"td_get", (PyCFunction)PyTclObj_td_get, METH_VARARGS | METH_KEYWORDS, "get from tcl dict"},
-    {"td_exists", (PyCFunction)PyTclObj_td_exists, METH_VARARGS | METH_KEYWORDS, "see if key exists in tcl dict"},
-    {"td_remove", (PyCFunction)PyTclObj_td_remove, METH_VARARGS | METH_KEYWORDS, "remove item or list hierarchy from tcl dict"},
-    {"td_iter", (PyCFunction)PyTohil_TD_td_iter, METH_VARARGS | METH_KEYWORDS, "iterate on a tclobj containing a tcl dict"},
-    {"td_set", (PyCFunction)PyTclObj_td_set, METH_VARARGS | METH_KEYWORDS, "set item in tcl dict"},
-    {"td_size", (PyCFunction)PyTclObj_td_size, METH_NOARGS, "get size of tcl dict"},
     {"getvar", (PyCFunction)PyTclObj_getvar, METH_O, "set tclobj to tcl var or array element"},
     {"setvar", (PyCFunction)PyTclObj_setvar, METH_O, "set tcl var or array element to tclobj's tcl object"},
     {"set", (PyCFunction)PyTclObj_set, METH_O, "set tclobj from some python object"},
     {"lindex", (PyCFunction)PyTclObj_lindex, METH_VARARGS | METH_KEYWORDS, "get value from tclobj as tcl list"},
     {"lappend", (PyCFunction)PyTclObj_lappend, METH_O, "lappend (list-append) something to tclobj"},
     {"lappend_list", (PyCFunction)PyTclObj_lappend_list, METH_O, "lappend another tclobj or a python list of stuff to tclobj"},
-    {"refcount", (PyCFunction)PyTclObj_refcount, METH_NOARGS, "get tclobj's reference count"},
-    {"type", (PyCFunction)PyTclObj_type, METH_NOARGS, "return the tclobj's type from tcl, or None if it doesn't have one"},
     {NULL} // sentinel
 };
 
@@ -2027,23 +1864,445 @@ static PyTypeObject PyTclObjType = {
     .tp_as_mapping = &PyTclObj_as_mapping,
     .tp_repr = (reprfunc)PyTclObj_repr,
     .tp_richcompare = (richcmpfunc)PyTclObj_richcompare,
+    .tp_getset = PyTclObj_getsetters,
 };
 
 //
 // end of tclobj python datatype
 //
 
+//
+// start of tcldict python datatype
+//
+
+// the object's python data structure is the same as tclobj
+// so we can reuse that to the max.  we're going to have different
+// iterators and stuff for dicts
+
+//
+// return true if python object is a tclobj type
+//
+int
+TohilTclDict_Check(PyObject *pyObj)
+{
+    return PyObject_TypeCheck(pyObj, &TohilTclDictType);
+}
+
+//
+// create a new python tcldict object from any Tcl_Obj
+//
+static PyObject *
+TohilTclDict_FromTclObj(Tcl_Obj *obj)
+{
+    PyTclObj *self = (PyTclObj *)TohilTclDictType.tp_alloc(&TohilTclDictType, 0);
+    if (self != NULL) {
+        self->tclobj = obj;
+        Tcl_IncrRefCount(obj);
+    }
+    return (PyObject *)self;
+}
+
+//
+// td - tcl dict stuff
+//
+
+//
+// td_locate(key) - do a dict get on the tclobj object using
+//   either a list of keys or a singleton key, and return
+//   the target value object.
+//
+//   internal routine used by td_get and td_exists
+//
+static Tcl_Obj *
+TohilTclDict_td_locate(PyTclObj *self, PyObject *keys)
+{
+    // printf("TohilTclDict_td_locate\n");
+    Tcl_Obj *keyObj = NULL;
+    Tcl_Obj *valueObj = NULL;
+
+    if (PyList_Check(keys)) {
+        int i;
+        Tcl_Obj *dictPtrObj = self->tclobj;
+        Py_ssize_t nKeys = PyList_GET_SIZE(keys);
+
+        for (i = 0; i < nKeys; i++) {
+            PyObject *keyPyObj = PyList_GET_ITEM(keys, i);
+            keyObj = pyObjToTcl(tcl_interp, keyPyObj);
+
+            if (Tcl_DictObjGet(tcl_interp, dictPtrObj, keyObj, &valueObj) == TCL_ERROR) {
+                Tcl_DecrRefCount(keyObj);
+                PyErr_SetString(PyExc_TypeError, "tclobj contents cannot be converted into a td");
+                return NULL;
+            }
+            if (valueObj == NULL) {
+                Tcl_DecrRefCount(keyObj);
+                return NULL;
+            }
+            dictPtrObj = valueObj;
+            Tcl_DecrRefCount(keyObj);
+        }
+
+        // at this point if there's been no error valueObj has our guy
+    } else {
+        // it's a singleton
+        Tcl_Obj *keyObj = pyObjToTcl(tcl_interp, keys);
+
+        if (Tcl_DictObjGet(NULL, self->tclobj, keyObj, &valueObj) == TCL_ERROR) {
+            Tcl_DecrRefCount(keyObj);
+            PyErr_SetString(PyExc_TypeError, "tclobj contents cannot be converted into a td");
+            return NULL;
+        }
+        Tcl_DecrRefCount(keyObj);
+    }
+
+    return valueObj;
+}
+
+//
+// td_get(key) - do a dict get on the tcl object
+//
+static PyObject *
+TohilTclDict_td_get(PyTclObj *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = {"key", "to", "default", NULL};
+    PyObject *keys = NULL;
+    PyTypeObject *to = NULL;
+    PyObject *pDefault = NULL;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|$OO", kwlist, &keys, &to, &pDefault)) {
+        return NULL;
+    }
+
+    Tcl_Obj *valueObj = TohilTclDict_td_locate(self, keys);
+    if (valueObj == NULL) {
+        if (pDefault != NULL) {
+            if (to == NULL) {
+                // not there but they provided a default,
+                // give them their default
+                Py_INCREF(pDefault);
+                return pDefault;
+            } else {
+                valueObj = pyObjToTcl(tcl_interp, pDefault);
+            }
+        } else {
+            // not there, no default.  it's an error.
+            // this is clean and the way python does it.
+            PyErr_SetObject(PyExc_KeyError, keys);
+            return NULL;
+        }
+    }
+
+    if (to == NULL && self->to != NULL)
+        to = self->to;
+
+    return tohil_python_return(tcl_interp, TCL_OK, to, valueObj);
+}
+
+//
+// TohilTclDict_subscript - return the value of an element in
+//   the tcl dict.  value may itself be a sub-dictionary.
+//
+//   key can be a list of keys in which case the tcl dict is
+//   treated as a hierarchy of dicts.
+//
+static PyObject *
+TohilTclDict_subscript(PyTclObj *self, PyObject *keys)
+{
+    // printf("TohilTclDict_subscript\n");
+    Tcl_Obj *valueObj = TohilTclDict_td_locate(self, keys);
+    if (valueObj == NULL) {
+        // not there, no default.  it's an error.
+        // this is clean and the way python does it.
+        PyErr_SetObject(PyExc_KeyError, keys);
+        return NULL;
+    }
+
+    return tohil_python_return(tcl_interp, TCL_OK, self->to, valueObj);
+}
+
+//
+// TohilTclDict_delitem - removes an item from the tcldict.  if
+//   keys is a python list, treats tcl dict as a hierarchy of
+//   tcl dicts.
+//
+static int
+TohilTclDict_delitem(PyTclObj *self, PyObject *keys)
+{
+    if (PyList_Check(keys)) {
+        int objc = 0;
+        Tcl_Obj **objv = NULL;
+
+        // build up a tcl objv of the keys
+        pyListToTclObjv((PyListObject *)keys, &objc, &objv);
+
+        // we are about to try to modify the object, so if it's shared we need to copy
+        PyTclObj_dup_if_shared(self);
+
+        int status = (Tcl_DictObjRemoveKeyList(tcl_interp, self->tclobj, objc, objv));
+
+        // tear down the objv of the keys we created
+        pyListToObjv_teardown(objc, objv);
+
+        if (status == TCL_ERROR) {
+            PyErr_SetString(PyExc_KeyError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
+            return -1;
+        }
+    } else {
+        Tcl_Obj *keyObj = _pyObjToTcl(tcl_interp, keys);
+
+        if (keyObj == NULL) {
+            PyErr_SetString(PyExc_RuntimeError, "unable to fashion argument into a string to be used as a dictionary key");
+            return -1;
+        }
+
+        // we are about to try to modify the object, so if it's shared we need to copy
+        PyTclObj_dup_if_shared(self);
+
+        if (Tcl_DictObjRemove(NULL, self->tclobj, keyObj) == TCL_ERROR) {
+            Tcl_DecrRefCount(keyObj);
+            PyErr_SetString(PyExc_TypeError, "tclobj contents cannot be converted into a td");
+            return -1;
+        }
+        Tcl_DecrRefCount(keyObj);
+    }
+
+    return 0;
+}
+
+//
+// self.setitem(keys, value) - translates python value object to
+//   tcl and sets it in the tcldict.  if keys is a python list,
+//   treats it as a hierarchy of dictionaries.
+//
+static int
+TohilTclDict_setitem(PyTclObj *self, PyObject *keys, PyObject *pValue)
+{
+    Tcl_Obj *valueObj = pyObjToTcl(tcl_interp, pValue);
+
+    // we are about to try to modify the object, so if it's shared we need to copy
+    PyTclObj_dup_if_shared(self);
+
+    if (PyList_Check(keys)) {
+        int objc;
+        Tcl_Obj **objv;
+
+        // build up a tcl objv of the keys
+        pyListToTclObjv((PyListObject *)keys, &objc, &objv);
+
+        int status = (Tcl_DictObjPutKeyList(tcl_interp, self->tclobj, objc, objv, valueObj));
+
+        // tear down the objv of the keys we created
+        pyListToObjv_teardown(objc, objv);
+
+        if (status == TCL_ERROR) {
+            Tcl_DecrRefCount(valueObj);
+            PyErr_SetString(PyExc_RuntimeError, Tcl_GetString(Tcl_GetObjResult(tcl_interp)));
+            return -1;
+        }
+    } else {
+        Tcl_Obj *keyObj = _pyObjToTcl(tcl_interp, keys);
+
+        if (keyObj == NULL) {
+            PyErr_SetString(PyExc_RuntimeError, "unable to fashion argument into a string to be used as a dictionary key");
+            return -1;
+        }
+
+        if (Tcl_DictObjPut(tcl_interp, self->tclobj, keyObj, valueObj) == TCL_ERROR) {
+            Tcl_DecrRefCount(keyObj);
+            Tcl_DecrRefCount(valueObj);
+            PyErr_SetString(PyExc_TypeError, "tclobj contents cannot be converted into a td");
+            return -1;
+        }
+        // something about this is a crash
+        // Tcl_DecrRefCount(keyObj);
+        // Tcl_DecrRefCount(valueObj);
+    }
+
+    return 0;
+}
+
+//
+// tclobj.td_set(key, value) - do a dict set on the tcl object.
+//   if key is a python list, td_set operates on a nested hierarchy
+//   of dictionaries.
+//
+static PyObject *
+TohilTclDict_td_set(PyTclObj *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = {"key", "value", NULL};
+    PyObject *keys = NULL;
+    PyObject *pValue = NULL;
+
+    // remember, "O" sets our pointer to the object without incrementing its reference count
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|$", kwlist, &keys, &pValue)) {
+        return NULL;
+    }
+
+    if (TohilTclDict_setitem(self, keys, pValue) < 0) {
+        return NULL;
+    }
+
+    Py_RETURN_NONE;
+}
+
+//
+// TohilTclDict_ass_sub() - if a python key object and python value
+//   object are present, set in the tcl dict for the key, the value.
+//
+//   the key may be a list in which case the dictionary is a hierarchy
+//   of dictionaries.
+//
+//   if the value is NULL then deletes the item.
+//
+static int
+TohilTclDict_ass_sub(PyTclObj *self, PyObject *key, PyObject *val)
+{
+    if (val == NULL) {
+        return TohilTclDict_delitem(self, key);
+    } else {
+        return TohilTclDict_setitem(self, key, val);
+    }
+}
+
+//
+// TohilTclDict_length() - return the dict size of a python tcldict's
+//   tcl object or set a python error and return -1 if something went wrong.
+//
+static Py_ssize_t
+TohilTclDict_length(PyTclObj *self)
+{
+    int length;
+    if (Tcl_DictObjSize(tcl_interp, self->tclobj, &length) == TCL_OK) {
+        return length;
+    }
+    PyErr_SetString(PyExc_TypeError, "tclobj contents cannot be converted into a td");
+    return -1;
+}
+
+//
+// TohilTclDict_size() - return a python object containing the dict size
+//   of a python tcldict's tcl object.
+//   returns null i.e. exception thrown if tcl object isn't a proper tcl dict.
+//
+static PyObject *
+TohilTclDict_size(PyTclObj *self, PyObject *pyobj)
+{
+    int length = TohilTclDict_length(self);
+    if (length < 0) {
+        return NULL;
+    }
+    return PyLong_FromLong(length);
+}
+
+//
+// TohilTclDictIter() - returns a tcldict iterator object that can
+//   iterate over a tcldict.
+//
+static PyObject *
+TohilTclDictIter(PyTclObj *self)
+{
+    // printf("TohilTclDictIter\n");
+    // we don't need size but we use this to make tclobj is or can be a dict
+    int size = 0;
+    if (Tcl_DictObjSize(tcl_interp, self->tclobj, &size) == TCL_ERROR) {
+        PyErr_Format(PyExc_TypeError, "tclobj contents cannot be converted into a td");
+        return NULL;
+    }
+
+    PyTohil_TD_IterObj *pIter = (PyTohil_TD_IterObj *)PyObject_New(PyTohil_TD_IterObj, &PyTohil_TD_IterType);
+
+    pIter->started = 0;
+    pIter->done = 0;
+    pIter->to = self->to;
+    Py_XINCREF(pIter->to);
+
+    memset((void *)&pIter->search, 0, sizeof(Tcl_DictSearch));
+
+    pIter->dictObj = ((PyTclObj *)self)->tclobj;
+    Tcl_IncrRefCount(pIter->dictObj);
+
+    return (PyObject *)pIter;
+}
+
+//
+// TohilTclDict_Contains - return 0 if key or keys is not
+//   contained in tcldict, 1 if it is, or -1 if there was
+//   an error.
+//
+static int
+TohilTclDict_Contains(PyObject *self, PyObject *keys)
+{
+    // printf("TohilTclDict_Contains\n");
+    Tcl_Obj *valueObj = TohilTclDict_td_locate((PyTclObj *)self, keys);
+    if (valueObj == NULL) {
+        if (PyErr_Occurred() != NULL) {
+            return -1;
+        }
+        return 0;
+    }
+    return 1;
+}
+
+static PyMappingMethods TohilTclDict_as_mapping = {(lenfunc)TohilTclDict_length, (binaryfunc)TohilTclDict_subscript,
+                                                   (objobjargproc)TohilTclDict_ass_sub};
+
+static PySequenceMethods TohilTclDict_as_sequence = {
+    .sq_contains = TohilTclDict_Contains,
+};
+
+static PyMethodDef TohilTclDict_methods[] = {
+    {"get", (PyCFunction)TohilTclDict_td_get, METH_VARARGS | METH_KEYWORDS, "get from tcl dict"},
+    // NB i don't know if this __len__ thing works -- python might
+    // be doing something gross to get the len of the dict, like
+    // enumerating the elements
+    {"__len__", (PyCFunction)TohilTclDict_size, METH_VARARGS | METH_KEYWORDS, "get length of tcl dict"},
+    {"td_set", (PyCFunction)TohilTclDict_td_set, METH_VARARGS | METH_KEYWORDS, "set item in tcl dict"},
+    {"getvar", (PyCFunction)PyTclObj_getvar, METH_O, "set tclobj to tcl var or array element"},
+    {"setvar", (PyCFunction)PyTclObj_setvar, METH_O, "set tcl var or array element to tclobj's tcl object"},
+    {"set", (PyCFunction)PyTclObj_set, METH_O, "set tclobj from some python object"},
+    {NULL} // sentinel
+};
+
+// NB need to change tp_itr, to_as_squence, to_as_mapping to do dict stuff
+
+static PyTypeObject TohilTclDictType = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+        // .tp_base = &PyTclObjType, NB - len() breaks when we inherit
+        .tp_name = "tohil.tcldict",
+    .tp_doc = "Tcl TD tcldict Object",
+    .tp_basicsize = sizeof(PyTclObj),
+    .tp_itemsize = 0,
+    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+    .tp_new = PyTclObj_new,
+    .tp_init = (initproc)PyTclObj_init,
+    .tp_dealloc = (destructor)PyTclObj_dealloc,
+    .tp_methods = TohilTclDict_methods,
+    .tp_str = (reprfunc)PyTclObj_str,
+    .tp_iter = (getiterfunc)TohilTclDictIter,
+    .tp_as_mapping = &TohilTclDict_as_mapping,
+    .tp_as_sequence = &TohilTclDict_as_sequence,
+    .tp_repr = (reprfunc)PyTclObj_repr,
+    .tp_richcompare = (richcmpfunc)PyTclObj_richcompare,
+    .tp_getset = PyTclObj_getsetters,
+};
+
+//
+//
+// end of tcldict python datatype
+//
+//
+
 // say return tohil_python_return(interp, tcl_result, to string, resultObject)
 // from any python C function in this library that accepts a to=python_data_type argument,
 // and this routine ought to handle it
 PyObject *
-tohil_python_return(Tcl_Interp *interp, int tcl_result, PyObject *toType, Tcl_Obj *resultObj)
+tohil_python_return(Tcl_Interp *interp, int tcl_result, PyTypeObject *toType, Tcl_Obj *resultObj)
 {
     const char *toString = NULL;
     PyTypeObject *pt = NULL;
 
     if (PyErr_Occurred() != NULL) {
-        printf("tohil_python_return invoked with a python error already present\n");
+        // printf("tohil_python_return invoked with a python error already present\n");
         // return NULL;
     }
 
@@ -2126,6 +2385,10 @@ tohil_python_return(Tcl_Interp *interp, int tcl_result, PyObject *toType, Tcl_Ob
         return PyTclObj_FromTclObj(resultObj);
     }
 
+    if (strcmp(toString, "tohil.tcldict") == 0) {
+        return TohilTclDict_FromTclObj(resultObj);
+    }
+
     if (strcmp(toString, "list") == 0) {
         return tclListObjToPyListObject(interp, resultObj);
     }
@@ -2142,7 +2405,7 @@ tohil_python_return(Tcl_Interp *interp, int tcl_result, PyObject *toType, Tcl_Ob
         return tclListObjToPyTupleObject(interp, resultObj);
     }
 
-    PyErr_SetString(PyExc_RuntimeError, "'to' conversion type must be str, int, bool, float, list, set, dict, tuple, or tohil.tclobj");
+    PyErr_SetString(PyExc_RuntimeError, "'to' conversion type must be str, int, bool, float, list, set, dict, tuple, tohil.tclobj or tohil.tcldict");
     return NULL;
 }
 
@@ -2153,7 +2416,7 @@ static PyObject *
 tohil_eval(PyObject *self, PyObject *args, PyObject *kwargs)
 {
     static char *kwlist[] = {"tcl_code", "to", NULL};
-    PyObject *to = NULL;
+    PyTypeObject *to = NULL;
     char *utf8Code = NULL;
 
     if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|$O", kwlist, &utf8Code, &to))
@@ -2177,7 +2440,7 @@ tohil_expr(PyObject *self, PyObject *args, PyObject *kwargs)
 {
     static char *kwlist[] = {"expression", "to", NULL};
     char *utf8expression = NULL;
-    PyObject *to = NULL;
+    PyTypeObject *to = NULL;
 
     if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|$O", kwlist, &utf8expression, &to))
         return NULL;
@@ -2204,7 +2467,7 @@ tohil_convert(PyObject *self, PyObject *args, PyObject *kwargs)
 {
     static char *kwlist[] = {"pyobject", "to", NULL};
     PyObject *pyInputObject = NULL;
-    PyObject *to = NULL;
+    PyTypeObject *to = NULL;
 
     if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|$O", kwlist, &pyInputObject, &to))
         return NULL;
@@ -2225,7 +2488,7 @@ tohil_getvar(PyObject *self, PyObject *args, PyObject *kwargs)
 {
     static char *kwlist[] = {"var", "to", "default", NULL};
     char *var = NULL;
-    PyObject *to = NULL;
+    PyTypeObject *to = NULL;
     PyObject *defaultPyObj = NULL;
     Tcl_Obj *obj = NULL;
 
@@ -2341,6 +2604,7 @@ tohil_incr(PyObject *self, PyObject *args, PyObject *kwargs)
         longValue += increment;
 
         if (Tcl_IsShared(obj)) {
+            Tcl_DecrRefCount(obj);
             obj = Tcl_DuplicateObj(obj);
             Tcl_SetLongObj(obj, longValue);
             if (Tcl_SetVar2Ex(tcl_interp, var, NULL, obj, (TCL_LEAVE_ERR_MSG)) == NULL) {
@@ -2380,7 +2644,7 @@ tohil_subst(PyObject *self, PyObject *args, PyObject *kwargs)
 {
     static char *kwlist[] = {"string", "to", NULL};
     char *string = NULL;
-    PyObject *to = NULL;
+    PyTypeObject *to = NULL;
 
     if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|$O", kwlist, &string, &to)) {
         return NULL;
@@ -2405,7 +2669,7 @@ tohil_call(PyObject *self, PyObject *args, PyObject *kwargs)
 {
     Py_ssize_t objc = PyTuple_GET_SIZE(args);
     int i;
-    PyObject *to = NULL;
+    PyTypeObject *to = NULL;
 
     //
     // allocate an array of Tcl object pointers the same size
@@ -2416,7 +2680,7 @@ tohil_call(PyObject *self, PyObject *args, PyObject *kwargs)
 
     // we need to process kwargs to get the to
     if (kwargs != NULL) {
-        to = PyDict_GetItemString(kwargs, "to");
+        to = (PyTypeObject *)PyDict_GetItemString(kwargs, "to");
     }
 
     // for each argument convert the python object to a tcl object
@@ -2611,6 +2875,11 @@ PyInit__tohil(void)
         return NULL;
     }
 
+    // turn up the tcldict python type
+    if (PyType_Ready(&TohilTclDictType) < 0) {
+        return NULL;
+    }
+
     // create the python module
     PyObject *m = PyModule_Create(&TohilModule);
     if (m == NULL) {
@@ -2644,6 +2913,14 @@ PyInit__tohil(void)
         return NULL;
     }
 
+    // add our tcldict type to python
+    Py_INCREF(&TohilTclDictType);
+    if (PyModule_AddObject(m, "tcldict", (PyObject *)&TohilTclDictType) < 0) {
+        Py_DECREF(&TohilTclDictType);
+        Py_DECREF(m);
+        return NULL;
+    }
+
     // ..and stash a pointer to the tcl interpreter in a python
     // capsule so we can find it when we're doing python stuff
     // and need to talk to tcl
diff --git a/pysrc/tohil/__init__.py b/pysrc/tohil/__init__.py
index 23fe2a2..d0def77 100644
--- a/pysrc/tohil/__init__.py
+++ b/pysrc/tohil/__init__.py
@@ -242,6 +242,7 @@ def rivet():
     subst,
     unset,
     tclobj,
+    tcldict,
     convert,
     incr,
 )


Test patch:
diff --git a/tests/test_tcldict.py b/tests/test_tcldict.py
new file mode 100644
index 0000000..1b3ec65
--- /dev/null
+++ b/tests/test_tcldict.py
@@ -0,0 +1,62 @@
+
+import unittest
+
+import tohil
+
+
+class TestTcleDict(unittest.TestCase):
+    def test_tcldict1(self):
+        """exercise tohil.tcldict data type"""
+        x = tohil.tcldict("a 1 b 2 c 3 d 4 e 5")
+
+        self.assertEqual("c" in x, True)
+        self.assertEqual("f" in x, False)
+
+    def test_tcldict2(self):
+        """exercise tohil.tcldict item access"""
+        x = tohil.tcldict("a 1 b 2 c 3 d 4 e 5")
+
+        self.assertEqual(x['c'], "3")
+        self.assertEqual(len(x), 5)
+
+        with self.assertRaises(KeyError):
+            x['zzz']
+
+    def test_tcldict3(self):
+        """exercise tohil.tcldict item insertion and modification"""
+        x = tohil.tcldict("a 1 b 2 c 3 d 4 e 5")
+
+        x['d'] = '42'
+
+        self.assertEqual(x['c'], '3')
+        self.assertEqual(x['d'], '42')
+        self.assertEqual(len(x), 5)
+
+        x['f'] = '6'
+        self.assertEqual(x['f'], '6')
+        self.assertEqual(len(x), 6)
+
+    def test_tcldict4(self):
+        """exercise nested tohil.tcldicts"""
+        d = tohil.tcldict('a 1 b 2 c 3 d 4')
+        e = tohil.tcldict('i i1 j j1 k k1')
+        d['m'] = e
+
+        self.assertEqual(str(d), 'a 1 b 2 c 3 d 4 m {i i1 j j1 k k1}')
+
+        self.assertEqual(d.get(['m', 'k']), 'k1')
+        self.assertEqual(d.get('m'), 'i i1 j j1 k k1')
+        self.assertEqual(len(d), 5)
+        self.assertEqual(len(e), 3)
+
+        s = ''
+        for i in d:
+            s += i
+        self.assertEqual(s, 'abcdm')
+
+        self.assertEqual(d[['m', 'j']], 'j1')
+
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/test_tclobj.py b/tests/test_tclobj.py
index ddaaba3..7a3c3d9 100644
--- a/tests/test_tclobj.py
+++ b/tests/test_tclobj.py
@@ -73,6 +73,8 @@ def test_tclobj10(self):
         """exercise tohil.tclobj subscripting, str(), and repr()"""
         x = tohil.eval("list 1 2 3 4 5", to=tohil.tclobj)
         self.assertEqual(str(x[2]), "3")
+        self.assertEqual(repr(x[2]), "'3'")
+        x.to = tohil.tclobj
         self.assertEqual(repr(x[2]), "<tohil.tclobj: '3'>")
 
     def test_tclobj11(self):
@@ -92,6 +94,11 @@ def test_tclobj12(self):
     def test_tclobj13(self):
         """tohil.tclobj slice stuff, 4 to the end"""
         x = tohil.eval("list 1 2 3 4 5 6 7", to=tohil.tclobj)
+        self.assertEqual(
+            x[4:],
+            ['5', '6', '7'],
+        )
+        x.to = tohil.tclobj
         self.assertEqual(
             repr(x[4:]),
             "[<tohil.tclobj: '5'>, <tohil.tclobj: '6'>, <tohil.tclobj: '7'>]",
@@ -100,6 +107,11 @@ def test_tclobj13(self):
     def test_tclobj14(self):
         """tohil.tclobj slice stuff, the beginning until 4"""
         x = tohil.eval("list 1 2 3 4 5 6 7", to=tohil.tclobj)
+        self.assertEqual(
+            x[:4],
+            ['1', '2', '3', '4'],
+        )
+        x.to = tohil.tclobj
         self.assertEqual(
             repr(x[:4]),
             "[<tohil.tclobj: '1'>, <tohil.tclobj: '2'>, <tohil.tclobj: '3'>, <tohil.tclobj: '4'>]",
@@ -108,6 +120,16 @@ def test_tclobj14(self):
     def test_tclobj15(self):
         """tohil.tclobj slice stuff, from the beginning to 4 from the end"""
         x = tohil.eval("list 1 2 3 4 5 6 7", to=tohil.tclobj)
+        self.assertEqual(
+            x[:-4],
+            ['1', '2', '3'],
+        )
+        x.to = int
+        self.assertEqual(
+            x[:-4],
+            [1, 2, 3],
+        )
+        x.to = tohil.tclobj
         self.assertEqual(
             repr(x[:-4]),
             "[<tohil.tclobj: '1'>, <tohil.tclobj: '2'>, <tohil.tclobj: '3'>]",
@@ -116,6 +138,11 @@ def test_tclobj15(self):
     def test_tclobj16(self):
         """tohil.tclobj slice stuff, from 4 from the end to the end"""
         x = tohil.eval("list 1 2 3 4 5 6 7", to=tohil.tclobj)
+        self.assertEqual(
+            x[-4:],
+            ['4', '5', '6', '7'],
+        )
+        x.to = tohil.tclobj
         self.assertEqual(
             repr(x[-4:]),
             "[<tohil.tclobj: '4'>, <tohil.tclobj: '5'>, <tohil.tclobj: '6'>, <tohil.tclobj: '7'>]",
@@ -124,6 +151,16 @@ def test_tclobj16(self):
     def test_tclobj17(self):
         """tohil.tclobj slice stuff, the whole thing with a :"""
         x = tohil.eval("list 1 2 3 4 5 6 7", to=tohil.tclobj)
+        self.assertEqual(
+            x[:],
+            ['1', '2', '3', '4', '5', '6', '7'],
+        )
+        x.to = float
+        self.assertEqual(
+            x[:],
+            [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0],
+        )
+        x.to = tohil.tclobj
         self.assertEqual(
             repr(x[:]),
             "[<tohil.tclobj: '1'>, <tohil.tclobj: '2'>, <tohil.tclobj: '3'>, <tohil.tclobj: '4'>, <tohil.tclobj: '5'>, <tohil.tclobj: '6'>, <tohil.tclobj: '7'>]",
@@ -133,8 +170,11 @@ def test_tclobj18(self):
         """tohil.tclobj comparing tclobjs with stuff"""
         x = tohil.eval("list 1 2 3 4 5 6 7", to=tohil.tclobj)
         self.assertTrue(x[2] == x[2])
+        x.to = int
         self.assertTrue(x[2] == 3)
+        x.to = str
         self.assertTrue(x[2] == "3")
+        x.to = float
         self.assertTrue(x[2] < 4.0)
         self.assertFalse(x[2] > 4.0)
 
diff --git a/tests/test_td.py b/tests/test_td.py
index 66e17e2..1b4cf9a 100644
--- a/tests/test_td.py
+++ b/tests/test_td.py
@@ -2,100 +2,106 @@
 
 import tohil
 
-from tohil import tclobj
+from tohil import tclobj, tcldict
 
 
 class TestTD(unittest.TestCase):
     def test_td1(self):
-        """tohil.tclobj td_get """
-        x = tohil.eval("list a 1 b 2 c 3", to=tohil.tclobj)
-        self.assertEqual(x.td_get("a"), "1")
-        self.assertEqual(x.td_get("a", to=int), 1)
+        """tohil.tcldict get """
+        x = tohil.eval("list a 1 b 2 c 3", to=tohil.tcldict)
+        self.assertEqual(x["a"], "1")
+        x.to = int
+        self.assertEqual(x["a"], 1)
         with self.assertRaises(KeyError):
-            x.td_get("z")
-        self.assertEqual(x.td_get("z", default="bar"), "bar")
-        self.assertEqual(x.td_get("z", default="bar", to=list), ["bar"])
+            x.get("z")
+        x.to = str
+        self.assertEqual(x.get("z", default="bar"), "bar")
+        self.assertEqual(x.get("z", default="bar", to=list), ["bar"])
         with self.assertRaises(RuntimeError):
-            x.td_get("z", default="bar", to=int)
-        self.assertEqual(x.td_get("z", default="1", to=int), 1)
+            x.get("z", default="bar", to=int)
+        self.assertEqual(x.get("z", default="1", to=int), 1)
 
     def test_td2(self):
-        """tohil.tclobj td_remove """
-        x = tohil.eval("list a 1 b 2 c 3", to=tohil.tclobj)
-        self.assertEqual(x.td_get("b"), "2")
-        self.assertEqual(x.td_get("b", to=int), 2)
-        x.td_remove("c")
-        self.assertEqual(repr(x), "<tohil.tclobj: 'a 1 b 2'>")
+        """tohil.tcldict get and delete """
+        x = tohil.eval("list a 1 b 2 c 3", to=tohil.tcldict)
+        self.assertEqual(x["b"], "2")
+        self.assertEqual(x.get("b", to=int), 2)
+        del x["c"]
+        self.assertEqual(str(x), 'a 1 b 2')
 
     def test_td3(self):
-        """tohil.tclobj td_set """
-        x = tohil.tclobj()
-        x.td_set("foo", "bar")
-        x.td_set("hey", "you")
-        self.assertEqual(x.td_get("foo"), "bar")
-        self.assertEqual(repr(x), "<tohil.tclobj: 'foo bar hey you'>")
-        x.td_remove("foo")
-        self.assertEqual(repr(x), "<tohil.tclobj: 'hey you'>")
+        """tohil.tcldict set """
+        x = tohil.tcldict()
+        x["foo"] = "bar"
+        x["hey"] = "you"
+        self.assertEqual(x["foo"], "bar")
+        self.assertEqual(str(x), 'foo bar hey you')
+        del x["foo"]
+        self.assertEqual(str(x), 'hey you')
 
     def test_td4(self):
-        """tohil.tclobj td_set, get and remove """
-        x = tclobj()
-        x.td_set("foo", 5)
-        x.td_set("foo", 5)
-        self.assertEqual(x.td_get("foo"), "5")
-        self.assertEqual(x.td_get("foo", to=int), 5)
-        self.assertEqual(repr(x), "<tohil.tclobj: 'foo 5'>")
-        x.td_remove("foo")
-        self.assertEqual(repr(x), "<tohil.tclobj: ''>")
-
-    def test_td4(self):
-        """tohil.tclobj list remove """
-        x = tclobj()
-        x.td_set("a", 1)
-        x.td_set("b", 2)
-        x.td_set("c", 3)
-        x.td_remove("a")
-        x.td_remove(["c"])
-        x.td_remove(["c"])
-        self.assertEqual(repr(x), "<tohil.tclobj: 'b 2'>")
+        """tohil.tcldict set, get and remove """
+        x = tcldict()
+        x["foo"] = 5
+        x["foo"] = 5
+        self.assertEqual(x["foo"], "5")
+        self.assertEqual(x.get("foo", to=int), 5)
+        self.assertEqual(repr(x), "<tohil.tcldict: 'foo 5'>")
+        del x["foo"]
+        self.assertEqual(repr(x), "<tohil.tcldict: ''>")
 
     def test_td5(self):
-        """tohil.tclobj td_set with list of keys"""
-        x = tclobj()
-        x.td_set(["a", "b", "c", "d"], "bar")
-        self.assertEqual(repr(x), "<tohil.tclobj: 'a {b {c {d bar}}}'>")
+        """tohil.tcldict list remove """
+        x = tcldict()
+        x["a"] = 1
+        x["b"] = 2
+        x["c"] = 3
+        del x["a"]
+        del x["c"]
+        del x["c"]
+        self.assertEqual(str(x), 'b 2')
 
     def test_td6(self):
-        """tohil.tclobj td_get with list of keys"""
-        x = tclobj()
-        x.td_set(["a", "b", "c", "d"], "foo")
-        x.td_set("b", "bar")
-        self.assertEqual(x.td_get(["a", "b", "c", "d"]), "foo")
+        """tohil.tcldict td_set with list of keys"""
+        x = tcldict()
+        x[["a", "b", "c", "d"]] = "bar"
+        self.assertEqual(repr(x), "<tohil.tcldict: 'a {b {c {d bar}}}'>")
 
     def test_td7(self):
-        """tohil.tclobj td_exists"""
-        x = tclobj()
-        x.td_set(["a", "b", "c"], "foo")
-        x.td_set("b", "bar")
-        self.assertEqual(x.td_get(["a", "b", "c"]), "foo")
-        self.assertEqual(x.td_exists(["a", "b", "c"]), True)
-        self.assertEqual(x.td_exists(["a", "d", "d"]), False)
-        x.set("monkey")
-        with self.assertRaises(TypeError):
-            x.td_exists(["a", "b", "c"])
+        """tohil.tcldict access with list of keys"""
+        x = tcldict()
+        x[["a", "b", "c", "d"]] = "foo"
+        x["b"] = "bar"
+        self.assertEqual(x[["a", "b", "c", "d"]], "foo")
 
     def test_td8(self):
-        """tohil.tclobj td_get of nested dictionaries"""
-        t = tclobj()
-        t.td_set(["a", "b", "c", "d"], 1)
-        t.td_set("b", "bar")
-        self.assertEqual(t.td_get(["a", "b", "c", "d"]), "1")
-        self.assertEqual(t.td_get(["a", "b"]), "c {d 1}")
-        x = t.td_get(["a", "b"], to=tohil.tclobj)
+        """tohil.tcldict nested-in checks"""
+        x = tcldict()
+        x[["a", "b", "c"]] = "foo"
+        x["b"] = "bar"
+        self.assertEqual(x[["a", "b", "c"]], "foo")
+        self.assertEqual(["a", "b", "c"] in x, True)
+        self.assertEqual(["a", "d", "d"] in x, False)
+        with self.assertRaises(TypeError):
+            ["a", "b", "c", "d"] in x
+
+    def test_td9(self):
+        """tohil.tcldict access and manipulation of nested dictionaries"""
+        t = tcldict()
+        t[["a", "b", "c", "d"]] = 1
+        t["b"] = "bar"
+        self.assertEqual(t.get(["a", "b", "c", "d"]), "1")
+        self.assertEqual(t.get(["a", "b"]), "c {d 1}")
+        x = t.get(["a", "b"], to=tohil.tcldict)
         with self.assertRaises(KeyError):
-            x.td_get("d")
-        self.assertEqual(x.td_get(["c", "d"]), "1")
-        self.assertEqual(x.td_exists(["c", "d"]), True)
+            x["d"]
+        self.assertEqual(x[["c", "d"]], "1")
+        self.assertEqual(["c", "d"] in x, True)
+        self.assertEqual(["c"] in x, True)
+        self.assertEqual("c" in x, True)
+        self.assertEqual("zzz" in x, False)
+        self.assertEqual(["zzz"] in x, False)
+        self.assertEqual(["zzz","zzz2"] in x, False)
 
 
 if __name__ == "__main__":
diff --git a/tests/test_td_iter.py b/tests/test_td_iter.py
index 4b519e5..df5ac48 100644
--- a/tests/test_td_iter.py
+++ b/tests/test_td_iter.py
@@ -7,19 +7,21 @@
 
 class Test_td_iter(unittest.TestCase):
     def test_td_iter1(self):
-        """tohil.tclobj td_iter """
-        t = tohil.tclobj("a 1 b 2 c 3 d 4 e 5 f 6")
-        self.assertEqual(list(t.td_iter()), ["a", "b", "c", "d", "e", "f"])
+        """tohil.tcldict iterator """
+        t = tohil.tcldict("a 1 b 2 c 3 d 4 e 5 f 6")
+        self.assertEqual(list(t), ["a", "b", "c", "d", "e", "f"])
 
     def test_td_iter2(self):
-        """tohil.tclobj td_iter with to= conversion """
-        t = tohil.tclobj("a 1 b 2 c 3 d 4 e 5 f 6")
+        """tohil.tcldict iter with to= conversion """
+        t = tohil.tcldict("a 1 b 2 c 3 d 4 e 5 f 6")
+        t.to=int
         self.assertEqual(
-            list(t.td_iter(to=int)),
+            list(t),
             [("a", 1), ("b", 2), ("c", 3), ("d", 4), ("e", 5), ("f", 6)],
         )
+        t.to=str
         self.assertEqual(
-            list(t.td_iter(to=str)),
+            list(t),
             [("a", "1"), ("b", "2"), ("c", "3"), ("d", "4"), ("e", "5"), ("f", "6")],
         )