重构(多)生成器python函数 [英] Refactor a (multi)generator python function
问题描述
我正在寻找一种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屋!