首页 > 解决方案 > 在跨多个文件和目录使用 Python OOP 时帮助理解层次继承

问题描述

介绍

我是大学学习编程的学生。我的课程侧重于 Java、C# 和 C++,因此我对 OOP 有一定的经验。目前我正忙于我的合作社,要求是使用 Python 和 tkinter 创建一个 GUI(我正在学习这两者)。

我的问题与 Python OOP 和跨多个目录中的多个文件的继承特别相关,我根本想不通(已经 3 天了)

如果您不想浏览多行缩短的文件和代码示例,可以在此处找到实际代码:

https://github.com/bkleynhans/Landsat-Buoy-Calibration.git

然后转到页面底部以获取问题的要旨。

除非您安装了 ModTran,否则您将无法运行校准系统,这是一种昂贵的许可软件。然而,GUI 应该是完全可操作的,唯一的非标准要求是 tkcalendar。

我做了什么

我已经完成了下面链接的线程以及其他成员包含的链接。

ImportError:无法导入名称

ImportError:无法导入名称 X

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

不幸的是,我似乎无法将它们与我的问题联系起来,无论是因为我知识不足还是因为我很慢。

文件夹结构

我的文件夹结构如下:

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
        │   
        └─ 

文件摘要

根目录中的 tarca_gui 只是一个 bash 脚本,它更改到工作目录并执行 gui

cd ~/Landsat-Buoy-Calibration/gui
python3 tarca_gui.py

将文件及其代码放在这里会很混乱,但是我将粘贴标题和结构。该项目的 GitHub 链接位于上面的介绍部分。

所需的非标准库:tkcalendar - https://pypi.org/project/tkcalendar/

tarca_gui.py - 从 linux 终端启动程序并使用 tkinter 构建界面。所有其他文件均基于以下结构派生

基类

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 和 status frame 来自(扩展)targa_gui

派生类摘要

progress_bar.py 扩展了 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 扩展了 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 扩展了 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 扩展了 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 扩展了 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 扩展了 tarca_gui.py

from tkinter import *
from tkinter import ttk
import tarca_gui

class Status_Frame(tarca_gui.Tarca_Gui):
    def __init__(self, master):

其余类:help_menu、settings_frame、settings_notebook、header_frame 和 input_notebook 是从(扩展)其他类派生的

header_frame.py 扩展 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 扩展 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 扩展了 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 扩展 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 扩展 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):

问题和问题

首先,当通过您可以在基类中查看的函数启动 gui 时,会更新 PYTHONPATH。

其次,我知道目前这些类并非都以相同的方式导入,它们曾经并且已经在我努力寻找问题的过程中进行了更改,并且一旦问题得到解决,它们就会被标准化。

问题

整个 GUI 工作正常,直到我尝试将 Settings_Frame 导入 settings_notebook.py

收到错误

% ./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'

发现

我相信这是由于循环导入,因为 settings_frame 导入 settings_notebook ,反之亦然。

困惑

我知道应该尽可能避免循环引用,但是按照我目前理解 Python OOP 的方式,我需要将 settings_notebook 导入 settings_frame 以构建它,但同时我需要将 settings_frame 导入 settings_notebook 以扩展它(settings_notebook 应该是 settings_frame 的子级)

问题

我不知道我做错了什么。

综上所述

如果你能做到这一点,首先,非常感谢你。

如前所述,我在大学并同时学习 C++、C#、Java、Python、HTML、CSS、JavaScript、PHP、SQL、Unity 和 Unreal 引擎(有时我对它们之间的概念感到困惑),所以任何相关的建议对我的代码的任何部分将不胜感激。

标签: pythonoopinheritance

解决方案


这段代码真的很难解开,但我认为问题的根源在于您试图使用继承在类之间共享数据。这不是继承的目的。

当 B 从 A 继承时,这并不意味着 B 共享 A 的数据,这意味着 B 是 A 的精确副本,并进行了一些增强。实际上,您有两个 A - 原始的和具有一些附加功能的新 A。

例如,您有一个名为Tarca_Gui. 这似乎是主要课程。它有一个状态栏、一个菜单栏等等。当您从 继承时Tarca_Gui,该新类具有状态栏、菜单栏等。这两个类不共享任何数据。

您需要使用组合而不是继承。不同之处在于,继承Menu_Bar 是 a Tarca_Gui,但您真正想要的是Tarca_Gui拥有a Menu_Bar

我不可能修复您的代码,因为您没有提供最小的可重现示例,但您需要做的第一件事是Tarca_Gui从每个类的定义中删除。然后,将实例传递Tarca_Gui给其他各种对象。

换句话说:

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 ...

然后,在这些对象中的每一个内部,如果他们需要由 管理的东西Tarca_Gui,他们可以使用self.tarca_gui来引用它。例如,如果您需要访问master您可以使用的小部件self.tarca_gui.master

同样,不应继承自Menu_BarorInput_FrameSettings_Frame

有关继承与组合的更多信息,请参阅继承与组合之间的区别


推荐阅读