重构(多)生成器python函数

时间:2016-08-07 14:04:08

标签: python refactoring generator

我正在寻找一种pythonic方法来进一步重构下面的函数event_stream()。这简化并从我正在编写的python flask web应用程序中抽象出来,用python进行实验。

该函数是一个生成器,具有无限循环检查多个对象(当前实现为dicts)以进行更改(在应用程序的其他位置进行)。

如果对象已更改,则会产生一个事件,然后调用者函数sse_request()将用于创建服务器端事件。

def event_stream():
    parrot_old = parrot.copy()
    grail_old = grail.copy()
    walk_old = walk.copy()
    while True:
        print("change poller loop")
        gevent.sleep(0.5)

        parrot_changed, parrot_old = check_parrot(parrot_new=parrot, parrot_old=parrot_old)
        if parrot_changed:
            yield parrot_event(parrot)

        grail_changed, grail_old = check_grail(grail_new=grail, grail_old=grail_old)
        if grail_changed:
            yield grail_event(grail)

        walk_changed, walk_old = check_walk(walk_new=walk, walk_old=walk_old)
        if walk_changed:
            yield walk_event(walk)


@app.route('/server_events')
def sse_request():
    return Response(
            event_stream(),
            mimetype='text/event-stream')

虽然event_stream()目前很短,可以读取,但它打破了“只做一个,做得好”的概念, 因为它跟踪三个不同对象的变化。如果我要添加更多对象来跟踪 (例如“调查官”或“布莱恩”)它会变得笨拙。

2 个答案:

答案 0 :(得分:1)

如何创建将对象映射到变化检测器功能的dict?

可能是这样的:

object_change_checker_events = {
    parrot: {
                "checker": check_parrot, 
                "event": parrot_event
            },
    //grail : ................
    .................
}


def event_stream(object_change_checker_events):
    copies = {}
    for obj in object_change_checker_events:
        copies[obj] = obj.copy())
    while True:
        print("change poller loop")
        gevent.sleep(0.5)

        for obj in copies:
            obj_changed, obj_old = object_change_checker_events[obj]["checker"](obj, copies[obj]
            if(obj_changed):
                yield object_change_checker_events[obj]["event"](obj)

这里的主要工作是构建 object_change_checker_events 字典。

我改变了字典的结构。试试这个:

object_change_checker_events = {

    'parrot': {
                "object": parrot
                "checker": check_parrot, 
                "event": parrot_event
            },
    //grail : ................
    .................
}


def event_stream(object_change_checker_events):
    copies = {}

    for obj in object_change_checker_events:
        copies[obj] = {
            "old": obj["object"].copy(),
            "new": obj["object"]
        }

    while True:
        print("change poller loop")
        gevent.sleep(0.5)

        for obj in copies:
            obj_changed, obj_old = object_change_checker_events[obj]["checker"](copies[obj]["new"], copies[obj]["old"])
            if(obj_changed):
                yield object_change_checker_events[obj]["event"](obj)

答案 1 :(得分:1)

简答:

两个重构步骤已应用于函数event_stream()。这些在下面的“长答案”中按时间顺序解释,并在此汇总:

原始函数有多个产量:每个“对象”一个,其变量将被跟踪。添加更多对象意味着增加产量。

  • 在第一次重构中,通过循环存储要跟踪的对象的结构来消除这种情况。
  • 在第二次重构中,将“对象”从dicts更改为真实对象的实例。这使得存储结构和循环变得更加简单。可以将“旧副本”和用于发现更改并创建生成的服务器端事件的函数移动到对象中。

完全重构的代码位于“长答案”的底部。

长答案,一步一步:

第一个重构:

下面我粘贴了我的第一次重构,受到 jgr0 的回答的启发。 他最初的建议并没有立即奏效,因为它使用了一个字典作为字典键;但是密钥必须是可清洗的(而不是这些密钥)。 看起来我们都使用字符串作为键,并将object / dict并行移动到属性。

此解决方案适用于“对象”是dict或dicts列表(因此使用deepcopy())。

每个对象都有一个“checker”函数来检查它是否已经改变(例如check_parrot), 和一个“事件”函数(例如parrot_event)来构建要产生的事件。

可以通过“args”属性为xxx_event()函数配置其他参数,该属性作为* args传递。

copies{}中的对象在复制时是固定的,而在change_objects{}中配置的对象是引用,因此反映了对象的最新状态。比较两者可以识别变化。

虽然现在可以说event_stream()函数的可读性低于原始函数, 我不再需要触摸它来跟踪更多物体的变化。这些已添加到change_objects{}

# dictionary of objects whose changes should be tracked by event_stream()
change_objects = {
    "parrot": {
        "object": parrot,
        "checker": check_parrot,
        "event": parrot_event,
        "args": (walk['sillyness'],),
            },
    "grail": {
        "object": grail,
        "checker": check_grail,
        "event": grail_event,
    },
    "walk": {
        "object": walk,
        "checker": check_walk,
        "event": walk_event,       
    },
}

def event_stream(change_objects):
    copies = {}
    for key, value in change_objects.items():
        copies[key] = {"obj_old": deepcopy(value["object"])}  # ensure a true copy, not a reference!

    while True:
        print("change poller loop")
        gevent.sleep(0.5)
        for key, value in change_objects.items():
            obj_new = deepcopy(value["object"]) # use same version in check and yield functions
            obj_changed, copies[key]["obj_old"] = value["checker"](obj_new, copies[key]["obj_old"])
            if (obj_changed): # handle additional arguments to the event function
                if "args" in value:
                    args = value["args"]
                    yield value["event"](obj_new, *args)
                else:
                    yield value["event"](obj_new)


@app.route('/server_events')
def sse_request():
    return Response(
            event_stream(change_objects),
            mimetype='text/event-stream')

编辑:第二次重构,使用Objects而不是Dicts:

如果我使用的是对象而不是原始的dicts:

  • 可以移动更改标识和事件构建方法 进入物体。
  • 对象也可以存储“旧”状态。

一切变得更简单,更具可读性:event_stream()不再需要copies{} dict(因此它只有一个结构可以循环),change_objects{}现在是一个简单的列表跟踪器对象:

def event_stream(change_objects):
    while True:
        print("change poller loop")
        gevent.sleep(0.5)
        for obj in change_objects:
            if obj.changed():
                yield obj.sse_event()

@app.route('/server_events')
def sse_request():
    # List of objects whose changes are tracked by event_stream
    # This list is in sse_request, so each client has a 'private' copy
    change_objects = [
        ParrotTracker(),
        GrailTracker(),
        WalkTracker(),
        ...
        SpamTracker(),
    ]
    return Response(
            event_stream(change_objects),
            mimetype='text/event-stream')

示例跟踪器类是:

from data.parrot import parrot
class ParrotTracker:

    def __init__(self):
        self.old = deepcopy(parrot)
        self.new = parrot

    def sse_event(self):
        data = self.new.copy()
        data['type'] = 'parrotchange'
        data = json.dumps(data)
        return "data: {}\n\n".format(data)

    def truecopy(self, orig):
        return deepcopy(orig) # ensure is a copy, not a reference

    def changed(self):
        if self.new != self.old:
            self.old = self.truecopy(self.new)
            return True
        else:
            return False

我认为它现在闻起来好多了!