Home > Software design >  Python: cannot import ... (most likely due to a circular import)
Python: cannot import ... (most likely due to a circular import)

Time:10-29

I am trying to create simple app with multiple screens. Here is my project structure.

 main.py
 - screens
     mainScreen.py
     regressions.py

What i want to do is basically switch between them which i cant because of ImportError: cannot import name 'MainScreen' from partially initialized module 'screens.mainScreen' (most likely due to a circular import) I think i understand what problem is but i dont know how to overcome it.

Here is my code
main.py

import tkinter as tk
from screens.mainScreen import MainScreen

root = tk.Tk()

run = MainScreen(root)
root.mainloop()

regressions.py

import tkinter as tk
from .mainScreen import MainScreen

class Regressions:

    def __init__(self, master):

        # keep `root` in `self.master`
        self.master = master

        self.label = tk.Button(self.master, text="Home", command=self.load_new)
        self.label.pack()

    def load_new(self):
        self.label.destroy()

        # use `root` with another class
        self.another = MainScreen(self.master)

mainScreen.py

import tkinter as tk
from .regressions import Regressions

class MainScreen:

    def __init__(self, master):

        # keep `root` in `self.master`
        self.master = master 

        self.label = tk.Button(self.master, text="Regressions", command=self.load_new)
        self.label.pack()

    def load_new(self):
        self.label.destroy()

        # use `root` with another class
        self.another = Regressions(self.master)

mainScreen.py is same as regressions.py but redirects to regressions.py I will be thankful for any hints how to solve it.

CodePudding user response:

The problem is, as stated in the error message, a circular import issue.

regressions.py

from .mainScreen import MainScreen

mainScreen.py

from .regressions import Regressions

regressions imports mainScreen and mainScreen imports regressions

You have to find a way to remove this dependency.

Maybe this could help: Python circular import in custom package and init.py

CodePudding user response:

I would organize it in differen way.

In main.py I would first create MainScreen and Regressions and later assign classes to .another

And then you don't have to import MainScreen in regressions.py and Regressions in MainScreen.py because all it done in main.py

# create screens
main_frame = MainScreen(root)
regressions_frame = Regressions(root)

# assing screens to other screens
main_frame.another = regressions_frame
regressions_frame.another = main_fram

But this may need different method to show it.

I would use Frame to create screens and later I could use .pack() to show screen in window , and .pack_forget() to remove previous screen from window.

It is also good idea because it doesn't destroy screen so it has all old values - so if you display again the same screen then you still have old values (in Entry, Checkbox, etc.) and Regressions may still access values which are in MainScreen, and MainScreen can access values which are in Regressions

mainScreen.py

import tkinter as tk

class MainScreen(tk.Frame):

    def __init__(self, master):
        super().__init__(master)

        # keep `root` in `self.master`
        #self.master = master   # super().__init__(master) should do it automatically

        self.label = tk.Button(self, text="Regressions", command=self.load_new)
        self.label.pack()

        self.another = None

    def load_new(self):
        if self.another:
            # hide current screen (without destroying)
            self.pack_forget()
    
            # show another screen
            self.another.pack()

regressions.py

import tkinter as tk

class Regressions(tk.Frame):

    def __init__(self, master):
        super().__init__(master)

        # keep `root` in `self.master`
        #self.master = master   # super().__init__(master) should do it automatically

        self.label = tk.Button(self, text="Home", command=self.load_new)
        self.label.pack()
        
        self.another = None

    def load_new(self):
        if self.another:
            # hide current screen (without destroying)
            self.pack_forget()
    
            # show another screen
            self.another.pack()

main.py

import tkinter as tk
from .mainScreen import MainScreen
from .regressions import Regressions

if __name__ == '__main__':
    root = tk.Tk()

    # create screens
    main_frame = MainScreen(root)
    regressions_frame = Regressions(root)

    # assing screens to other screens
    main_frame.another = regressions_frame
    regressions_frame.another = main_frame
    
    # show main_frame
    main_frame.pack()
    
    root.mainloop()

CodePudding user response:

you should use if __name__=='__main__': before import the module to avoid circular import

regressions.py

import tkinter as tk


if __name__=='__main__':
   from .mainScreen import MainScreen

class Regressions:

    def __init__(self, master):

        # keep `root` in `self.master`
        self.master = master

        self.label = tk.Button(self.master, text="Home", command=self.load_new)
        self.label.pack()

    def load_new(self):
        self.label.destroy()

        # use `root` with another class
        self.another = MainScreen(self.master)

mainScreen.py

import tkinter as tk

if __name__=='__main__':
   from .regressions import Regressions

class MainScreen:

    def __init__(self, master):

        # keep `root` in `self.master`
        self.master = master

        self.label = tk.Button(self.master, text="Regressions", command=self.load_new)
        self.label.pack()

    def load_new(self):
        self.label.destroy()

        # use `root` with another class
        self.another = Regressions(self.master)

BTW:

Here more complex version created 10 years ago by Bryan Oakley.

Switch between two frames in tkinter

You can see this method reused in many questions on Stackoverflow.

  • Related