重构(多)生成器python函数 [英] Refactor a (multi)generator python function

查看:90
本文介绍了重构(多)生成器python函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找一种Python方法来进一步重构下面的功能event_stream().这是我编写的用于尝试python的python flask Web应用程序的简化和抽象.

I am looking for a pythonic way to further refactor the function event_stream() below. This simplified and abstracted from a python flask web app I am writing to experiment with python.

该函数是一个生成器,带有一个无限循环,用于检查多个对象(当前以dict形式实现)是否有更改(在应用程序的其他位置进行).

The function is a generator, with an endless loop checking a number of objects (currently implemented as dicts) for changes (made elsewhere in the application).

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

If an object has changed, an event is yielded, which the caller function sse_request() will then use to create a server-side-event.

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()当前足够短以至于可读,但它打破了只做一个,然后做得很好"的概念, 因为它正在跟踪对三个不同对象的更改.如果我要添加其他对象进行跟踪 (例如审讯人"或布莱恩人")变得笨拙.

While event_stream() is currently short enough to be readable, it breaks the concept of doing "one only, and do that well", because it is tracking changes to three different objects. If I was to add further objects to track (e.g. an "inquisitor", or a "brian") it would become unwieldy.

推荐答案

简短答案:


重构两个步骤已应用到功能event_stream().这些在下面的详细解答"中按时间顺序进行了说明,并在此处进行了总结:

Short Answer:


Two steps of refactoring have been applied to the function event_stream(). These are explained in chronological order in the "Long Answer" below, and in summary here:

原始函数有多个产量:每个对象"都有一个产量,其变化将被跟踪.添加更多的对象意味着增加更多的产量.

The original function had multiple yields: one per "object" whose changes are to be tracked. Adding further objects implied adding further yields.

  • 在第一次重构中,通过遍历存储要跟踪的对象的结构来消除这种情况.
  • 在第二个重构中,将对象"从字典更改为真实对象的实例.这使存储结构和循环从根本上变得更简单.可以将旧副本"和用于发现更改并创建生成的服务器端事件的功能移入对象中.

完全重构的代码位于长答案"的底部.

The fully refactored code is at the bottom of the "Long Answer".

下面,我受 jgr0 的回答启发,粘贴了我的第一个重构. 他最初的建议并没有立即生效,因为它使用了dict作为dict键.但键必须是可散列的(其字典不可). 看起来我们俩都使用字符串作为键,然后将对象/字典并行移动到一个属性.

Below I have pasted my first refactoring, inspired by jgr0's answer. His initial suggestion did not work straight away, because it used a dict as a dict key; but keys must be hashable (which dicts are not). It looks like we both hit on using a string as key, and moving the object/dict to an attribute in parallel.

此解决方案适用于对象"是字典或字典列表(因此使用deepcopy()的情况)的情况.

This solution works where the "object" is either a dict, or a list of dicts (hence use of deepcopy()).

每个对象都有一个检查器"功能来检查它是否已更改(例如check_parrot), 和事件"函数(例如parrot_event)来构建要产生的事件.

Each object has a "checker" function to check if it has changed (e.g check_parrot), and an "event" function (e.g. parrot_event) to build the event to be yielded.

可以通过"args"属性(为* args传递)为xxx_event()函数配置其他参数.

Additional arguments can be configured for the xxx_event() functions via the "args" attribute, which is passed as *args.

copies{}中的对象在复制时是固定的,而change_objects{}中配置的对象是引用,因此反映了对象的最新状态.将两者进行比较,可以识别出更改.

The objects in copies{} are fixed at the time of copying, while those in configured in change_objects{} are references, and thus reflect the latest state of the objects. Comparing the two allows changes to be identified.

虽然现在说event_stream()函数的可读性比我原来的要差, 我不再需要触摸它来跟踪其他对象的变化.这些已添加到change_objects{}.

While the event_stream() function is now arguably less readable than my original, I no longer need to touch it to track the changes of further objects. These are added to 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')

使用对象而不是Dicts进行第二次重构:

如果我使用对象而不是原始命令:

Second Refactor, using Objects instead of Dicts:

If I use objects rather than the original dicts:

  • 变更标识和事件构建方法可以移动 进入物体.
  • 对象还可以存储旧"状态.
  • The change-identification and event-building methods can be moved into the objects.
  • The objects can also store the "old" state.

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

Everything becomes simpler and even more readable: event_stream() no longer needs a copies{} dict (so it has only one structure to loop over), and the change_objects{} is now a simple list of tracker 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

我觉得现在闻起来好多了!

I think it now smells much better!

这篇关于重构(多)生成器python函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆