jsのsetTimeout的な非同期っぽい感じのsleepをpythonでどうするのか考えてみた。

こんな方向でいけるかもしれない。書いたコードは、setTimeoutとは似ても似つかないけれど。
イメージ見たいなものはつかめた。。。かもしれない。

  • 実際にsleepをするわけではなく、現在時刻との差分を確認
  • 「できるよ」、「おわった」、「まだ待って」みたいな状態を持てば良さそう。

無駄にメタクラスとか使っているけど、単に同じようなメソッドをたくさん定義したくなかっただけです。

import sys
import time

class Queue(object):
    """FIFO"""
    def __init__(self):
        self._queue = []

    def enqueue(self, x):
        self._queue.append(x)
    
    def dequeue(self):
        return self._queue.pop(0)


class EvalQueue(Queue):
    def dorun(self):
        result = []
        while len(self._queue) != 0:
            e = self.dequeue()
            self._do(e, result)
        return result
            
    def _do(self, event, result):
        if event.state == "finished":
            pass
        elif event.state == "waiting":
            self.enqueue(event)
        elif event.state == "runnable":
            result.append(event())
            if event.state != "finished":
                self.enqueue(event)
        else:
            raise NotImplementedError


class StateMeta(type):
    def __new__(cls, name, bases, attrs):
        for state in attrs["state_candidates"]:
            attrs[state] = cls._set_state_generate(state)
        return super(StateMeta, cls).__new__(cls, name, bases, attrs)	

    @classmethod
    def _set_state_generate(cls, state):
        def _set_state(self):
            self._state = state
        return _set_state

class Event(object):
    __metaclass__ = StateMeta
    state_candidates = ["runnable", "waiting", "finished"]

    def __init__(self, fn, *args):
        self.fn = fn
        self.args = args
        self.runnable()

    @property
    def state(self):
        return self._state

    def __call__(self):
        result = self.fn(*self.args)
        self.finished()
        return result

class TimedEvent(Event):
    state_candidates = ["runnable", "waiting", "finished"]

    def __init__(self, *args,**kw):
        super(TimedEvent, self).__init__(*args, **kw)
        self.start_time = time.time()
        self._waittime = 0
        self.waiting()

    def _get_waittime(self):
        return self._waittime
    def _set_waittime(self, N):
        self._waittime = N
    waittime = property(_get_waittime, _set_waittime)

    @property
    def diff(self):
        return time.time() - self.start_time

    @property
    def state(self):
        if self._state != "finished" and self.diff > self._waittime:
            self.runnable()
        return self._state

class StickyEvent(Event):
    state_candidates = ["runnable", "waiting", "finished"]

    def __init__(self, *args,**kw):
        super(StickyEvent, self).__init__(*args, **kw)
        self._life = 1

    def _get_life(self):
        return self._life
    def _set_life(self, n):
        self._life = n
    life = property(_get_life, _set_life)

    def __call__(self):
        result = super(StickyEvent, self).__call__()
        self.life -= 1
        if self.life >= 0:
            self.runnable()
        return result

class EventFactory(object):
    @classmethod
    def event(cls, fn, *args):
        return Event(fn, *args)

    @classmethod
    def tevent(cls, N, fn, *args):
        te = TimedEvent(fn, *args)
        te.waittime = N
        return te

    @classmethod
    def sevent(cls, life, fn, *args):
        se = StickyEvent(fn, *args)
        se.life = life
        return se


eq = EvalQueue()

# x = Event(lambda x : x + x, 10)
# x.runnable()
# print x.state
# print x()
# print x.state

# import sys
# tx = TimedEvent(lambda x : sys.stdout.write("hey:%s" % x))
# tx.waittime = 2

# for i in range(10):
#     print "%s: waittime %s, diff %s" % (tx.state, tx._waittime, tx.diff)
#     time.sleep(0.5)

eq.enqueue(EventFactory.tevent(3, lambda : sys.stdout.write("hey")))
eq.enqueue(EventFactory.tevent(1.5, lambda : sys.stdout.write("foo")))
eq.enqueue(EventFactory.sevent(50, lambda : sys.stdout.write("(@.@)\n")))
eq.enqueue(EventFactory.sevent(17, lambda : time.sleep(0.1)))
eq.dorun()

あと、「ある処理の後に、この処理を。。。」とかは関係を持つようなeventでwrapすればできそうな感じ。