Retrieving variable from another class

2019-03-03 13:47发布

问题:

I am programming a GUI using Tkinter. In one of the classes I have defined a variable (entry_filename) and would like to use it in another class. A part of the code is as follows:

class Loginpage(tk.Frame,Search):


    def __init__(self,parent,controller):
        tk.Frame.__init__(self,parent)
        self.controller=controller
        self.label_user=tk.Label(self, text="Username")
        self.label_user.grid(row=0, column=0)
        self.label_pass=tk.Label(self, text="Password")
        self.label_pass.grid(row=1, column=0)

        self.entry_user=tk.Entry(self)
        self.entry_user.focus_set()
        self.entry_user.grid(row=0, column=1)

        self.entry_pass=tk.Entry(self,show="*")
        self.entry_pass.grid(row=1, column=1)


        self.button=ttk.Button(self, text="Login",command= self.Logincheck)
        self.button.grid(columnspan=2)

    def Logincheck(self):
        global username
        global password
        try:
            username=self.entry_user.get()
            password=self.entry_pass.get()

            self.ssh = paramiko.SSHClient()
            self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            self.ssh.connect(server, username=username, password=password)#input your username&password
            button1 = ttk.Button(self, text="Click to Continue",command= lambda: self.controller.show_frame(Inputpage))
            button1.grid(columnspan=2)
        except:
            tm.showerror("Login error", "Incorrect username/password")


class Inputpage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller=controller

        self.filein_label=tk.Label(self,text="Input file name")
        self.filein_label.grid(row=0,column=0)

        self.entry_filename=tk.Entry(self)
        self.entry_filename.focus_set()
        self.entry_filename.grid(row=0,column=1)

        self.button1 = ttk.Button(self, text="Click to Continue",command= lambda: self.controller.show_frame(Graphpage))
        self.button1.grid(columnspan=2)


class Graphpage(tk.Frame,Inputpage):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller=controller
        self.label = tk.Label(self, text="Graph Page!", font=LARGE_FONT)
        self.label.pack(pady=10,padx=10)

        button1 = ttk.Button(self, text="Back to Input Page",command=lambda: self.controller.show_frame(Inputpage))
        button1.pack()

        filename=Inputpage.entry_filename.get()

The Graphpage calls the variable filename which is later used to create the graph (that part of the code is omitted here). When the code is run the following error is returned:
TypeError: Cannot create a consistent method resolution order (MRO) for bases Frame, Inputpage

It seems that I have hit another roadblock in attempting to solve the earlier issue, however, if I can understand the resolution to this, I hope that I can attempt to solve further issues. Thanks for your help

回答1:

ssh is a local variable inside function LoginCheck so you are not able to retrieve it from another class. One thing possible to do is to define ssh as self.ssh so it will be accessible through instance_of_Loginpage.ssh. It will work only when you will pass an instance of Loginpage into an instance of Graphpage. If you need access to an ssh connection from many places I suggest to create another class just to handle ssh (you can use Borg patter to achieve it).



回答2:

The culprit is that you should not share class member variables that way.

If different classes share some common data, that data is probably another class and they can inherit from it.

class CommonData():
    client = 100


class A(CommonData):
    def __init__(self):
        print(A.client)

class B(CommonData):
    def __init__(self):
        print(B.client)

a = A()
b = B()

CommonData.client = 300

print(a.client)
print(b.client)

In above case every instance of A and every instance of B share all the CommonData class variables, like client.

CommonData.client = 400

class C():
    pass

You can use multiple inheritance too. define all common data as CommonData attributes and use CommonData as a class to hold data, like in above example, don't create instances from it:

class D(C, CommonData):
    def __init__(self):
        print(D.client)

c = C()
d = D()

A simpler option would be to just define a variable CommonData in the outer scope and use it from anywhere:

common_data = 500

class A():
    def __init__(self):
        global common_data
        print(common_data)
        common_data = 200
        # ... 

But global variables are generally seen as a bad thing in a program as their use can become a problem for several reasons.

Yet another way is to pass the variable to the object initializer. That makes the instance to keep its own value copied from the creation value:

common_data = 600

class A():
    def __init__(self, data):
        self.common = data
        print(self.common)

a = A(common_data)
common_data = 0
print(a.common)

If you run all the code above it will print

100
100
300
300
400
600
600

Edit:

See my comment to your answer and a simple example here. Here I opt for two global references to tkinter StringVars. The stringvars exist themselves in the Tk() namespace, like the widgets; besides they are global Python names.

import tkinter as tk
from tkinter import ttk



class Page1(tk.Toplevel):
    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self.title('Page1')
        self.label1 = ttk.Label(self, text='Filename:')
        self.entry1 = ttk.Entry(self, textvariable=input_file1)
        self.label1.pack(side=tk.LEFT)
        self.entry1.pack()


class Page2(tk.Toplevel):
    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self.title('Page2')
        self.label1 = ttk.Label(self, text='Filename:')
        self.entry1 = ttk.Entry(self, textvariable=input_file2)
        self.button1 = ttk.Button(self, text='Copy Here', command=copy_filename)
        self.label1.pack(side=tk.LEFT)
        self.entry1.pack(side=tk.LEFT)
        self.button1.pack()

def copy_filename():
    input_file2.set(input_file1.get())

root = tk.Tk() # has to exist for the StringVars to be created
root.iconify()
input_file1 = tk.StringVar()
input_file2 = tk.StringVar()

page1 = Page1(root)
page2 = Page2(root)
root.mainloop()

Now in the next example see how I turn the stringvars into variables of Page1 and Page2 instances (not classes), making them local instead of global. Then I am forced to pass a reference for the widget page1 object into the widget page2 object.

This looks more close to what you are asking.

About MRO trouble, if you avoid multiple inheritance it won't happen.

Or you deal with it usually by using super()

In your case the error is because you store the widget in the object/instance (in self.somename), and then you try to invoke a widget method qualifying with the class name. There is no widget there in the class for you to use a method.

So the search using the method resolution order fails, because there is no corresponding name there.

Note that I have not used multiple inheritance, so I could have just written tk.Frame. instead of calling super. I like super because it makes clear in the text that I am invoking the parent class but super is really needed only when there are multiple parents and various levels of subclassing (usually forming a diamond shape).

Now the example:

import tkinter as tk
from tkinter import ttk


class Page1(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)

        self.input_file1 = tk.StringVar()

        self.label1 = ttk.Label(self, text='Filename:')
        self.entry1 = ttk.Entry(self, textvariable=self.input_file1)
        self.label1.pack(side=tk.LEFT)
        self.entry1.pack()


class Page2(tk.Frame):
    # note the page1 reference being
    # passed to initializer and stored in a var
    # local to this instance:
    def __init__(self, parent, page1, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)

        self.page1 = page1

        self.input_file2 = tk.StringVar()

        self.label1 = ttk.Label(self, text='Filename:')
        self.entry1 = ttk.Entry(self, textvariable=self.input_file2)
        self.button1 = ttk.Button(self, text='Copy Here',
            command=self.copy_filename)
        self.label1.pack(side=tk.LEFT)
        self.entry1.pack(side=tk.LEFT)
        self.button1.pack()

    def copy_filename(self):
        # see how the page1 refernce is used to acess
        # the Page1 instance
        self.input_file2.set(page1.input_file1.get())

root = tk.Tk() # has to exist for the StringVars to be created

page1 = Page1(root)
page2 = Page2(root, page1) # pass a reference to page1 instance
page1.pack(side=tk.LEFT)
page2.pack(side=tk.LEFT)
root.mainloop()