我正在寻找一种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()目前很短,可以读取,但它打破了“只做一个,做得好”的概念, 因为它跟踪三个不同对象的变化。如果我要添加更多对象来跟踪 (例如“调查官”或“布莱恩”)它会变得笨拙。
答案 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()
。这些在下面的“长答案”中按时间顺序解释,并在此汇总:
原始函数有多个产量:每个“对象”一个,其变量将被跟踪。添加更多对象意味着增加产量。
完全重构的代码位于“长答案”的底部。
下面我粘贴了我的第一次重构,受到 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')
如果我使用的是对象而不是原始的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
我认为它现在闻起来好多了!