[Sqlalchemy-tickets] Issue #3163: use deque() for event lists so that remove() raises during an eve
Brought to you by:
zzzeek
|
From: Mike B. <iss...@bi...> - 2014-08-12 16:12:34
|
New issue 3163: use deque() for event lists so that remove() raises during an event https://bitbucket.org/zzzeek/sqlalchemy/issue/3163/use-deque-for-event-lists-so-that-remove Mike Bayer: quick test: ``` #!python import unittest from sqlalchemy import event from sqlalchemy.orm import Session class Receiver(object): def __init__(self, instance, remove): self.instance = instance self.remove = remove def __call__(self, session, context): self.instance.calls += 1 if self.remove: event.remove(self.instance.session, 'after_flush_postexec', self) @classmethod def register(cls, instance, remove): self = cls(instance, remove=remove) event.listen(instance.session, 'after_flush_postexec', self) class FlushingTest(unittest.TestCase): def setUp(self): self.session = Session() self.calls = 0 def flush(self): self.session.dispatch.after_flush_postexec(self.session, {}) def testTripleRemove(self): Receiver.register(self, remove=True) Receiver.register(self, remove=True) Receiver.register(self, remove=True) self.flush() self.assertEqual(self.calls, 3) unittest.main() ``` which fails, because we're iterating through a list. This patch replaces the lists with a deque, just as fast or faster than a list, all tests pass: ``` #!diff diff --git a/lib/sqlalchemy/event/attr.py b/lib/sqlalchemy/event/attr.py index 7641b59..dba1063 100644 --- a/lib/sqlalchemy/event/attr.py +++ b/lib/sqlalchemy/event/attr.py @@ -37,6 +37,7 @@ from . import registry from . import legacy from itertools import chain import weakref +import collections class RefCollection(object): @@ -96,8 +97,8 @@ class _DispatchDescriptor(RefCollection): self.update_subclass(cls) else: if cls not in self._clslevel: - self._clslevel[cls] = [] - self._clslevel[cls].insert(0, event_key._listen_fn) + self._clslevel[cls] = collections.deque() + self._clslevel[cls].appendleft(event_key._listen_fn) registry._stored_in_collection(event_key, self) def append(self, event_key, propagate): @@ -113,13 +114,13 @@ class _DispatchDescriptor(RefCollection): self.update_subclass(cls) else: if cls not in self._clslevel: - self._clslevel[cls] = [] + self._clslevel[cls] = collections.deque() self._clslevel[cls].append(event_key._listen_fn) registry._stored_in_collection(event_key, self) def update_subclass(self, target): if target not in self._clslevel: - self._clslevel[target] = [] + self._clslevel[target] = collections.deque() clslevel = self._clslevel[target] for cls in target.__mro__[1:]: if cls in self._clslevel: @@ -145,7 +146,7 @@ class _DispatchDescriptor(RefCollection): to_clear = set() for dispatcher in self._clslevel.values(): to_clear.update(dispatcher) - dispatcher[:] = [] + dispatcher.clear() registry._clear(self, to_clear) def for_modify(self, obj): @@ -287,7 +288,7 @@ class _ListenerCollection(RefCollection, _CompoundListener): self.parent_listeners = parent._clslevel[target_cls] self.parent = parent self.name = parent.__name__ - self.listeners = [] + self.listeners = collections.deque() self.propagate = set() def for_modify(self, obj): @@ -337,7 +338,7 @@ class _ListenerCollection(RefCollection, _CompoundListener): def clear(self): registry._clear(self, self.listeners) self.propagate.clear() - self.listeners[:] = [] + self.listeners.clear() class _JoinedDispatchDescriptor(object): diff --git a/lib/sqlalchemy/event/registry.py b/lib/sqlalchemy/event/registry.py index a34de3c..ba2f671 100644 --- a/lib/sqlalchemy/event/registry.py +++ b/lib/sqlalchemy/event/registry.py @@ -243,4 +243,4 @@ class _EventKey(object): def prepend_to_list(self, owner, list_): _stored_in_collection(self, owner) - list_.insert(0, self._listen_fn) + list_.appendleft(self._listen_fn) ``` this is a totally easy win. |