Python Pub / Sub - 取消订阅问题

时间:2015-01-23 18:39:58

标签: python

我遇到了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成为一本字典,但我不知道该如何做到这一点。有更好的方法吗?

1 个答案:

答案 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

正如所料。