我遇到了Publisher类的问题。我希望班级一旦超过3就取消订阅。我可以让所有这些工作,但问题是取消订阅是随机抓取sub取消订阅。
class Publisher:
def __init__(self):
self.sub_list = []
def subscribe(self, subscriber):
if subscriber in self.sub_list:
raise ValueError("Multiple subscriptions are not allowed")
self.sub_list.append(subscriber)
def unsubscribe(self, subscriber):
if subscriber not in self.sub_list:
raise ValueError("Can only unsubscribe subscribers")
self.sub_list.remove(subscriber)
def publish(self, s):
for subscriber in self.sub_list:
subscriber(s)
if __name__ == '__main__':
class SimpleSubscriber:
def __init__(self, name, publisher):
self.name = name
self.publisher = publisher
self.count = 0
publisher.subscribe(self.process)
self.count += 1
def process(self, s):
self.count +=1
if self.count > 3:
self.publisher.unsubscribe(self.publisher.sub_list[0])
print(self, ":", s.upper())
def __repr__(self):
return self.name
publisher = Publisher()
for i in range(6):
newsub = SimpleSubscriber("Sub"+str(i), publisher)
line = input("Input {}: ".format(i))
publisher.publish(str(line))
输出结果为:
Input 0: 0
(Sub0, ':', '0')
Input 1: 1
(Sub0, ':', '1')
(Sub1, ':', '1')
Input 2: 2
(Sub0, ':', '2')
(Sub2, ':', '2')
Input 3: 3
(Sub1, ':', '3')
(Sub2, ':', '3')
(Sub3, ':', '3')
Input 4: 4
(Sub1, ':', '4')
(Sub3, ':', '4')
(Sub4, ':', '4')
Input 5: 5
(Sub2, ':', '5')
(Sub4, ':', '5')
(Sub5, ':', '5')
我希望Input5是SUB3,SUB4,SUB5。我知道问题是Publisher.publish试图击中一个移动目标,而取消订阅正在从sub_list中删除东西。我不确定如何创建一个在调用Publisher.publish后不会更改的列表?我想让self.sub_list成为一本字典,但我不知道该如何做到这一点。有更好的方法吗?
答案 0 :(得分:1)
您对移动目标的想法是完全正确的,但让其他人更清楚一点:
首先,我清理了一些东西并添加了一些调试输出:
class Publisher:
def __init__(self):
self.sub_list = []
def subscribe(self, subscriber):
if subscriber in self.sub_list:
raise ValueError("Multiple subscriptions are not allowed")
self.sub_list.append(subscriber)
def unsubscribe(self, subscriber):
if subscriber not in self.sub_list:
raise ValueError("Can only unsubscribe subscribers")
self.sub_list.remove(subscriber)
def publish(self, s):
for subscriber in self.sub_list:
subscriber(s)
class Subscriber:
def __init__(self, name, publisher):
self.publisher = publisher
publisher.subscribe(self.process)
self.name = name
self.calls = 0
def process(self, s):
print(" call {}".format(self.name)) # debug on call
self.calls += 1
if self.calls <= 3:
print("{} call #{}: {}".format(self.name, self.calls, s.upper()))
else:
print(" remove {}".format(self.name)) # debug on remove
self.publisher.unsubscribe(self.process)
if __name__ == '__main__':
pub = Publisher()
for i in range(6):
Subscriber("Sub"+str(i), pub)
pub.publish("test" + str(i))
print()
产生
call Sub0
Sub0 call #1: TEST0
call Sub0
Sub0 call #2: TEST1
call Sub1
Sub1 call #1: TEST1
call Sub0
Sub0 call #3: TEST2
call Sub1
Sub1 call #2: TEST2
call Sub2
Sub2 call #1: TEST2
call Sub0
remove Sub0
call Sub2 # call Sub1 was skipped
Sub2 call #2: TEST3
call Sub3
Sub3 call #1: TEST3
call Sub1 # ... but it shows up here
Sub1 call #3: TEST4
call Sub2
Sub2 call #3: TEST4
call Sub3
Sub3 call #2: TEST4
call Sub4
Sub4 call #1: TEST4
call Sub1
remove Sub1
call Sub3 # call Sub2 was skipped, but this was invisible
Sub3 call #3: TEST5 # because it was ready to remove itself anyway
call Sub4
Sub4 call #2: TEST5
call Sub5
Sub5 call #1: TEST5
这实际上非常简单,但是所有开销都是伪装的:
如果您正在遍历列表,并且删除了当前项目,则会跳过以下项目。所以
def publish(self, s):
for subscriber in self.sub_list:
subscriber(s) # if subscriber deletes itself from .sub_list,
# next subscriber does not get called
相反,我建议跟踪要删除的项目,并进行单独的清理过程:
class Publisher:
def __init__(self):
self.sub_list = []
self.deleted = set()
...
def unsubscribe(self, subscriber):
if subscriber not in self.sub_list:
raise ValueError("Can only unsubscribe subscribers")
self.deleted.add(subscriber)
def publish(self, s):
for subscriber in self.sub_list:
subscriber(s)
if self.deleted:
self.sub_list = [sub for sub in self.sub_list if sub not in self.deleted]
self.deleted = set()
完成这些更改(并删除调试输出)后,您将获得
Sub0 call #1: TEST0
Sub0 call #2: TEST1
Sub1 call #1: TEST1
Sub0 call #3: TEST2
Sub1 call #2: TEST2
Sub2 call #1: TEST2
Sub1 call #3: TEST3
Sub2 call #2: TEST3
Sub3 call #1: TEST3
Sub2 call #3: TEST4
Sub3 call #2: TEST4
Sub4 call #1: TEST4
Sub3 call #3: TEST5
Sub4 call #2: TEST5
Sub5 call #1: TEST5
正如所料。