【问题标题】:Assistance with understanding hierarchical inheritance when using Python OOP across multiple files and directories在跨多个文件和目录使用 Python OOP 时帮助理解分层继承
【发布时间】:2019-10-17 09:53:45
【问题描述】:

简介

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

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

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

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

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

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

我做了什么

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

ImportError: cannot import name

ImportError: Cannot import name 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
        │   
        └─ 

文件摘要

root 中的 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 会通过您可以在基类中查看的函数进行更新。

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

问题

在我尝试将 Settings_Frame 导入 settings_notebook.py 之前,整个 GUI 工作正常

收到错误

% ./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 引擎(有时我对它们之间的概念感到困惑),所以任何与我的代码的任何部分有关的建议将不胜感激。

【问题讨论】:

  • "我知道应该尽可能避免循环引用,但是按照我目前理解 Python OOP 的方式,我需要将 settings_notebook 导入到 settings_frame 中才能构建它,但同时我需要将 settings_frame 导入 settings_notebook 以扩展它(settings_notebook 应该是 settings_frame 的子级)”是的,这就是问题所在。解决此问题的“愚蠢”方法是将这两个模块合并为一个。这可能是一个有争议的观点,但怪物模块可能是较小的邪恶。您还可以将导入移动到底部。
  • python 类定义是动态执行的。类型在运行时解析。这就是为什么 python 在类定义中不能很好地处理循环依赖的原因。您可以将类定义视为围绕调用类对象构造函数 metaclass type('MyClass', (object,), {'__init__': lambda self: print("I'm a MyClass instance")})() 的语法糖

标签: python oop inheritance


【解决方案1】:

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

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

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

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

由于您没有提供minimal reproducible example,我无法修复您的代码,但您需要做的第一件事是从每个类的定义中删除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_BarInput_FrameSettings_Frame

有关继承与组合的更多信息,请参阅Difference between Inheritance and Composition

【讨论】:

  • 嗨,布莱恩,感谢您的回复。您在我对继承的“误用”中是正确的,这很可能是由于必须每 4 个月学习一门新语言而从未真正实现高级水平的任何东西(我在使用 OOP 18 个月时就知道它是如何工作的以前。我什至没有意识到我忘记了,谢谢)。哦,我没有提供一个最小的可重现示例,因为我认为人们希望看到整个解决方案的结构,这就是我提供 GitHub 链接的原因。下次我会尝试做一些不同的事情。
猜你喜欢
  • 1970-01-01
  • 2021-03-29
  • 1970-01-01
  • 1970-01-01
  • 2021-09-04
  • 2012-02-17
  • 1970-01-01
  • 2018-05-21
  • 1970-01-01
相关资源
最近更新 更多