1

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.

  • "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)" Yes, this is the problem. a 'dumb' way to fix this is to merge both those modules into one. This may be a controversial opinion, but monster modules can be the lesser evil. you can also move imports to the bottom. – juanpa.arrivillaga May 31 '19 at 18:51
  • python class definitions are executed dynamically. types are resolved at runtime. this is why python doesn't work well with circular dependencies in class definitions. you can think of a class definition as syntactic sugar around calling the class object constructor, the *metaclass* `type('MyClass', (object,), {'__init__': lambda self: print("I'm a MyClass instance")})()` – juanpa.arrivillaga May 31 '19 at 18:59

1 Answers1

0

This code is really hard to untangle, but I think the root of the problem is that you are trying to use inheritance to share data between classes. That is not what inheritance is for.

When B inherits from A, that doesn't mean that B shares A's data, it means that B is an exact copy of A with some enhancements. In effect, you have two A's - the original, and a new one that has some additional features.

For example, you have a class named Tarca_Gui. This appears to be the main class. It has a status bar, a menu bar, and so on. When you inherit from Tarca_Gui, that new class also has a status bar, a menu bar, and so on. The two classes don't share any data.

Instead of inheritance, you need to be using composition. The difference is that, with inheritance Menu_Bar is a Tarca_Gui, but what you really want is for Tarca_Gui to have a Menu_Bar.

It's impossible for me to fix your code since you didn't provide a minimal reproducible example, but the first thing you need to do is remove Tarca_Gui from the definition of each class. Then, pass the instance of Tarca_Gui to the various other objects.

In other words:

class Tarca_Gui():
    def __init__(self, master):
        ...
        self.menu_bar = Menu_Bar(self)
        self.header_frame.Header_Frame(self)
        self.input_frame = Input_Frame(self)
        self.output_frame = Output_Frame(self)
        self.status_frame = Status_Frame(self)
        ...

class Menu_Bar():
    def __init__(self, tarca_gui):
        self.tarca_gui = tarca_gui
        ...
class Progress_Bar():
    def __init__(self, tarca_gui):
        self.tarca_gui = tarca_gui
        ...
class Header_Frame():
    def __init__(self, tarca_gui):
        self.tarca_gui = tarca_gui
        ...
... and so on ...

Then, inside each of those objects, if they need something that is managed by Tarca_Gui, they can use self.tarca_gui to reference it. For example, if you need access to the master widget you could use self.tarca_gui.master.

Similarly, nothing should be inheriting from Menu_Bar or Input_Frame or Settings_Frame.

For more information about inheritance vs composition, see Difference between Inheritance and Composition

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Hi Bryan, thank you for your response. You are correct in my "misapplication" of inheritance, it is most likely a product of having to learn a new language every 4 months and never getting to actually implement anything at an advanced level (I knew how OOP worked when I used it 18 months ago. I didn't even realize I had forgotten, thank you). Oh, I didn't provide a minimal reproducible example because I thought people would want to see the structure of the entire solution, which is why I provided the GitHub link. I will try to do something different next time. – Benjamin Kleynhans Jun 03 '19 at 12:05