Inconsistent behavior: tkinter and os.environ

What is the expected behavior between tkinter and os.environ? In the MWE below, I set the variable FOO in os.environ and it is visible in the first tkinter.Tcl evaluation. Before the second evaluation FOO is popped from os.environ but the line [info exists env(FOO)] still evaluates to 1. However, when the variable is fetched in $env(FOO) it fails because FOO does not exist in the environment. Clearly, there is some interaction between the tkinter._tkinter environment and os.environ, but there seems to be some inconsistency. Can anyone shed light, or point to some documentation, that describes the interaction?

This is a simple example, but comes up in the context of a Python implementation of environment modules that I am working on.

$ cat test.py
import os
import tkinter

os.environ.pop("FOO", None)
tcl = tkinter.Tcl()
tcl.createcommand("", lambda: None)
os.environ["FOO"] = "BAR"
tcl.eval("""\
if {[info exists env(FOO)]} {
    [puts stdout "FOO exists and is $env(FOO)"]
} else {
    [puts stdout "FOO does no exist"]
}
""")
os.environ.pop("FOO")
#tcl.call("unset", "env(SPAM)")
#tcl.call("catch", "unset env(TYL)", "")
tcl.eval("""\
if {[info exists env(FOO)]} {
    [puts stdout "FOO exists and is $env(FOO)"]
} else {
    [puts stdout "FOO does not exist"]
}
""")
print("DONE")
$ python test.py
FOO exists and is BAR
Traceback (most recent call last):
  File "/Users/tjfulle/Desktop/test.py", line 18, in <module>
    tcl.eval("""\
_tkinter.TclError: can't read "env(FOO)": no such variable

The Python os.environ dict is a copy of the operating system
environment strings when the interpreter starts up.

I expect that the TCL library, which knows nthing about Python, is
inspecting the OS environment C array. Popping from os.environ does
not change that.

See: os — Miscellaneous operating system interfaces — Python 3.11.2 documentation
particularly the second paragraph.

Cheers,
Cameron Simpson cs@cskk.id.au

Thanks for the reply Cameron - that was my understanding as well. But the example posted shows it is not quite the case. Calls to putenv (through assignment to os.environ) are reflected in the tkinter evaluation and calls to unsetenv (through os.environ.pop) are also reflected in in the tkinter evaluation. Where there is a disconnect is in the statement [info exists env(FOO)] which thinks FOO is in the environment, but fetching it via $env(FOO) raises an error (because it was popped).

On further investigation, I believe that in tcl $env(NAME) is read from the process environment as needed and the result cached. Once it is read, it will need to be modified by the tcl environment directly

I had not read your example closely enough.

I’ve done some experimentation, and I think you really need to say
env("FOO") and not env(FOO). Here’s test version of your final
tcl.eval call:

 tcl.eval(
     """\
 if {[info exists env(FOO)]} {
     puts stdout "A"
     set ex [info exists env("FOO")]
     puts stdout "ex $ex"
     set ex [info exists env(FOO)]
     puts stdout "ex $ex"
     if {0} {puts stdout "YES 0"}
     if {1} {puts stdout "YES 1"}
 ##    foreach envvar [lsort [array names env]] {
 ##        puts stdout $envvar
 ##        puts stdout "exists $ex"
 ##    }
     puts stdout "B"
     puts stdout "FOO exists and is $env(\"FOO\")"
 } else {
     puts stdout "FOO does not exist"
 }
 """
 )

The output has:

 A
 ex 0
 ex 1

which shows the difference between [info exists env("FOO")]
and tyour less correct [info exists env(FOO)].

I am a TCL neophyte and haven’t figured out exactly what env(FOO)
actually does but I don’t think it is doing what you wanted.

Also, you don’t need aoo those [] around the puts and other commands
:slight_smile:

Also, both our scripts show that the TCL eval environment is given an
env array based on Python’s os.environ array at the time of call,
and my remarks about it being a copy are not relevant. Though I imagine
that modifying env inside TCL won’t affect os.environ (but I haven’t
tested that, either).

Cheers,
Cameron Simpson cs@cskk.id.au

Thanks again Cameron. After some more exhaustive testing the best way I have found is sync up the python and tcl environments is to synchronize calls to os.environ.pop("VARNAME") with tcl.call("unset", "env(VARNAME)") and os.environ.__setitem__["VARNAME"] = VALUE with tcl.call("set", env(VARNAME), VALUE).

As to quotes, I would double check your assertion that you want to call ::env("VARNAME") vs. ::env(VARNAME). See env

Thanks again Cameron. After some more exhaustive testing the best way
I have found is sync up the python and tcl environments is to
synchronize calls to os.environ.pop("VARNAME") with
tcl.call("unset", "env(VARNAME)") and
os.environ.__setitem__["VARNAME"] = VALUE with tcl.call("set", env(VARNAME), VALUE).

Maybe. But it seems like they were in sync anyway in my tests.

As to quotes, I would double check your assertion that you want to call ::env("VARNAME") vs. ::env(VARNAME). See https://wiki.tcl-lang.org/page/env

It was based on this code:

 set ex [info exists env("FOO")]
 puts stdout "ex $ex"
 set ex [info exists env(FOO)]
 puts stdout "ex $ex"

which was printing:

 ex 0
 ex 1

which suggested that the quoted version was doing the lookup you needed,
and the unquoted one was doing… something else.

As you say, the URL you cite suggests a bare FOO should work, unless
there’s a rendering problem :frowning: Note also the $::env notation to refer
to the global env array.

Naively I’d expect a bare FOO to be a reference to a variable name,
itself containg the key to use with $::env, but I’m not up to speed
with TCL.

Cheers,
Cameron Simpson cs@cskk.id.au