pypa/pip · error · ValueError

No subscribers: %r

Error message

No subscribers: %r

What it means

Raised as ValueError by EventMixin.remove when you try to remove a subscriber for an event name that has no subscribers registered at all. The pub/sub dict self._subscribers does not contain the event key, so removal is impossible. It is distinct from the case where the event exists but the specific subscriber is absent (which would raise a different error from deque.remove).

Source

Thrown at src/pip/_vendor/distlib/util.py:1048

        if event not in subs:
            subs[event] = deque([subscriber])
        else:
            sq = subs[event]
            if append:
                sq.append(subscriber)
            else:
                sq.appendleft(subscriber)

    def remove(self, event, subscriber):
        """
        Remove a subscriber for an event.

        :param event: The name of an event.
        :param subscriber: The subscriber to be removed.
        """
        subs = self._subscribers
        if event not in subs:
            raise ValueError('No subscribers: %r' % event)
        subs[event].remove(subscriber)

    def get_subscribers(self, event):
        """
        Return an iterator for the subscribers for an event.
        :param event: The event to return subscribers for.
        """
        return iter(self._subscribers.get(event, ()))

    def publish(self, event, *args, **kwargs):
        """
        Publish a event and return a list of values returned by its
        subscribers.

        :param event: The event to publish.
        :param args: The positional arguments to pass to the event's
                     subscribers.
        :param kwargs: The keyword arguments to pass to the event's

View on GitHub (pinned to f399c37189)

Solutions

  1. Guard removal: if event in obj.get_subscribers(event) or check the event was added.
  2. Track your own registration flag/set so remove is only called after a successful add.
  3. Use try/except ValueError around remove for fire-and-forget cleanup.

Example fix

# before
obj.remove('evt', handler)
# after
try:
    obj.remove('evt', handler)
except ValueError:
    pass
Defensive patterns

Strategy: try-catch

Validate before calling

def safe_remove(obj, event, subscriber):
    if any(s is subscriber for s in obj.get_subscribers(event)):
        obj.remove(event, subscriber)

Type guard

def has_subscribers(obj, event) -> bool:
    return event in obj._subscribers

Try / catch

try:
    obj.remove(event, subscriber)
except ValueError as e:
    if 'No subscribers' in str(e):
        pass  # already clean

Prevention

When it happens

Trigger: Calling EventMixin().remove('my-event', handler) before any add('my-event', handler); removing from an event after all subscribers were already removed and the key cleared; typo in the event name passed to remove.

Common situations: Teardown/cleanup code calling remove unconditionally; event-name mismatches between add and remove; lifecycle ordering bugs where remove runs before add.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/0fd2a79fbe141cc9. Report an issue: GitHub.