Как лучше всего получить доступ к объекту подкласса в другом подклассе?

Прилагается минимальный рабочий пример проблемы, с которой я столкнулся:

class A():
    def __init__(self, *args, **kwargs):
        
        self.b=B(self)
        self.c=C(self)
        
        
class B():
    def __init__(self, parent):
    
        self.a1=3
    
class C():
    def __init__(self, parent):
        
        self.a2=4
        
        print(B.a1)
    
    def ab():
        print(B.a1)

C(object).ab()

Каков наилучший способ доступа в классе C к объекту класса B?

В принципе, я создал приложение TKinter, в котором каждый кадр является классом, и я хочу, чтобы в одном кадре был доступ к переменной другого кадра.

РЕДАКТИРОВАТЬ @all, спасибо всем за вклад. Я расширил минимальный рабочий пример для своей конкретной проблемы:

import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox


class main_window(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.title("xxx")
        self.geometry("800x1000")
        
        #Build GUI
        self.inputframe = INPUTframe(self)
        self.imregframe = IMREGframe(self)
        self.calcframe = CALCframe(self)

class INPUTframe(tk.Frame):
    def __init__(self, parent, width=800, height=150):
        tk.Frame.__init__(self, parent, width=width, height=height)
        self.place(x=0, y=0)
        
        label= ttk.Label(self, text = "xxx", font=('Arial',12))
        label.place(x=10,y=0)

        self.create_widgets()
        
    def create_widgets(self):
        
        label1 = tk.Label(self, text = "xxx", font=('Arial',10))
        label1.place(x=30,y=30)
        button1 = tk.Button(self, text='xxx',command = lambda: self.path(1))
        button1.place(x=100,y=30)
        self.textentry1=tk.StringVar()
        self.entry1 = tk.Entry (self, textvariable= self.textentry1)
        self.entry1.place(x=160,y=30,width = "350")
        label11 = tk.Label(self, text = "xxx", font=('Arial',10))
        label11.place(x=500,y=30)
        
    def path(self,var):

        match var:
           case 1:
               self.entry1.delete(0, 'end')
               filename = filedialog.askopenfilename(parent=self,title='Choose a file')
               self.entry1.insert("end",filename)
               return filename

class IMREGframe(tk.Frame):
    def __init__(self, parent, width=250, height=150):
        tk.Frame.__init__(self, parent, width=width, height=height)
        self.place(x=0,y=150)

        label4 = tk.Label(self, text = "xxx", font=('Arial',12))
        label4.place(x=10,y=0)
        
        self.SFAtrue=tk.BooleanVar(value=True)
        
        self.create_widgets()      
        
    def create_widgets(self):
        #self.SFAtrue=tk.BooleanVar(value=True)
        checkb1 = tk.Checkbutton(self, text='xxx', variable=self.SFAtrue, onvalue=0, offvalue=1)
        checkb1.place(x=30,y=30)
        checkb2 = tk.Checkbutton(self, text='xxx', variable=self.SFAtrue, onvalue=1, offvalue=0)
        checkb2.place(x=30,y=50)
 

class CALCframe(tk.Frame):
    def __init__(self, parent, width=800, height=100):
        tk.Frame.__init__(self, parent, width=width, height=height)
        self.place(x=0,y=370)
        self.parent=parent
        
        self.create_widgets()
        
    
    def create_widgets(self):
        
        button4 = tk.Button(self, text='xxx', command = lambda: self.calculate(self.parent.imregframe.SFAtrue.get())) #bester Weg um Variabele aus anderer Klasse/Objekt zu holen?
        button4.config( height = 4, width = 12 )
        button4.place(x=50,y=0)

        self.Previewtrue=tk.BooleanVar(value=False)
        checkb3 = tk.Checkbutton(self, text='xxx',variable=self.Previewtrue, onvalue=1, offvalue=0)
        checkb3.place(x=150,y=0)
        label5 = tk.Label(self,text = "xxx", font=("Arial", 9))
        label5.place(x=150,y=30)
        label51 = tk.Label(self,text = "xxx", font=("Arial", 9))
        label51.place(x=150,y=50)
        
    def calculate (self, calcType):
        
        print(calcType)
        print(self.parent.inputframe.textentry1.get())
    
main_window().mainloop()  

То, чего мне не хватало, было self.parent=parent, как было предложено @Obaskly, и теперь оно работает. Это хороший способ сделать это? Переменные становятся довольно длинными: self.parent.inputframe.textentry1.get()

🤔 А знаете ли вы, что...
Python позволяет создавать графические приложения с использованием библиотеки PyQt.


50
2

Ответы:

Решено

В вашем коде есть две ошибки:

  1. Вы пытаетесь получить доступ к B.a1, что неверно, поскольку a1 — это переменная экземпляра, а не переменная класса. Вы должны получить к нему доступ через экземпляр B, который принадлежит A.

  2. Метод ab() внутри C должен принимать self в качестве первого параметра, который будет методом экземпляра.

Вам необходимо сохранить ссылку на экземпляр B, созданный в классе A.

Это должно выглядеть так, чтобы класс C мог получить доступ к экземпляру B и его атрибутам через своего родителя A.

class A():
    def __init__(self, *args, **kwargs):
        self.b = B(self)
        self.c = C(self)


class B():
    def __init__(self, parent):
        self.a1 = 3 


class C():
    def __init__(self, parent):
        self.parent = parent  # Reference to the parent class
        self.a2 = 4
        print(self.parent.b.a1)  # Access B's a1 via the parent

    def ab(self):
        print(self.parent.b.a1) 


# Create an instance of A
instance_of_A = A()

# Access method ab() of class C through instance of A
instance_of_A.c.ab()

РЕДАКТИРОВАТЬ

Имена переменных могут быть довольно длинными, это точно. Есть несколько методов, которые вы можете использовать. Например, как и при использовании ссылок, вы можете создавать ярлыки ссылок на часто используемые виджеты или переменные.

class CALCframe(tk.Frame):
    def __init__(self, parent, width=800, height=100):
        # ...
        
        # Shortcut references
        self.imregframe = self.parent.imregframe
        self.inputframe = self.parent.inputframe
        
        self.create_widgets()
        
    def create_widgets(self):
        button4 = tk.Button(self, text='xxx', command=lambda: self.calculate(self.imregframe.SFAtrue.get()))
        button4.config(height=4, width=12)
        button4.place(x=50, y=0)
        
        # ...
        
    def calculate(self, calcType):
        print(calcType)
        print(self.inputframe.textentry1.get())

Или вы можете использовать класс контроллера; который хранит состояние вашего приложения и позволяет фреймам взаимодействовать через него.

class AController:
    def __init__(self):
        self.main_window = main_window(self)
        
    def get_SFAtrue(self):
        return self.main_window.imregframe.SFAtrue.get()
    
    def get_textentry1(self):
        return self.main_window.inputframe.textentry1.get()

class main_window(tk.Tk):
    def __init__(self, controller, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.controller = controller
        self.title("xxx")
        self.geometry("800x1000")
        
        self.inputframe = INPUTframe(self)
        self.imregframe = IMREGframe(self)
        self.calcframe = CALCframe(self)

class CALCframe(tk.Frame):
    def __init__(self, parent, width=800, height=100):
        tk.Frame.__init__(self, parent, width=width, height=height)
        self.controller = parent.controller
        
        self.create_widgets()
        
    def create_widgets(self):
        button4 = tk.Button(self, text='xxx', command=self.calculate)
        button4.config(height=4, width=12)
        button4.place(x=50, y=0)
        
    def calculate(self):
        calcType = self.controller.get_SFAtrue()
        textEntry1 = self.controller.get_textentry1()
        print(calcType)
        print(textEntry1)

AController()

Если вы хотите избежать длинных цепочек точек и лучше соблюдать Закон Деметры, я бы попросил основной класс передавать переменные, а не требовать от других классов их извлечения.

В идеале CALCframe не нужно знать внутренности main_window, INPUTframe и IMREGframe. В противном случае, если вы решите повторно использовать CALCframe в другом месте или вам понадобится вложить его дальше, он перестанет работать.

В вашем конкретном случае простой способ обойти это — передать main_window переменные Tkinter INPUTframe и IMREGframe в CALCframe. Это отделяет CALCframe и позволяет использовать его с любым родителем, если вы передаете соответствующие переменные.

Вот ваш код обновлен, чтобы показать, что я имею в виду:

import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox


class main_window(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.title("xxx")
        self.geometry("800x1000")
        
        #Build GUI
        self.inputframe = INPUTframe(self)
        self.imregframe = IMREGframe(self)
        self.calcframe = CALCframe(self, self.inputframe.textentry1, self.imregframe.SFAtrue)

class INPUTframe(tk.Frame):
    def __init__(self, parent, width=800, height=150):
        tk.Frame.__init__(self, parent, width=width, height=height)
        self.place(x=0, y=0)
        
        label= ttk.Label(self, text = "xxx", font=('Arial',12))
        label.place(x=10,y=0)

        self.create_widgets()
        
    def create_widgets(self):
        
        label1 = tk.Label(self, text = "xxx", font=('Arial',10))
        label1.place(x=30,y=30)
        button1 = tk.Button(self, text='xxx',command = lambda: self.path(1))
        button1.place(x=100,y=30)
        self.textentry1=tk.StringVar()
        self.entry1 = tk.Entry (self, textvariable= self.textentry1)
        self.entry1.place(x=160,y=30,width = "350")
        label11 = tk.Label(self, text = "xxx", font=('Arial',10))
        label11.place(x=500,y=30)
        
    def path(self,var):

        match var:
           case 1:
               self.entry1.delete(0, 'end')
               filename = filedialog.askopenfilename(parent=self,title='Choose a file')
               self.entry1.insert("end",filename)
               return filename

class IMREGframe(tk.Frame):
    def __init__(self, parent, width=250, height=150):
        tk.Frame.__init__(self, parent, width=width, height=height)
        self.place(x=0,y=150)

        label4 = tk.Label(self, text = "xxx", font=('Arial',12))
        label4.place(x=10,y=0)
        
        self.SFAtrue=tk.BooleanVar(value=True)
        
        self.create_widgets()      
        
    def create_widgets(self):
        #self.SFAtrue=tk.BooleanVar(value=True)
        checkb1 = tk.Checkbutton(self, text='xxx', variable=self.SFAtrue, onvalue=0, offvalue=1)
        checkb1.place(x=30,y=30)
        checkb2 = tk.Checkbutton(self, text='xxx', variable=self.SFAtrue, onvalue=1, offvalue=0)
        checkb2.place(x=30,y=50)
 

class CALCframe(tk.Frame):
    def __init__(self, parent, textentry1, SFAtrue, width=800, height=100):
        tk.Frame.__init__(self, parent, width=width, height=height)
        self.place(x=0,y=370)
        self.parent=parent

        self.SFAtrue = SFAtrue
        self.textentry1 = textentry1
        
        self.create_widgets()
        
    
    def create_widgets(self):
        
        button4 = tk.Button(self, text='xxx', command = lambda: self.calculate(self.SFAtrue.get())) #bester Weg um Variabele aus anderer Klasse/Objekt zu holen?
        button4.config( height = 4, width = 12 )
        button4.place(x=50,y=0)

        self.Previewtrue=tk.BooleanVar(value=False)
        checkb3 = tk.Checkbutton(self, text='xxx',variable=self.Previewtrue, onvalue=1, offvalue=0)
        checkb3.place(x=150,y=0)
        label5 = tk.Label(self,text = "xxx", font=("Arial", 9))
        label5.place(x=150,y=30)
        label51 = tk.Label(self,text = "xxx", font=("Arial", 9))
        label51.place(x=150,y=50)
        
    def calculate (self, calcType):
        
        print(calcType)
        print(self.textentry1.get())
    
main_window().mainloop()