#๐Ÿ”’ Subclass object type is of superclass object rather than its own

37 messages ยท Page 1 of 1 (latest)

lavish lion
#

note: I have omitted irrelevant code to make this more concise

I'm trying to make 3 subclasses (TopUi, BottomLeftUi, and BottomRightUi) of a superclass (Ui), wherein the subclasses each have their own unique members that are explicitly called from a main.py file. I'm using this structure because I have data/members I want centralized to a single point rather than passed around to every new subclass.

However when I try to call methods from the subclasses, I get an error saying 'Ui' object has no attribute <x> Specifically,

Traceback:

File "main.py", line 81, in <module>
  window = MainWindow()
File "main.py", line 20, in __init__
  self.setup_ui()
File "main.py", line 39, in setup_ui
  top_ui.setup_top_ui()
# UI.py
class Ui:
  _instance = None
  _initialized = False
  
  # Shared data across all subclasses
  _options = {}

  def __new__(cls, *args, **kwargs):
    if cls._instance is None:
      cls._instance = super().__new__(cls)
    return cls._instance
  
  def __getitem__(self, key):
    return self._options.get(key)

  def __setitem__(self, key, value):
    self.options[key] = value
  
  def __init__(self, event_controller):
    if self.__class__._initialized:
      return
    self.__class__._initialized = True
    self.options: dict[str, bool] = self._options
    self.event_controller = event_controller
  # ... Shared members here ... #

class TopUi(Ui)
  def __init__(self, top_section, event_controller):
    super().__init__(event_controller)
  
  # ... Unique members here ... #

class BottomLeftUi(Ui):
  def __init__(self, left_bottom_section, event_controller):
    super().__init__(event_controller)
  
  # ... Unique members here ... #

class BottomRightUi(Ui):
  def __init__(self, right_bottom_section, event_controller):
    super().__init__(event_controller)

  # ... Unique members here ... #
# main.py
class MainWindow(QMainWindow):
  def __init__(self):
    super().__init__()
    self.event_controller = EventControl() # custom event control class
    self.ui_base_class = Ui(self.event_controller)
  
    self.setup_ui()
  
  def setup_ui(self):
    self.top_section = QWidget()
    top_ui = TopUi(self.top_section, self.event_controller)
    top_ui.setup_top_ui()
    
    self.left_bottom_section = QWidget()
    left_bottom_ui = BottomLeftUi(self.left_bottom_section, self.event_controller)
    left_bottom_ui.setup_bottom_left_ui()

    self.right_bottom_section = QWidget()
    right_bottom_ui = BottomRightUi(self.right_bottom_section, self.event_controller)
    right_bottom_ui.setup_bottom_right_ui()
sour ruinBOT
#

@lavish lion

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

outer bronze
#

!traceback
Your traceback does not show the ful error, including the exception.

sour ruinBOT
#
Traceback

Please provide the full traceback for your exception in order to help us identify your issue.
While the last line of the error message tells us what kind of error you got,
the full traceback will tell us which line, and other critical information to solve your problem.
Please avoid screenshots so we can copy and paste parts of the message.

A full traceback could look like:

Traceback (most recent call last):
  File "my_file.py", line 5, in <module>
    add_three("6")
  File "my_file.py", line 2, in add_three
    a = num + 3
        ~~~~^~~
TypeError: can only concatenate str (not "int") to str

If the traceback is long, use our pastebin.

lavish lion
#
Traceback (most recent call last):
  File "main.py", line 81, in <module>
    window = MainWindow()
  File "main.py", line 20, in __init__
    self.setup_ui()
    ~~~~~~~~~~~~~^^
  File "main.py", line 39, in setup_ui
    top_ui.setup_top_ui()
    ^^^^^^^^^^^^^^^^^^^
AttributeError: 'Ui' object has no attribute 'setup_top_ui'
outer bronze
#

Why are you using __new__ ?

lavish lion
#

To make sure only one instance of Ui exists

visual willow
#

(Theory) When you perform the magic in Ui.__new__(), that magic is carried over to its subclasses, which runs the Ui.__new__ and get a UI object in return and not a TopUI or BottomLeftUi, etc.

outer bronze
lavish lion
#

No the subclasses aren't singletons

outer bronze
#

I'd be print("top_ui",type(top_ui)) to check things.

outer bronze
lavish lion
#

Would defining __new__ to return a TopUi, BottomLeftUi, etc. object fix it?

#

In the subclasses that is

outer bronze
lavish lion
#

Would cls indicate that the call is coming from a subclass?

visual willow
lavish lion
visual willow
#

maybe you could try making _instance a dictionary?

class Ui:
   _instances: dict[type, "Ui"] = {}

   def __new__(cls, *args, **kwargs):
       if cls not in cls._instances:
           cls._instances[cls] = super().__new__(cls)
       return cls._instances[cls]
lavish lion
visual willow
#

according to your code, _options is a class attribute for Ui so every subclass should inherit the same _options attr

#

and it seems to work in repl at least ```py

In [4]: class Ui:
...: _instances: dict[type, "Ui"] = {}
...:
...: def new(cls, *args, **kwargs):
...: if cls not in cls._instances:
...: cls._instances[cls] = super().new(cls)
...: return cls._instances[cls]

In [5]: class Sub(Ui): ...

In [6]: b = Ui()

In [7]: s = Sub()

In [8]: s
Out[8]: <main.Sub at 0x7654404c76b0>

In [9]: b
Out[9]: <main.Ui at 0x7654404c7d40>

In [10]: Ui._instances
Out[10]:
{main.Ui: <main.Ui at 0x7654404c7d40>,
main.Sub: <main.Sub at 0x7654404c76b0>}

lavish lion
#

Ok I'll implement that and test

outer bronze
#

I've been thinking. I wouldn't do the new thing. Instead, separate the "just one" aspect of the top ui from the class. A regular class with an init like normal. And an access function to return just the one:

def topui():
    global _topui
    if _topui is None:
        _topui = TopUI()
    return _topui

Then your classes have normal behaviour and you've still got a convenience route to just one of the top UI.

arctic goblet
lavish lion
lavish lion
outer bronze
#

You could make the topui() function a class method of TopUI maybe, store the "global" as a class attribute. Keeps things slightly self contained.

#

Like:

class TopUI:
    _default = None
    .........
    @classmethod
    def default(cls):
        if cls._default is None:
            cls._default = cls()
        return cls._default
lavish lion
#
class Ui:
    def __init__(self, event_controller: EventControl, options: dict[str, bool], sections: list[QWidget]):
        self.top_ui_class = None
        self.left_bottom_ui_class = None
        self.right_bottom_ui_class = None
        
        self.event_controller = event_controller
        self.options = options
        self.sections = sections
    
    def top_ui(self):
        if self.top_ui_class is None:
            section_widget = None
            for section in self.sections:
                if section.objectName() == "TopSection":
                    section_widget = section
                    break
            self.top_ui_class = TopUi(self, section_widget)
        return self.top_ui_class

class TopUi:
    def __init__(self, ui_base_class: Ui, section_widget: QWidget):
        self.superclass = ui_base_class
        self.top_section = section_widget
#

I think this would be ideal

#

That way I can reference the Ui class from inside TopUi (?) and not worry about whatever mess I had before

#

I'm not sure if passing self to another class will pass that instance though

#

Also, this way I can clearly keep everything in main.py

outer bronze
sour ruinBOT
#
Python help channel closed for inactivity

This help channel has been closed. Feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.