Advanced Classes

16_advanced_classes

Advanced Class Topics

Super Function

The super function is required when more than one class in a hierarchy has an __init__() method. Below Wizard inherits from Player and they both have an __init__() method. To make this work we need to call super().__init__() in the child class’s __init__(), and we should usually do that first. The super call will have the same signature as the __init__() of the parent class. See below.

In [0]:
class Player:

    def __init__(self, name, level):
        self.Name = name
        self.Class = "Villager"
        self.Level = min(max(1, level), 20)  # Min: 1, Max: 20
        self.Health = self.Level * 8

    def __str__(self):
        _fields = (f"{k}: {v}" for k, v in self.__dict__.items())
        return '\n  '.join(_fields) + '\n'


class Wizard(Player):

    def __init__(self, name, level, school):
        super().__init__(name, level)
        self.Class = f"Wizard of {school}"
        self.Mana = self.Level * 10


print(Player("George", 1))
print(Wizard("Jim Darkmagic", level=10, school="Illusion"))
Name: George
  Class: Villager
  Level: 1
  Health: 8

Name: Jim Darkmagic
  Class: Wizard of Illusion
  Level: 10
  Health: 80
  Mana: 100

Meta Classes

If a class is an object factory, then a meta class is a class factory. Meta Classes are often considered black magic, please use them with caution. Meta classes should never be your first impulse as a solution to solve any given puzzle. Often a simple decorator will be faster, easier and less surprising.

Custom meta classes typically inherit from type and redefine the __new__() method. A meta class is like a class decorator in capability but the meta class allows modifications to take place before the instances are created. Decorators do their magic strictly after the fact. While a decorator can affects any decorated class individually, a meta class at the top level will affect an entire class hierarchy.

In [0]:
class Foo(type):
    def __new__(cls, name, bases, clsdict):
        print(f"A New {cls.__qualname__} named {name}!")
        return super().__new__(cls, name, bases, clsdict)


class Bar(metaclass=Foo):
    """ If Foo must be declared as a metaclass `metaclass=Foo`.
    This will not work the same if we just inherit from Foo. """
    pass


class Baz(Bar):
    """ Now we can inherit from Bar and get the same behavior. """
    pass

b = Bar()
z = Baz()
A New Foo named Bar!
A New Foo named Baz!

Structure Example

In [0]:
from inspect import Parameter, Signature
In [0]:
class StructMeta(type):
    def __new__(cls, clsname, bases, clsdict):
        clsobj = super().__new__(cls, clsname, bases, clsdict)
        sig = cls.make_signature(clsobj._fields)
        setattr(clsobj, '__signature__', sig)
        return clsobj

    @staticmethod
    def make_signature(names):
        return Signature(
            Parameter(name, Parameter.POSITIONAL_OR_KEYWORD) 
            for name in names)


class Structure(metaclass=StructMeta):
    _fields = []
    def __init__(self, *args, **kwargs):
        bound = self.__signature__.bind(*args, **kwargs)
        for name, val in bound.arguments.items():
            setattr(self, name, val)
    
    def __str__(self):
        out = (f"{name}: {val}" for name, val in self.__dict__.items())
        return '\n'.join(out)
In [0]:
class Struct(Structure):
    _fields = ['name']


s = Struct("Baz")
print(s)
name: Baz

Dataclasses

The dataclass is a class decorator for quickly defining a common type of class without all the boilerplate.

In [0]:
from dataclasses import dataclass


@dataclass
class Color:
    hue: int
    saturation: float
    lightness: float = 0.5
    
In [0]:
blue = Color(hue=240, saturation=0.75, lightness=0.75)
print(blue)
Color(hue=240, saturation=0.75, lightness=0.75)
In [0]:
print(blue.hue)
print(blue.saturation)
print(blue.lightness)
240
0.75
0.75
In [0]:
light_blue = Color(hue=240, saturation=0.75, lightness=0.25)
print(light_blue == blue)
False
In [0]:
blue2 = Color(hue=240, saturation=0.75, lightness=0.75)
print(blue == blue2)
True
In [0]: