Introduction
I am a student at university learning programming. My courses focus on Java, C# and C++ so I have some experience with OOP. Currently I'm busy with my co-op and the requirement is to create a GUI using Python and tkinter (both of which I'm learning as I go).
My problem is related specifically to Python OOP and inheritance across multiple files in multiple directories and I simply can't figure it out (its been 3 days)
If you don't want to go through multiple lines of shortened files and code examples, you can find the actual code here:
https://github.com/bkleynhans/Landsat-Buoy-Calibration.git
Then go to the bottom of the page to get to the jist of the question.
You will not be able to run the calibration system unless you have ModTran installed, which is an expensive licensed software. The GUI however should be fully operational with the only non-standard requirement being tkcalendar.
What I've Done
I've worked through the threads linked below as well as the links included in both by other members.
ImportError: cannot import name
ImportError: Cannot import name X
as well as
https://www.digitalocean.com/community/tutorials/understanding-class-inheritance-in-python-3
https://www.python-course.eu/python3_inheritance.php
https://chrisyeh96.github.io/2017/08/08/definitive-guide-python-imports.html
Unfortunately I can't seem to relate them to my problem whether it be because I'm not knowledgeable enough or because I'm just slow.
Folder Structure
My folder structure is as follows:
Z:.
│
│ tarca_gui
│
└───gui
│
│ __init__.py
│ tarca_gui.py
│
│
└───forms
│
│ input_notebook.py
│ status_frame.py
│ settings_frame.py
│ example_date_picker_supplied.py
│ settings_notebook.py
│ input_frame.py
│ help_menu.py
│ header_frame.py
│ progress_bar.py
│ __init__.py
│ menu_bar.py
│ example_date_picker.py
│ output_frame.py
│
└─
File Summaries
tarca_gui in the root is simply a bash script that changes into the working directory and executes the gui
cd ~/Landsat-Buoy-Calibration/gui
python3 tarca_gui.py
Placing the files and their code in here would be extensive clutter, however I'll paste the headers and structures. A GitHub link to the project is in the introduction section above.
Required Non-standard Libraries: tkcalendar - https://pypi.org/project/tkcalendar/
tarca_gui.py - Launches the program from a linux terminal and builds the interface using tkinter. All the other files are derived based on the following structure
The Base Class
tarca_gui.py
from tkinter import *
from tkinter import messagebox
import inspect
import sys
import os
import pdb
class Tarca_Gui:
def __init__(self, master):
# Import gui paths
from forms import progress_bar
from forms import menu_bar
from forms import header_frame
from forms import input_frame
from forms import output_frame
from forms import status_frame
# Create the root Tkinter object
master.title('CIS Top Of Atmosphere Radiance Calibration System')
master.geometry('800x600')
master.resizable(False, False)
#master.configure(background = '#FFFFFF')
master.option_add('*tearOff', False)
# Create the Progressbar window - accessed via master.progressbar_window.progress_bar
progress_bar.Progress_Bar(master)
# Create the Menubar - accessed via master.menu_bar
menu_bar.Menu_Bar(master)
# Create the Header - accessed via master.header_frame
header_frame.Header_Frame(master)
# Create the Input Frame - accessed via master.
input_frame.Input_Frame(master)
# Create the Input Frame - accessed via master.
output_frame.Output_Frame(master)
# Create the Input Frame - accessed via master.
status_frame.Status_Frame(master)
# Calculate fully qualified path to location of program execution
def get_module_path():
filename = inspect.getfile(inspect.currentframe())
path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
return path, filename
# Set environment variables to locate current execution path
def set_path_variables():
path, filename = get_module_path()
# find the Calibration program path
path_index = path.rfind('/')
# append gui paths
sys.path.append(path[:path_index])
sys.path.append(path)
sys.path.append(path + "/forms")
def on_closing(root):
if messagebox.askyesno("Quit", "Do you really wish to quit?"):
root.destroy()
def main():
set_path_variables()
root = Tk()
root.protocol("WM_DELETE_WINDOW", lambda: on_closing(root))
tarca_gui = Tarca_Gui(root)
root.mainloop()
if __name__ == "__main__": main()
progress_bar, menu_bar, header_frame, input_frame, output_frame and status frame are derived from (extend) targa_gui
Summaries of Derived Classes
progress_bar.py extends tarca_gui.py
from tkinter import *
from tkinter import ttk
import tarca_gui
class Progress_Bar(tarca_gui.Tarca_Gui):
def __init__(self, master):
menu_bar.py extends tarca_gui.py
from tkinter import *
from tkinter import ttk
import tarca_gui
import help_menu
from gui.forms import settings_frame
class Menu_Bar(tarca_gui.Tarca_Gui):
def __init__(self, master):
header_frame.py extends tarca_gui.py
from tkinter import *
from tkinter import ttk
from forms import input_frame
class Header_Frame(input_frame.Input_Frame):
def __init__(self, master):
input_frame.py extends tarca_gui.py
from tkinter import *
from tkinter import ttk
import tarca_gui
from gui.forms import input_notebook
class Input_Frame(tarca_gui.Tarca_Gui):
def __init__(self, master):
output_frame.py extends tarca_gui.py
from tkinter import *
from tkinter import ttk
import tarca_gui
class Output_Frame(tarca_gui.Tarca_Gui):
def __init__(self, master):
status_frame.py extends tarca_gui.py
from tkinter import *
from tkinter import ttk
import tarca_gui
class Status_Frame(tarca_gui.Tarca_Gui):
def __init__(self, master):
The remaining classes: help_menu, settings_frame, settings_notebook, header_frame and input_notebook are derived from (extend) other classes
header_frame.py extends input_frame.py
from tkinter import *
from tkinter import ttk
from forms import input_frame.py
import pdb
class Header_Frame(input_frame.Input_Frame):
def __init__(self, master):
input_notebook.py extends input_frame.py
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
from gui.forms import input_frame
from tkcalendar import Calendar, DateEntry
from datetime import date
class Input_Notebook(input_frame.Input_Frame):
def __init__(self, master):
help_menu.py extends menu_bar.py
from tkinter import *
from tkinter import ttk
import time
import threading
import menu_bar
class Help_Menu(menu_bar.Menu_Bar):
def __init__(self, master):
settings_frame.py extends settings_notebook.py
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
from gui.forms import menu_bar
from gui.forms import settings_notebook
class Settings_Frame(menu_bar.Menu_Bar):
def __init__(self, master):
settings_notebook.py extends settings_frame.py
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
from gui.forms import settings_frame
class Settings_Notebook(settings_frame.Settings_Frame):
def __init__(self, master):
The Problem and Question
Firstly, PYTHONPATH is updated when the gui is launched through functions you can view in the base class.
Secondly, I understand that currently the classes are not all imported in the same way, they were and have been changed in my efforts to find the problem and will be standardized as soon as the problem has been resolved.
The Problem
The entire GUI works fine until I try to import Settings_Frame into settings_notebook.py
Error received
% ./tarca_gui
Traceback (most recent call last):
File "tarca_gui.py", line 104, in <module>
if __name__ == "__main__": main()
File "tarca_gui.py", line 100, in main
tarca_gui = Tarca_Gui(root)
File "tarca_gui.py", line 31, in __init__
from forms import menu_bar
File "/cis/otherstu/bxk8027/Landsat-Buoy-Calibration/gui/forms/menu_bar.py", line 20, in <module>
import help_menu
File "/cis/otherstu/bxk8027/Landsat-Buoy-Calibration/gui/forms/help_menu.py", line 21, in <module>
import menu_bar
File "/cis/otherstu/bxk8027/Landsat-Buoy-Calibration/gui/forms/menu_bar.py", line 21, in <module>
from gui.forms import settings_frame
File "/cis/otherstu/bxk8027/Landsat-Buoy-Calibration/gui/forms/settings_frame.py", line 21, in <module>
from gui.forms import settings_notebook
File "/cis/otherstu/bxk8027/Landsat-Buoy-Calibration/gui/forms/settings_notebook.py", line 25, in <module>
class Settings_Notebook(settings_frame.Settings_Frame):
AttributeError: module 'gui.forms.settings_frame' has no attribute 'Settings_Frame'
Findings
I believe this to be due to circular imports because settings_frame imports settings_notebook and vica versa.
Confusion
I understand that circular references should be avoided where possible, however the way I currently understand Python OOP, I need to import settings_notebook into settings_frame in order to construct it, but at the same time I need to import settings_frame into settings_notebook in order to extend it (settings_notebook should be a child of settings_frame)
The Question
I don't know what I'm doing wrong.
- Is it import related, or is it the way that I structured my classes?
- If I am understanding the class structure (with imports) incorrectly, please explain what I did wrong so I can learn and not make the same mistake again.
- Any other suggestions pertaining to the could would be very much appreciated.
In Conclusion
If you made it this far, firstly, thank you very much.
As mentioned I'm at university and learning C++, C#, Java, Python, HTML, CSS, JavaScript, PHP, SQL, Unity and Unreal engine at the same time (sometimes I get confused with concepts between them), so any suggestions relating to any part of my code would be greatly appreciated.