Pythonissa luokkia määritellessä on toisinaan tarvetta viitata luokkaan itseensä. Tyypillinen esimerkki on datamalliluokka, joka luo "managerin" huolehtimaan kyseisen mallin varastoimasta datasta:

class Manager(object):
    def __init__(self, model):
        self.model = model
    def get(self):
        return self.model()

class Model(object):
    pass

class User(Model):
    objects = Manager(User) # tämä ei toimi näin

Ongelmana tässä siis on, miten välittää uudelle Manager-objektille tieto siitä, mihin malliluokkaan se liittyy. Tämä olisi myös kätevää automatisoida niin, ettei jokainen Modelin perivä luokka joudu manuaalisesti luomaan objects-attribuuttia.

Avuksi tulee Pythonin metaclass-toiminto. Sen avulla päästään käsiksi siihen vaiheeseen, jossa uusi luokka peritään. Tässä vaiheessa luokkaan voidaan lisätä dynaamisesti attribuutteja.

class Manager(object):
    def __init__(self, model):
        self.model = model
    def get(self):
        return self.model()

class ModelBase(type):
    def __new__(cls, name, bases, attrs):
        new_class = super(ModelBase, cls).__new__(cls, name, bases, attrs)
        new_class.objects = Manager(new_class)
        return new_class

class Model(object):
    __metaclass__ = ModelBase

class User(Model):
    pass

Ylläolevassa esimerkissä User-luokkaan (ja mihin tahansa Modelista periytyvään luokkaan) lisätään automaattisesti objects-attribuutti luokan määrittelyn yhteydessä. Python kutsuu ModelBase.new()-funktiota jokaisen class Xxx(Model) -määrittelyn kohdalla.

Esimerkissä on oleellista, että ModelBase periytyy type-luokasta. Se tarkoittaa, että luokasta luodut instanssit ovat uusia luokkia (eivätkä tavallisia objekteja). Funktio type.new() on se, joka varsinaisesti luo uuden perityn luokan.

Kun hommat on hoidettu kuntoon konepellin alla, mallien käyttäminen on mukavan yksinkertaista tähän tapaan:

class User(Model):
    pass

class Group(Model):
    pass

user = User.objects.get()
group = Group.objects.get()

Tämä tuntuisi olevan aika tyypillistä Python-ohjelmoinnissa. Kulissien takana kirjastokomponenteissa joutuu joskus tekemään hieman "likaisia" temppuja, mutta sovellustasolla koodi on sujuvaa ja yksinkertaista. Kirjastokoodi on onneksi helppo tehdä geneeriseksi ja jakaa kaikkien sovellusten kesken.

Published 27.9.2009