Executing a Button command from another file?
Asked Answered
O

2

2

I have started working on a GUI system where I need to import a function from one file to be executed in the main file when a button is pressed, but every time I run it I get:

AttributeError: partially initialized module 'Two' has no attribute 'sum'
  (most likely due to a circular import)

The program is supposed to input two values, Value_a and Value_b, and the function being called sum() is supposed to add the two and output a result in a new window. Here is an example of the file I want to import and its function sum():

Two.py:

from tkinter import *  #Import the tkinter module    
import One #This is the main file, One.py

def sum():
    newWindow = Toplevel(One.Window)
    newWindow.title("Sum")
    a = int(One.Value_a.get())
    b = int(One.Value_b.get())
    c = a+b
    Label(newWindow, text= str(c)).grid(row=1, column=0)

This is how the main file looks like:

One.py:

from tkinter import *
import Two

Window = Tk()
Window.title("Main Window")

Value_a = Entry(Window, width=15).grid(row=1, column=0)
Value_b = Entry(Window, width=15).grid(row=2, column=0)
my_button = Button(Window, text="Test", command=lambda: Two.sum).grid(row=3, column=0)

Window.mainloop()

When this is run, I end up getting the above error.

Ovoviviparous answered 19/3, 2021 at 6:5 Comment(2)
Importing One inside Two.py will create another instance of Tk (One.Window), not using the instance of Tk (Window) when running One.py. You need to pass Window, Value_a and Value_b to Two.sum() instead and don't import One inside Two.py.Indigence
@acw1668: It's not quite that easy. See answer I posted.Unshakable
U
2

The problem is because you really do have a circular import. Module One imports module Two which imports module One... etc. However the simple fix @acw1668 suggested isn't enough to fix things because the Two module references more than just the Window attribute of the One module. My solution passes the things in module One the function in module Two needs as arguments (so the Two module doesn't need to import it to get access to them).

Another problem with your tkinter code is discussed in the question Tkinter: AttributeError: NoneType object has no attribute , which I suggest you read.

Below are changes to both your modules that fix all these issues.

One.py:

from tkinter import *
import Two


Window = Tk()
Window.title("Main Window")

Value_a = Entry(Window, width=15)
Value_a.grid(row=1, column=0)
Value_b = Entry(Window, width=15)
Value_b.grid(row=2, column=0)

my_button = Button(Window, text="Test",
                   command=lambda: Two.sum(Window, Value_a, Value_b))
my_button.grid(row=3, column=0)

Window.mainloop()

Two.py:

from tkinter import *


def sum(Window, Value_a, Value_b):
    newWindow = Toplevel(Window)
    newWindow.title("Sum")
    a = int(Value_a.get())
    b = int(Value_b.get())
    c = a+b
    Label(newWindow, text= str(c)).grid(row=1, column=0)
Unshakable answered 19/3, 2021 at 7:19 Comment(6)
I would prefer using class in One.py and pass the instance of the class to Two.sum().Indigence
@acw1668: That would work of course, but it would be even simpler to simply pass multiple arguments to sum().Unshakable
@acw1668: Since it's so much simpler, I've modified my answer to do it like that — the whole sys.modules approach was just too weird…Unshakable
Although it is simpler, but if later Two.sum() needs to refer another widget then the function definition is required to be changed. Your original design (or my suggestion of using class instance) need not to change the definition of Two.sum(), only need to change the content of the function.Indigence
@martineau: Thanks for the solution. The program works as I wanted it thanks to your adjustment on the code. Thanks.Ovoviviparous
@acw1668: After a little research, I decided passing the module wasn't too unusual and found a slightly better way of getting a reference to it — see the other answer I've posted.Unshakable
U
0

(Here's another answer which is very similar to what I initially had in my other answer before doing something I felt was simpler. I'm posting it as separate answer because it turns out that the idea of a module getting a reference to itself has been around for a very long time, so doing so isn't that weird or as hackish as I initially thought.)

The problem is because you really do have a circular import. Module One imports module Two which imports module One... etc. However the simple fix @acw1668 suggested isn't enough to fix things because the Two module references more than just the Window attribute of the One module. My solution passes the whole One module to the function as an argument (so the Two module doesn't need to import it to get access to its attributes).

Another problem with your tkinter code is discussed in the question Tkinter: AttributeError: NoneType object has no attribute , which I suggest you read.

Below are changes to both your modules that fix all these issues. To avoid the circular import, the Button command now passes the calling module as an argument to the sum() function in module Two. While doing something like this is somewhat unusual, in fact is a very logical if you think about it (and want to avoid a circular import`).

One.py:

from tkinter import *
import Two

CURRENT_MODULE = __import__(__name__)

Window = Tk()
Window.title("Main Window")

Value_a = Entry(Window, width=15)
Value_a.grid(row=1, column=0)
Value_b = Entry(Window, width=15)
Value_b.grid(row=2, column=0)

my_button = Button(Window, text="Test", command=lambda: Two.sum(CURRENT_MODULE))
my_button.grid(row=3, column=0)

Window.mainloop()

Two.py:

from tkinter import *

def sum(One):
    newWindow = Toplevel(One.Window)
    newWindow.title("Sum")
    a = int(One.Value_a.get())
    b = int(One.Value_b.get())
    c = a+b
    Label(newWindow, text= str(c)).grid(row=1, column=0)
Unshakable answered 19/3, 2021 at 17:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.