Hlavní navigace

Model z reálného světa

18. 8. 2014 19:48 (aktualizováno) | Petr Blahoš

Začnu jinak, než jak jsem minule zamýšlel. Zkusím se rovnou zaměřit na skoro skutečnou situaci. No, dobře, ne úplně skutečnou, ale podobnou tomu, co občas sám dělám. Budu chtít implementovat celý model jako jeden objekt, který mi bude při změně „vystřelovat“ nějaké události. A teď ta skoro skutečná situace: Máme nějakou sadu výrobků. Při přečtení čárového kódu na výrobku chceme zobrazit informace o něm. To znamená postup – seznam operací, kterými se ten výrobek vyrábí, a seznam operací, které na výrobku byly skutečně provedeny. Tak jdeme na to: github/modellerkit, step04

Než vůbec začneme, budeme si muset udělat nový „horký“ objekt – tentokrát opravdu objekt. Budu mu říkat HotObject. Představuju si ho jako objekt, který funguje docela normálně, ale má „horké“ properties. Buď takové, které při změně „vystřelí“ událost, nebo „horké“ kontejnery: HotList, TypedHotList nebo HotObject. Kromě toho bude moct mít i „normální“ properties, které žádné „střílení“ nezpůsobí. Ty „horké“ properties si budeme deklarovat. Je to jako když děláme TypedHotList, ale složitější. Třeba: Můj objekt je HotObject, který má horkou unicode property article, horkou int property sn, TypedHotList process a TypedHotList operations.

V rámci přípravy uděláme objekt Server, který nám bude dávat informace o výrobcích. Hledejte v production.py třídu Server, funkce get_product_ops, která vrátí seznam operací provedených pro výrobek, a get_process, která vrátí postup. S modelem budeme mluvit asi takto: Modele, chci výrobek ABC1234, s/n 653. A teď chci výrobek ABC8562, s/n 21680. Model na základě toho řekne serveru, hele serveře, dej mi data, a nastaví své „horké properties“. To „střílení“ se pak už děje jakoby samo.

Základem HotObject je přiřazování hodnot do „horkých properties“. V principu vypadá asi takto (zkráceno, hotmodel.py: __setattr__):

def __setattr__(self, name, val):
    if name.startswith("_"):
        return super(HotObject, self).__setattr__(name, val)
    if name in self._hot_properties:
        (type_info, allow_none) = self._hot_properties[name]

        if issubclass(type_info, HotObject):
            raise AttributeError("Cannot assign to %s" % name)

        if getattr(self, name) == val:
            return
        if not val is None and not isinstance(val, type_info):
            raise TypeError(
                "Only %s allowed for %s" % (type_info.__name__, name),
            )
        ret = super(HotObject, self).__setattr__(name, val)

        self._fire("update", name)

        return ret
    # normal (not hot) properties
    return super(HotObject, self).__setattr__(name, val)
Zaprvé, nevšímáme si ničeho, co začíná _. Pak, „horké brambory“ jsou v self._hot_properties. Ostatní zase zpracováváme normálně. Jestli to tedy je „horká“ property, tak zjistíme, jestli je to HotObject a v tom případě nemůžeme přiřadit přímo do něj. Jinak zkontrolujeme typ, přiřadíme, a vystřelíme.

Teď ještě ta deklarace. Funkcí make_hot_property nastavíme, že žvýkačka je „horká“ (zkráceno).

def make_hot_property(self, name, type_info, allow_none, initial_value):
    if type_info in IMMUTABLE_TYPES or issubclass(type_info, HotBase):
        pass
    else:
        raise TypeError(
            "Type not allowed as a hot property: %s" % type_info,
        )

    self._hot_properties[name] = (type_info, allow_none, )
    if not initial_value is None and not isinstance(
        initial_value, type_info,
    ):
        raise TypeError(
            "Only %s allowed for %s" % (type_info.__name__, name),
        )
    super(HotObject, self).__setattr__(name, initial_value)
    return initial_value
Zapíšeme si to do self._hot_properties, nastavíme počáteční hodnotu, a je hotovo. Vynechal jsem nějaké kontroly typů a hodnot.

Věc se pak používá takto:

class ProductModel(hotmodel.HotObject):
    def __init__(self, server):
        super(ProductModel, self).__init__()
        self.server = server
        self.make_hot_property("article", str, True, None)
        self.make_hot_property("sn", int ,True, None)
        self.make_hot_property(
            "process",
            hotmodel.TypedHotList,
            False,
            hotmodel.TypedHotList(ProcessOperation),
        )
        self.make_hot_property(
            "operations",
            hotmodel.TypedHotList,
            False,
            hotmodel.TypedHotList(ProductOperation),
        )
        self.make_hot_property("process_selection", int ,True, None)
        self.make_hot_property("operation_selection", int ,True, None)

Pro zajímavost se podívejte ještě na ProductModel.set_product.

def set_product(self, article, sn):
    self.article = article
    self.sn = sn
    self.process[:] = self.server.get_process(article, sn)
    self.operations[:] = self.server.get_product_ops(article, sn)
Takhle se do modelu „nahraje“ nový výrobek, a model sám „vystřelí“, co se změnilo.

Tím pro dnešek skončíme. Kdo dává pozor, vidí, že je tam něco špatně. Když si ukázku pustíte, uvidíte, že to informace o změnách vypisuje, ale zkuste si na to navěsit nějaké ty „listenery“. Nepůjde to. A na to se podíváme příště.