Skip to content

Inherit Magic Class

Class inheritance is fundamental in object-oriented languages. It makes class definition much clearer in many cases.

Magic-class is designed to make GUI structures connected with the structure of class itself, so how to deal with class inheritance is not a well-defined feature by default. Here are some points that you have to keep in mind before making abstract classes.

The Order of Widget

First, let's see following example. It is obvious that created GUI will have two buttons named "common function" and "main function", but it is not clear which is upper and which is lower.

In magic-class, methods defined in base classes will appear upper than those in subclasses. In the case of the example below, buttons will be arranged in order "common function", "main function".

from magicclass import magicclass

class Base:
    def common_function(self):
        """Do some common things."""

@magicclass
class Main(Base):
    def main_function(self):
        """Main one."""

ui = Main()
ui.show()

Warning

Do NOT decorate Base class with @magicclass, otherwise constructor will raise TypeError. You only have to decorate the final concrete classes.

Field Objects in the Base Class

You may want to add widgets using fields. Fields behave similarly as methods. In the following example, Two widgets, x and y will be packed in the Main GUI, in order x, y.

from magicclass import magicclass, field

class Base:
    x = field("this is x, appearing first")

@magicclass
class Main(Base):
    y = field("this is y, appearing second")

ui = Main()
ui.show()

However, if you want to use "bind" to bind values to method or connect callback function to a field, you must re-define fields in the subclasses.

1. Bind methods or fields

This will not work

from typing import Annotated
from magicclass import magicclass, field

class Base:
    x = field(int)

@magicclass
class Main(Base):
    def func(self, value: Annotated[int, {"bind": x}]):
        """Do something"""

This will work

from magicclass import magicclass, field
from magicclass.types import Bound

class Base:
    x = field(int)

@magicclass
class Main(Base):
    x = field(int)

    def func(self, value: Annotated[int, {"bind": x}]):
        """Do something"""

1. Define Callbacks

This will not work

from magicclass import magicclass, field

class Base:
    x = field(int)

@magicclass
class Main(Base):
    @x.connect
    def _callback(self):
        """Do something"""

This will work

from magicclass import magicclass, field

class Base:
    x = field(int)

@magicclass
class Main(Base):
    x = field(int)

    @x.connect
    def _callback(self):
        """Do something"""

Note

These caveats are quite natural considering the concept of scope in Python. When you define a variable in a class, it is not available from other classes until class definition finishes.

class A:
    x = 1
class B(A):
    print(x)
Output
NameError: name 'x' is not defined

This is because class inheritance has not finished yet in the line print(x).

Nesting Magic Classes

Nesting magic classes is useful for designing layout of widgets. You don't have to worry about inheriting base class with a nested magic class.

from magicclass import magicclass, MagicTemplate, field, set_design

class Base(MagicTemplate):
    # All of these widgets and their layout will be inherited to subclasses
    result = field(str)

    @magicclass
    class X(MagicTemplate):
        def func(self): ...

    @set_design(location=X)
    def func(self):
        self.result.value = self.__class__.__name__

@magicclass
class A(Base):
    pass

Predefinition of Methods and Fields

Most of the time you want to inherit a class is when you want to prepare a template of multipule GUIs. As mentioned above, methods and fields that are defined in the base class will packed before those in the subclasses. This is not desirable if you want the subclasses share same header and footer and make the middle widgets variable.

Just like using location=... argument, the pre-definition strategy is also useful here. First arrange all the widgets in the base class, and specifically define the real widgets in the subclasses.

from magicclass import magicclass, MagicTemplate, field, set_design, abstractapi

class Base(MagicTemplate):
    header = field("this is header", widget_type="Label")
    x = abstractapi()  # pre-definition
    footer = field("this is footer", widget_type="Label")

@magicclass
class A(Base):
    def x(self):
        """Do something"""

@magicclass
class B(Base):
    x = field(int)

@magicclass(layout="horizontal")
class Main(MagicTemplate):
    a = field(A)
    b = field(B)

ui = Main()
ui.show()