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)
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()