This is one of those things that still catches me out - perhaps because I don't code frequently enough to avoid it. Luckily, by now, my very confusion usually points me to the solution.
So, the situation is this:
- I have a working GUI design done in Python Tkinter
- I add an extra frame and some widgets
- at least one of those widgets is a combobox
- as I write the code for the combobox I make a starting idea of what values should be selectable
- I write the code, run the application and lo, I see the combobox looking and acting as it should
- I choose one of those values to be the default for the combo box
- I add a line of code to set the combobox to the default value
- I run the application, but the combobox is still initially blank
- cue me being very confused and asking what is going on?
Here's the actual example. As per usual with Tkinter, first I define the widget - although in the case of a combobox I precede it with a Tkinter string variable to hold the selected value and tell the combobox to use it for the
t02_str_shell = m_tkntr.StringVar()
t02_cb_shell = m_tkntr.ttk.Combobox( t02_frame91, width = 20, textvariable = t02_str_shell)
t02_cb_shell['values'] = ( "Bash", "MS-DOS", "PowerShell" )
and follow that with a placement action - here I'm using the "pack" method.
t02_cb_shell.pack( side = m_tkntr.LEFT)
And here is the part where I add a setting of the default value - note that I do this by assigning to the Tkinter string variable that I had set up.
t02_str_shell.set( "Bash" )
And yet, as already described, this doesn't seem to work.
The short answer is:
- Python's aggressive garbage collection is the culprit !
In slightly deeper terms: there is nothing actually wrong with the code that I wrote. The statement settings the default value really does do that. However, as I've not yet written anything to use that value, the garbage collector immediately knows that the assignment to the Tkinter string variable was the only mention that it ever gets. By the logic of aggressive garbage collection that means it can be discarded - and not some time later, it will happen immediately.
Needless to say, when I first encountered this phenomenon it drove me bonkers - but, thankfully I did eventually work it out.
In practical terms, the solution is that when going through the steps of adding Tkinter elements to a design, be sure to always write some usage of the value somewhere else in the code.
For example, here is the line that I added - to a
def elsewhere inside the scope of my wider Tkinker code - that then let me see the GUI acting as I want it to.
i_str_shell = t02_str_shell.get()
Compared to compiled procedural programming, Python can be a weird beast at times. The question of quite where else I needed to put the code that would use my
t02_str_shell variable can be a little hard to describe.
For example, note that when I say:
t02_str_shellvariable I really should be saying:
t02_str_shellTkinter String variable because this makes clear what run-time presence the variable will really have.
The Python garbage collector is a run-time actor and the conception you have of a variable as type in your source code, needs to always be projecting forward in time to what will happen when the code runs.
As an aside, this touches on a whole other topic about understanding a language like Python. For me personally, this is a bit ironic. I learned to program on the assumption of compiled software, so for the most part, the run-time conception was closely tied to exactly what my source code specified. I did therefore, have to understand well quite what the run-time actions of my code would be. When I would then occasionally use interpreted languages, I mostly understood them as late-in-the-day compilers. In both cases, for many of the things that most "dynamic" languages now do, I had to learn to learn to do them myself - e.g. monitoring stack and heap usages and bullet-proofing my use of pointers to ensure there were no (literal) loose ends.
- The paradox therefore, is that where a language like Python includes a garbage collector for the "objects" it is managing pointers to, I can readily imagine it as like the code I used to write to manually do the same thing. But, that very need to imagine is my Achilles heel, because I'm not very good at just taking the garbage collection for granted. In my head, by a default so implicit I'm barely conscious of it, it I set a value to a variable then that variable continues to exist in the scope even if I don't ever use it again.
- Yes, this is the "If a tree falls in a forest" thought experiment where the tree really made a sound (rather than Schrödinger's cat where the question is only settled by actually using the variable).
As this is Tkinter we're talking about, it helps to remember that Tkinter is not itself written in Python, so we're somewhat talking about both Tkinter and its connection/realisation in CPython.
To be a little more existential, this raises the (perhaps pointless) question of whether it was Tkinter that garbage collected my absent combobox initial value, or if it was Python that decided I was never going to use the Tkinter String variable and so it never sent it to the Tkinter non-in-Python library.
The distinction is moot as either way, the solution was to add a line of Python code.