don't create _ListenerCollection objects if not needed?

Issue #2516 resolved
Mike Bayer repo owner created an issue

This would most ideally apply across the board. Here's a patch that does it in an awkward way just for schema events:

diff -r e15fc03a16a322298e27c416dce3f3fe869bdf1a lib/sqlalchemy/event.py
--- a/lib/sqlalchemy/event.py   Mon Jun 18 10:06:49 2012 -0400
+++ b/lib/sqlalchemy/event.py   Wed Jun 20 16:03:04 2012 -0400
@@ -115,6 +115,14 @@
     def __reduce__(self):
         return _UnpickleDispatch(), (self._parent_cls, )

+    def _has(self, name, cls):
+        cls_dispatch = getattr(self.__class__, name)
+        if cls in cls_dispatch._clslevel and \
+            cls_dispatch._clslevel[cls](cls):
+            return True
+        else:
+            return name in self.__dict__
+
     def _update(self, other, only_propagate=True):
         """Populate from the listeners in another :class:`_Dispatch`
             object."""
diff -r e15fc03a16a322298e27c416dce3f3fe869bdf1a lib/sqlalchemy/events.py
--- a/lib/sqlalchemy/events.py  Mon Jun 18 10:06:49 2012 -0400
+++ b/lib/sqlalchemy/events.py  Wed Jun 20 16:03:04 2012 -0400
@@ -230,9 +230,11 @@
         raise NotImplementedError()

     def _set_parent_with_dispatch(self, parent):
-        self.dispatch.before_parent_attach(self, parent) 
+        if self.dispatch._has('before_parent_attach', self.__class__):
+            self.dispatch.before_parent_attach(self, parent) 
         self._set_parent(parent) 
-        self.dispatch.after_parent_attach(self, parent) 
+        if self.dispatch._has('after_parent_attach', self.__class__):
+            self.dispatch.after_parent_attach(self, parent)

 class PoolEvents(event.Events):
     """Available events for :class:`.Pool`.

but perhaps we can alter _DispatchDescriptor.get to not actually generate a _ListnerCollection. _ListenerCollection can be created only when listen() is called and we can issue a separate codepath for that.

Comments (5)

  1. Mike Bayer reporter

    the attached patch will do it. here's a proof of concept:

    from sqlalchemy.event import _ListenerCollection as old_collection
    from sqlalchemy import event
    from collections import defaultdict
    
    from sqlalchemy.event import _EmptyListener as old_empty
    
    class _ListenerCollection(old_collection):
        canary = defaultdict(int)
        def __init__(self, parent, target_cls):
            _ListenerCollection.canary[parent.__name__)]((target_cls,) += 1
            old_collection.__init__(self, parent, target_cls)
    
    event._ListenerCollection = _ListenerCollection
    
    class _EmptyListener(old_empty):
        canary = defaultdict(int)
        def __init__(self, parent, target_cls):
            _EmptyListener.canary[parent.__name__)]((target_cls,) += 1
            old_empty.__init__(self, parent, target_cls)
    event._EmptyListener = _EmptyListener
    
    from sqlalchemy import *
    
    metadata = MetaData()
    
    for i in xrange(60):
        Table('t%d' % i, metadata,
            Column('id', Integer, primary_key=True),
            *[           Column("c%d" % j, Integer)
                for j in xrange(10)
            ](
    )
        )
    
    print "-----------"
    for (cls, name), count in _ListenerCollection.canary.items():
        print "Class: %r Event name: %s  Count: %s" % (cls.__name__, name, count)
    print "Total: %d" % sum(_ListenerCollection.canary.values())
    
    print "-----------"
    for (cls, name), count in _EmptyListener.canary.items():
        print "Class: %r Event name: %s  Count: %s" % (cls.__name__, name, count)
    print "Total: %d" % sum(_EmptyListener.canary.values())
    
  2. Mike Bayer reporter
    • changed status to open
    • removed status

    major regression, not found by any existing tests....good thing I caught this

        def test_bool_clslevel(self):
            def listen_one(x, y):
                pass
            event.listen(self.Target, "event_one", listen_one)
            t = self.Target()
            assert t.dispatch.event_one
    
  3. Log in to comment