Let's be blunt: sometimes, coding is just tedious. It is tedious and requires an unglamorous commitment to working through something consistently.
The classic situation for this, is where you want to achieve some kind of uniformity so as to make later changes less bothersome. Even though, there's some chance that you will never end up making another change. That's merely one of the paradoxes of good coding - or at least, what I consider "good coding" to mean.
This is the companion post for the video: TechoCodger - Walkthrough Video 006
The Example
I wasn't happy with how fiddly a particular pattern inside my program was proving to be. This revolved around the instances of a combination of:
- a drop-down aka pull-down menu control
- the values displayed on that control versus the internal representation of the value.
I had an idea that there was a better way to integrate those two things so that they both referenced the same internal concept. I did some experiments and found a solution that I liked - but it would require rewriting code every single place where the original mixed-concept was used so that it could be replaced by using the new concept.
But, this "internal concept" is itself only an archetype - throughout the program there were multiple similar combinations that I wanted to change over from the old method to the new style.
The purpose of this article is to walk through the example of making that change for the initial instance - and in such a way that we're ready to do the same for the other instances. It is about accepting that it will be tedious, and finding a good-enough way to work through the whole process.
As an extra requirement, I want:
- to always be able to run the current code;
- to be able to quickly flick a switch between using the old code and the new;
- to keep all the multiple "flick a switch" options viable until the very last instance has been achieved.
The reasoning behind that last point is that my initial "new method" was based on making something "good enough" for the first instance. I won't really know it is good enough for all of the instances until the last has been done.
What that means in Python code
The example comes from work on the program Foldatry.
- btw this is not a veiled promotion for that program, which I only consider to be a prototype, something written for my own use, and gets used as an example simply because the idea for this article came during work on its code.
So, for this program I'm writing, I recently made two coincident changes:
- changed the definition and features for an Enumeration type (in the module "commontry")
- made a reworked "Combobox" control by making a new class based on the Tkinter Combobox control.
With those as available new features, I then wrote and proved successful the usage of them.
For this walk-through, we'll take it as read that there is an Enumeration defined as:
@unique
class DeletionMode(Enum):
DryRun = "DelMod_Dry"
DeleteNow = "DelMod_Delete"
Bash = "DelMod_Bash"
The enumeration is supported by some additional functions, but we'll let those be mentioned in context as they arise.
What I will show here is how I implemented the first instance, but with a mind to being a process I knew I would have to repeat for similar adaptations.
Preparing
As the basis for the change will include
I could search for DeletionMode_GetTuple
t01_str_prunatry_mode = m_tkntr.StringVar()
t01_cb_prunatry_modes = m_tkntr_ttk.Combobox( t01_frame11, state="readonly", width = 20, textvariable = t01_str_prunatry_mode)
t01_cb_prunatry_modes['values'] = cmntry.DeletionMode_GetTuple()
t01_str_prunatry_mode.set( cmntry.DeletionMode_Default_Value() )
t01_cb_prunatry_modes.pack( side = m_tkntr.LEFT, padx=tk_padx(), pady=tk_pady() )
The code that will replace this, will be to create a Combobox object from the new type PairTupleCombobox
with its associated calls to new features for the Enumeration class ( DeletionMode_LstPairTpls
and DeletionMode_Default
)
i.e.
t01_cb_prunatry_mode = PairTupleCombobox( t01_frame11, cmntry.DeletionMode_LstPairTpls(), cmntry.DeletionMode_Default(), state="readonly", width = 50)
t01_cb_prunatry_mode.pack( side = m_tkntr.LEFT, padx=tk_padx(), pady=tk_pady() )
But, as an at-home amateur coder, I want my code to keep working as is until I'm ready to throw a switch and then test the replacements.
So what I do is:
- leave the old aspects of the Enumeration intact and merely add the new support functions
- bracket the changes I make in Foldatry with boolean flags.
t_t01_old_combo_DeletionMode = True
if t_t01_old_combo_DeletionMode :
# the old code
else :
# the new code
Now, one detail to note here. In most other languages I would add my boolean control flag: t_t01_old_combo_DeletionMode
at a global level. That way when it comes time to "throw the swtich" I just change a single True
to a False
. For reasons I won't go into, I don't trust the scoping of Python to be clear enough that I want to do that here. ([^1])
So what I will do - seeing as these control switches are all going to be temporary - is to always have the line pair:
t_t01_old_combo_DeletionMode = True
if t_t01_old_combo_DeletionMode :
in every single place where I'm making these changes. In practice it will merely mean that I will perform the changeover - from True to False - using a "replace all" in my editor instead of editing a single line. It's not a big deal.
First Phase - Locate and Insert If Blocks
The first time through, what I am doing is finding all the places where there will need to be a controllable switch between old and new methods.
For each I will insert the line pair before the existing code and put an empty else :
clause after the existing code. Here is the first case:
t_t01_old_combo_DeletionMode = True
if t_t01_old_combo_DeletionMode :
setting_value = cmntry.DeletionMode_EnumOfString( t01_str_prunatry_mode.get() )
else :
pass
The orginal was just the line: setting_value = cmntry.DeletionMode_EnumOfString( t01_str_prunatry_mode.get() )
Note that Python syntax requires a pass
otherwise the else
will cause an error.
Here is the next one:
t_t01_old_combo_DeletionMode = True
if t_t01_old_combo_DeletionMode :
t01_str_prunatry_mode.set( cmntry.DeletionMode_EnumValueString( val ) )
else :
pass
We can repeat these edits, mainly ignoring what the line actually does or needs.
t_t01_old_combo_DeletionMode = True
if t_t01_old_combo_DeletionMode :
t_prunatry_mode_show = t01_str_prunatry_mode.get()
else :
pass
Another,
t_t01_old_combo_DeletionMode = True
if t_t01_old_combo_DeletionMode :
t_prunatry_mode_ok = t01_str_prunatry_mode.get() in cmntry.DeletionMode_GetList()
else :
pass
and another - we will come back to what each these really does, when we come around again to write the replacements.
t_t01_old_combo_DeletionMode = True
if t_t01_old_combo_DeletionMode :
i_enum_prunatry_mode = cmntry.DeletionMode_EnumOfString( t01_str_prunatry_mode.get() )
else :
pass
Finally - due to the way I like to structure my Tkinter code, the actual creation of the Tkinger GUI objects comes last - so here we see the creation of the Combobox GUI control.
t_t01_old_combo_DeletionMode = True
if t_t01_old_combo_DeletionMode :
t01_str_prunatry_mode = m_tkntr.StringVar()
t01_cb_prunatry_modes = m_tkntr_ttk.Combobox( t01_frame11, state="readonly", width = 20, textvariable = t01_str_prunatry_mode)
t01_cb_prunatry_modes['values'] = cmntry.DeletionMode_GetTuple()
t01_str_prunatry_mode.set( cmntry.DeletionMode_Default_Value() )
t01_cb_prunatry_modes.pack( side = m_tkntr.LEFT, padx=tk_padx(), pady=tk_pady() )
else :
pass
To state the obvious, all of those if
statements are set to evaluate as True
and thereby the code at run time remains unaffected.
Second Phase - Write In Else Clauses
Having identified all the places where all the replacements will need to be inserted, we can now work through the process of writing them all.
Here is the first one. This was about reading the state of the Combobox for then saving settings into a configuration file.
t_t01_old_combo_DeletionMode = False
if t_t01_old_combo_DeletionMode :
setting_value = cmntry.DeletionMode_EnumOfString( t01_str_prunatry_mode.get() )
else :
setting_value = t01_cb_prunatry_mode.getSelectedKey()
The second. This was about using settings that had been loaded from a configuration file to reset the Combobox.
t_t01_old_combo_DeletionMode = False
if t_t01_old_combo_DeletionMode :
t01_str_prunatry_mode.set( cmntry.DeletionMode_EnumValueString( val ) )
else :
t01_cb_prunatry_mode.setSelectedKey( val )
The third - more about this later. This was just to have a string to display in the status window and logs.
t_t01_old_combo_DeletionMode = False
if t_t01_old_combo_DeletionMode :
t_prunatry_mode_show = t01_str_prunatry_mode.get()
else :
t_prunatry_mode_show = cmntry.DeletionMode_String( t01_cb_prunatry_mode.getSelectedKey() )
The fourth. This was to feed a Boolean check of whether an acceptable value had been set in the Combobox. As it happens, the change to the new type of Combobox means that this check is unnecessary, but for now we'll not tackle removing this part.
t_t01_old_combo_DeletionMode = False
if t_t01_old_combo_DeletionMode :
t_prunatry_mode_ok = t01_str_prunatry_mode.get() in cmntry.DeletionMode_GetList()
else :
t_prunatry_mode_ok = t01_cb_prunatry_mode.getSelectedKey() in cmntry.DeletionMode
The fifth. This is where the actual mode value - as an Enumeration - has to be pulled from the Combobox. In the old method I was getting a string from the Combobox and then having to call a function to get the matching Enumeration. In the replacement I directly get an Enumeration from the new class object.
t_t01_old_combo_DeletionMode = False
if t_t01_old_combo_DeletionMode :
i_enum_prunatry_mode = cmntry.DeletionMode_EnumOfString( t01_str_prunatry_mode.get() )
else :
i_enum_prunatry_mode = t01_cb_prunatry_mode.getSelectedKey()
Finally, here is where the actual Combobox control gets created.
t_t01_old_combo_DeletionMode = False
if t_t01_old_combo_DeletionMode :
t01_str_prunatry_mode = m_tkntr.StringVar()
t01_cb_prunatry_modes = m_tkntr_ttk.Combobox( t01_frame11, state="readonly", width = 20, textvariable = t01_str_prunatry_mode)
t01_cb_prunatry_modes['values'] = cmntry.DeletionMode_GetTuple()
t01_str_prunatry_mode.set( cmntry.DeletionMode_Default_Value() )
t01_cb_prunatry_modes.pack( side = m_tkntr.LEFT, padx=tk_padx(), pady=tk_pady() )
else :
t01_cb_prunatry_mode = PairTupleCombobox( t01_frame11, cmntry.DeletionMode_LstPairTpls(), cmntry.DeletionMode_Default(), state="readonly", width = 50)
t01_cb_prunatry_mode.pack( side = m_tkntr.LEFT, padx=tk_padx(), pady=tk_pady() )
Complexities
Some those changes were slightly more complex than they might seem.
For example, in the third example above, the original was a one-line with a call to fetch the visible value of the Combobox, which ironically is no longer a valid thing to do with the new Combox
t02_label_paths_process_status.configure( text = "Abandoned processing in mode:" + t01_str_prunatry_mode.get())
In which case, one apparent way to tackle this is to replicate the configuring of the label for True and False - i.e.
t_t01_old_combo_DeletionMode = False
if t_t01_old_combo_DeletionMode :
t02_label_paths_process_status.configure( text = "Abandoned processing in mode:" + t01_str_prunatry_mode.get())
else :
t02_label_paths_process_status.configure( text = "Abandoned processing in mode:" + cmntry.DeletionMode_String( t01_cb_prunatry_mode.getSelectedKey() )
But I didn't like the feel of doing that, so I reworked it to set an interim variable as the thing governed by the if
and then have the label configuration appear only once.
Hence, first doing this:
t_t01_old_combo_DeletionMode = False
if t_t01_old_combo_DeletionMode :
t_prunatry_mode_show = t01_str_prunatry_mode.get()
else :
t_prunatry_mode_show = cmntry.DeletionMode_String( t01_cb_prunatry_mode.getSelectedKey() )
and then doing this:
t02_label_paths_process_status.configure( text = "Abandoned processing in mode:" + t_prunatry_mode_show )
Now you might (rightly) ask: why bother with such sophistries when this is all code that will eventually be deleted after all the changes prove themselves successful?
To me, this is about establishing and holding various "good routine practices". And the answer to the "why?" question is that I'm going to have quite a few of these sequences of changes to work through. I'm therefore interested in using a method where there is less that can go wrong.
This is one of many coding paradoxes - extra work to reduce risk, or to reduce future extra work - and I assume that all coders go about these in ways that suit themselves. I can tell you what I do and why I do it, but not whether you should also do it.
Footnotes:
([^1]: It is perhaps more accurate to say I don't trust my clarity about the scoping rationale in Python. This being a problem of writing in multiple languages.
Top comments (0)