]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- wip - start factoring events so that we aren't using descriptors for dispatch,
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 4 Jan 2015 20:07:36 +0000 (15:07 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 4 Jan 2015 23:06:02 +0000 (18:06 -0500)
allowing us to move to __slots__

lib/sqlalchemy/event/attr.py
lib/sqlalchemy/event/base.py
lib/sqlalchemy/event/legacy.py
lib/sqlalchemy/event/registry.py
lib/sqlalchemy/orm/events.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/pool.py
test/base/test_events.py

index 5e3499209b0951674d65261d5e33a2f66e260788..de5d34950f6dd49230c5f529143d5d784cd91c65 100644 (file)
@@ -46,11 +46,11 @@ class RefCollection(object):
         return weakref.ref(self, registry._collection_gced)
 
 
-class _DispatchDescriptor(RefCollection):
-    """Class-level attributes on :class:`._Dispatch` classes."""
+class _ClsLevelDispatch(RefCollection):
+    """Class-level events on :class:`._Dispatch` classes."""
 
     def __init__(self, parent_dispatch_cls, fn):
-        self.__name__ = fn.__name__
+        self.name = fn.__name__
         argspec = util.inspect_getargspec(fn)
         self.arg_names = argspec.args[1:]
         self.has_kw = bool(argspec.keywords)
@@ -64,7 +64,6 @@ class _DispatchDescriptor(RefCollection):
             self, parent_dispatch_cls, fn)
 
         self._clslevel = weakref.WeakKeyDictionary()
-        self._empty_listeners = weakref.WeakKeyDictionary()
 
     def _adjust_fn_spec(self, fn, named):
         if named:
@@ -152,34 +151,23 @@ class _DispatchDescriptor(RefCollection):
     def for_modify(self, obj):
         """Return an event collection which can be modified.
 
-        For _DispatchDescriptor at the class level of
+        For _ClsLevelDispatch at the class level of
         a dispatcher, this returns self.
 
         """
         return self
 
-    def __get__(self, obj, cls):
-        if obj is None:
-            return self
-        elif obj._parent_cls in self._empty_listeners:
-            ret = self._empty_listeners[obj._parent_cls]
-        else:
-            self._empty_listeners[obj._parent_cls] = ret = \
-                _EmptyListener(self, obj._parent_cls)
-        # assigning it to __dict__ means
-        # memoized for fast re-access.  but more memory.
-        obj.__dict__[self.__name__] = ret
-        return ret
 
+class _InstanceLevelDispatch(object):
+    __slots__ = ()
 
-class _HasParentDispatchDescriptor(object):
     def _adjust_fn_spec(self, fn, named):
         return self.parent._adjust_fn_spec(fn, named)
 
 
-class _EmptyListener(_HasParentDispatchDescriptor):
-    """Serves as a class-level interface to the events
-    served by a _DispatchDescriptor, when there are no
+class _EmptyListener(_InstanceLevelDispatch):
+    """Serves as a proxy interface to the events
+    served by a _ClsLevelDispatch, when there are no
     instance-level events present.
 
     Is replaced by _ListenerCollection when instance-level
@@ -187,14 +175,17 @@ class _EmptyListener(_HasParentDispatchDescriptor):
 
     """
 
+    propagate = frozenset()
+    listeners = ()
+
+    __slots__ = 'parent', 'parent_listeners', 'name'
+
     def __init__(self, parent, target_cls):
         if target_cls not in parent._clslevel:
             parent.update_subclass(target_cls)
-        self.parent = parent  # _DispatchDescriptor
+        self.parent = parent  # _ClsLevelDispatch
         self.parent_listeners = parent._clslevel[target_cls]
-        self.name = parent.__name__
-        self.propagate = frozenset()
-        self.listeners = ()
+        self.name = parent.name
 
     def for_modify(self, obj):
         """Return an event collection which can be modified.
@@ -205,9 +196,11 @@ class _EmptyListener(_HasParentDispatchDescriptor):
         and returns it.
 
         """
-        result = _ListenerCollection(self.parent, obj._parent_cls)
-        if obj.__dict__[self.name] is self:
-            obj.__dict__[self.name] = result
+        result = _ListenerCollection(self.parent, obj._instance_cls)
+        if getattr(obj, self.name) is self:
+            setattr(obj, self.name, result)
+        else:
+            assert isinstance(getattr(obj, self.name), _JoinedListener)
         return result
 
     def _needs_modify(self, *args, **kw):
@@ -233,9 +226,11 @@ class _EmptyListener(_HasParentDispatchDescriptor):
     __nonzero__ = __bool__
 
 
-class _CompoundListener(_HasParentDispatchDescriptor):
+class _CompoundListener(_InstanceLevelDispatch):
     _exec_once = False
 
+    __slots__ = ()
+
     @util.memoized_property
     def _exec_once_mutex(self):
         return threading.Lock()
@@ -282,12 +277,15 @@ class _ListenerCollection(RefCollection, _CompoundListener):
 
     """
 
+    # RefCollection has a @memoized_property, so can't do
+    # __slots__ here
+
     def __init__(self, parent, target_cls):
         if target_cls not in parent._clslevel:
             parent.update_subclass(target_cls)
         self.parent_listeners = parent._clslevel[target_cls]
         self.parent = parent
-        self.name = parent.__name__
+        self.name = parent.name
         self.listeners = collections.deque()
         self.propagate = set()
 
@@ -339,23 +337,6 @@ class _ListenerCollection(RefCollection, _CompoundListener):
         self.listeners.clear()
 
 
-class _JoinedDispatchDescriptor(object):
-    __slots__ = 'name',
-
-    def __init__(self, name):
-        self.name = name
-
-    def __get__(self, obj, cls):
-        if obj is None:
-            return self
-        else:
-            obj.__dict__[self.name] = ret = _JoinedListener(
-                obj.parent, self.name,
-                getattr(obj.local, self.name)
-            )
-            return ret
-
-
 class _JoinedListener(_CompoundListener):
     _exec_once = False
 
index 37bd2c49e8a536833fdf5fc3ac17e38ef30a3af9..962d850c2ad81b9e8d7bc8412dfdb3ecfdb810c8 100644 (file)
@@ -17,9 +17,11 @@ instances of ``_Dispatch``.
 """
 from __future__ import absolute_import
 
+import weakref
+
 from .. import util
-from .attr import _JoinedDispatchDescriptor, \
-    _EmptyListener, _DispatchDescriptor
+from .attr import _JoinedListener, \
+    _EmptyListener, _ClsLevelDispatch
 
 _registrars = util.defaultdict(list)
 
@@ -34,10 +36,11 @@ class _UnpickleDispatch(object):
 
     """
 
-    def __call__(self, _parent_cls):
-        for cls in _parent_cls.__mro__:
+    def __call__(self, _instance_cls):
+        for cls in _instance_cls.__mro__:
             if 'dispatch' in cls.__dict__:
-                return cls.__dict__['dispatch'].dispatch_cls(_parent_cls)
+                return cls.__dict__['dispatch'].\
+                    dispatch_cls._for_class(_instance_cls)
         else:
             raise AttributeError("No class with a 'dispatch' member present.")
 
@@ -62,16 +65,41 @@ class _Dispatch(object):
 
     """
 
-    _events = None
-    """reference the :class:`.Events` class which this
-        :class:`._Dispatch` is created for."""
-
-    def __init__(self, _parent_cls):
-        self._parent_cls = _parent_cls
-
-    @util.classproperty
-    def _listen(cls):
-        return cls._events._listen
+    # in one ORM edge case, an attribute is added to _Dispatch,
+    # so __dict__ is used in just that case and potentially others.
+    __slots__ = '_parent', '_instance_cls', '__dict__'
+
+    _empty_listeners = weakref.WeakKeyDictionary()
+
+    def __init__(self, parent, instance_cls=None):
+        self._parent = parent
+        self._instance_cls = instance_cls
+        if instance_cls:
+            try:
+                _empty_listeners = self._empty_listeners[instance_cls]
+            except KeyError:
+                _empty_listeners = self._empty_listeners[instance_cls] = [
+                    _EmptyListener(ls, instance_cls)
+                    for ls in parent._event_descriptors
+                ]
+            for ls in _empty_listeners:
+                setattr(self, ls.name, ls)
+
+    @property
+    def _event_descriptors(self):
+        for k in self._event_names:
+            yield getattr(self, k)
+
+    def _for_class(self, instance_cls):
+        return self.__class__(self, instance_cls)
+
+    def _for_instance(self, instance):
+        instance_cls = instance.__class__
+        return self._for_class(instance_cls)
+
+    @property
+    def _listen(self):
+        return self._events._listen
 
     def _join(self, other):
         """Create a 'join' of this :class:`._Dispatch` and another.
@@ -83,36 +111,27 @@ class _Dispatch(object):
         if '_joined_dispatch_cls' not in self.__class__.__dict__:
             cls = type(
                 "Joined%s" % self.__class__.__name__,
-                (_JoinedDispatcher, self.__class__), {}
+                (_JoinedDispatcher, ), {'__slots__': self._event_names}
             )
-            for ls in _event_descriptors(self):
-                setattr(cls, ls.name, _JoinedDispatchDescriptor(ls.name))
 
             self.__class__._joined_dispatch_cls = cls
         return self._joined_dispatch_cls(self, other)
 
     def __reduce__(self):
-        return _UnpickleDispatch(), (self._parent_cls, )
+        return _UnpickleDispatch(), (self._instance_cls, )
 
     def _update(self, other, only_propagate=True):
         """Populate from the listeners in another :class:`_Dispatch`
             object."""
-
-        for ls in _event_descriptors(other):
+        for ls in other._event_descriptors:
             if isinstance(ls, _EmptyListener):
                 continue
             getattr(self, ls.name).\
                 for_modify(self)._update(ls, only_propagate=only_propagate)
 
-    @util.hybridmethod
     def _clear(self):
-        for attr in dir(self):
-            if _is_event_name(attr):
-                getattr(self, attr).for_modify(self).clear()
-
-
-def _event_descriptors(target):
-    return [getattr(target, k) for k in dir(target) if _is_event_name(k)]
+        for ls in self._event_descriptors:
+            ls.for_modify(self).clear()
 
 
 class _EventMeta(type):
@@ -131,26 +150,37 @@ def _create_dispatcher_class(cls, classname, bases, dict_):
     # there's all kinds of ways to do this,
     # i.e. make a Dispatch class that shares the '_listen' method
     # of the Event class, this is the straight monkeypatch.
-    dispatch_base = getattr(cls, 'dispatch', _Dispatch)
+    if hasattr(cls, 'dispatch'):
+        dispatch_base = cls.dispatch.__class__
+    else:
+        dispatch_base = _Dispatch
+
+    event_names = [k for k in dict_ if _is_event_name(k)]
     dispatch_cls = type("%sDispatch" % classname,
-                        (dispatch_base, ), {})
-    cls._set_dispatch(cls, dispatch_cls)
+                        (dispatch_base, ), {'__slots__': event_names})
+
+    dispatch_cls._event_names = event_names
 
-    for k in dict_:
-        if _is_event_name(k):
-            setattr(dispatch_cls, k, _DispatchDescriptor(cls, dict_[k]))
-            _registrars[k].append(cls)
+    dispatch_inst = cls._set_dispatch(cls, dispatch_cls)
+    for k in dispatch_cls._event_names:
+        setattr(dispatch_inst, k, _ClsLevelDispatch(cls, dict_[k]))
+        _registrars[k].append(cls)
+
+    for super_ in dispatch_cls.__bases__:
+        if issubclass(super_, _Dispatch) and super_ is not _Dispatch:
+            for ls in super_._events.dispatch._event_descriptors:
+                setattr(dispatch_inst, ls.name, ls)
+                dispatch_cls._event_names.append(ls.name)
 
     if getattr(cls, '_dispatch_target', None):
         cls._dispatch_target.dispatch = dispatcher(cls)
 
 
 def _remove_dispatcher(cls):
-    for k in dir(cls):
-        if _is_event_name(k):
-            _registrars[k].remove(cls)
-            if not _registrars[k]:
-                del _registrars[k]
+    for k in cls.dispatch._event_names:
+        _registrars[k].remove(cls)
+        if not _registrars[k]:
+            del _registrars[k]
 
 
 class Events(util.with_metaclass(_EventMeta, object)):
@@ -163,17 +193,30 @@ class Events(util.with_metaclass(_EventMeta, object)):
         # "self.dispatch._events.<utilitymethod>"
         # @staticemethod to allow easy "super" calls while in a metaclass
         # constructor.
-        cls.dispatch = dispatch_cls
+        cls.dispatch = dispatch_cls(None)
         dispatch_cls._events = cls
+        return cls.dispatch
 
     @classmethod
     def _accept_with(cls, target):
         # Mapper, ClassManager, Session override this to
         # also accept classes, scoped_sessions, sessionmakers, etc.
         if hasattr(target, 'dispatch') and (
-            isinstance(target.dispatch, cls.dispatch) or
-            isinstance(target.dispatch, type) and
-            issubclass(target.dispatch, cls.dispatch)
+
+                isinstance(target.dispatch, cls.dispatch.__class__) or
+
+
+                (
+                    isinstance(target.dispatch, type) and
+                    isinstance(target.dispatch, cls.dispatch.__class__)
+                ) or
+
+                (
+                    isinstance(target.dispatch, _JoinedDispatcher) and
+                    isinstance(target.dispatch.parent, cls.dispatch.__class__)
+                )
+
+
         ):
             return target
         else:
@@ -195,12 +238,19 @@ class Events(util.with_metaclass(_EventMeta, object)):
 class _JoinedDispatcher(object):
     """Represent a connection between two _Dispatch objects."""
 
-    __slots__ = 'local', 'parent', '_parent_cls'
+    __slots__ = 'local', 'parent', '_instance_cls'
 
     def __init__(self, local, parent):
         self.local = local
         self.parent = parent
-        self._parent_cls = local._parent_cls
+        self._instance_cls = self.local._instance_cls
+        for ls in local._event_descriptors:
+            setattr(self, ls.name, _JoinedListener(
+                parent, ls.name, ls))
+
+    @property
+    def _listen(self):
+        return self.parent._listen
 
 
 class dispatcher(object):
@@ -218,5 +268,5 @@ class dispatcher(object):
     def __get__(self, obj, cls):
         if obj is None:
             return self.dispatch_cls
-        obj.__dict__['dispatch'] = disp = self.dispatch_cls(cls)
+        obj.__dict__['dispatch'] = disp = self.dispatch_cls._for_instance(obj)
         return disp
index 3b1519cb6b97cdfb87ed823d85c49606547a60e4..7513c7d4d0b3422a429a3ac5c5d55d93976bdb1c 100644 (file)
@@ -22,8 +22,8 @@ def _legacy_signature(since, argnames, converter=None):
     return leg
 
 
-def _wrap_fn_for_legacy(dispatch_descriptor, fn, argspec):
-    for since, argnames, conv in dispatch_descriptor.legacy_signatures:
+def _wrap_fn_for_legacy(dispatch_collection, fn, argspec):
+    for since, argnames, conv in dispatch_collection.legacy_signatures:
         if argnames[-1] == "**kw":
             has_kw = True
             argnames = argnames[0:-1]
@@ -40,7 +40,7 @@ def _wrap_fn_for_legacy(dispatch_descriptor, fn, argspec):
                     return fn(*conv(*args))
             else:
                 def wrap_leg(*args, **kw):
-                    argdict = dict(zip(dispatch_descriptor.arg_names, args))
+                    argdict = dict(zip(dispatch_collection.arg_names, args))
                     args = [argdict[name] for name in argnames]
                     if has_kw:
                         return fn(*args, **kw)
@@ -58,16 +58,16 @@ def _indent(text, indent):
     )
 
 
-def _standard_listen_example(dispatch_descriptor, sample_target, fn):
+def _standard_listen_example(dispatch_collection, sample_target, fn):
     example_kw_arg = _indent(
         "\n".join(
             "%(arg)s = kw['%(arg)s']" % {"arg": arg}
-            for arg in dispatch_descriptor.arg_names[0:2]
+            for arg in dispatch_collection.arg_names[0:2]
         ),
         "    ")
-    if dispatch_descriptor.legacy_signatures:
+    if dispatch_collection.legacy_signatures:
         current_since = max(since for since, args, conv
-                            in dispatch_descriptor.legacy_signatures)
+                            in dispatch_collection.legacy_signatures)
     else:
         current_since = None
     text = (
@@ -80,7 +80,7 @@ def _standard_listen_example(dispatch_descriptor, sample_target, fn):
         "\n    # ... (event handling logic) ...\n"
     )
 
-    if len(dispatch_descriptor.arg_names) > 3:
+    if len(dispatch_collection.arg_names) > 3:
         text += (
 
             "\n# named argument style (new in 0.9)\n"
@@ -96,17 +96,17 @@ def _standard_listen_example(dispatch_descriptor, sample_target, fn):
         "current_since": " (arguments as of %s)" %
         current_since if current_since else "",
         "event_name": fn.__name__,
-        "has_kw_arguments": ", **kw" if dispatch_descriptor.has_kw else "",
-        "named_event_arguments": ", ".join(dispatch_descriptor.arg_names),
+        "has_kw_arguments": ", **kw" if dispatch_collection.has_kw else "",
+        "named_event_arguments": ", ".join(dispatch_collection.arg_names),
         "example_kw_arg": example_kw_arg,
         "sample_target": sample_target
     }
     return text
 
 
-def _legacy_listen_examples(dispatch_descriptor, sample_target, fn):
+def _legacy_listen_examples(dispatch_collection, sample_target, fn):
     text = ""
-    for since, args, conv in dispatch_descriptor.legacy_signatures:
+    for since, args, conv in dispatch_collection.legacy_signatures:
         text += (
             "\n# legacy calling style (pre-%(since)s)\n"
             "@event.listens_for(%(sample_target)s, '%(event_name)s')\n"
@@ -117,7 +117,7 @@ def _legacy_listen_examples(dispatch_descriptor, sample_target, fn):
                 "since": since,
                 "event_name": fn.__name__,
                 "has_kw_arguments": " **kw"
-                if dispatch_descriptor.has_kw else "",
+                if dispatch_collection.has_kw else "",
                 "named_event_arguments": ", ".join(args),
                 "sample_target": sample_target
             }
@@ -125,8 +125,8 @@ def _legacy_listen_examples(dispatch_descriptor, sample_target, fn):
     return text
 
 
-def _version_signature_changes(dispatch_descriptor):
-    since, args, conv = dispatch_descriptor.legacy_signatures[0]
+def _version_signature_changes(dispatch_collection):
+    since, args, conv = dispatch_collection.legacy_signatures[0]
     return (
         "\n.. versionchanged:: %(since)s\n"
         "    The ``%(event_name)s`` event now accepts the \n"
@@ -135,14 +135,14 @@ def _version_signature_changes(dispatch_descriptor):
         "    signature(s) listed above will be automatically \n"
         "    adapted to the new signature." % {
             "since": since,
-            "event_name": dispatch_descriptor.__name__,
-            "named_event_arguments": ", ".join(dispatch_descriptor.arg_names),
-            "has_kw_arguments": ", **kw" if dispatch_descriptor.has_kw else ""
+            "event_name": dispatch_collection.name,
+            "named_event_arguments": ", ".join(dispatch_collection.arg_names),
+            "has_kw_arguments": ", **kw" if dispatch_collection.has_kw else ""
         }
     )
 
 
-def _augment_fn_docs(dispatch_descriptor, parent_dispatch_cls, fn):
+def _augment_fn_docs(dispatch_collection, parent_dispatch_cls, fn):
     header = ".. container:: event_signatures\n\n"\
         "     Example argument forms::\n"\
         "\n"
@@ -152,16 +152,16 @@ def _augment_fn_docs(dispatch_descriptor, parent_dispatch_cls, fn):
         header +
         _indent(
             _standard_listen_example(
-                dispatch_descriptor, sample_target, fn),
+                dispatch_collection, sample_target, fn),
             " " * 8)
     )
-    if dispatch_descriptor.legacy_signatures:
+    if dispatch_collection.legacy_signatures:
         text += _indent(
             _legacy_listen_examples(
-                dispatch_descriptor, sample_target, fn),
+                dispatch_collection, sample_target, fn),
             " " * 8)
 
-        text += _version_signature_changes(dispatch_descriptor)
+        text += _version_signature_changes(dispatch_collection)
 
     return util.inject_docstring_text(fn.__doc__,
                                       text,
index fc26f91d72f5f365497f9962c41a7ce9593a212f..ebc0e6d1823800499a76c8842157b5a46595a49d 100644 (file)
@@ -37,7 +37,7 @@ listener collections and the listener fn contained
 
 _collection_to_key = collections.defaultdict(dict)
 """
-Given a _ListenerCollection or _DispatchDescriptor, can locate
+Given a _ListenerCollection or _ClsLevelListener, can locate
 all the original listen() arguments and the listener fn contained
 
 ref(listenercollection) -> {
@@ -191,9 +191,9 @@ class _EventKey(object):
         target, identifier, fn = \
             self.dispatch_target, self.identifier, self._listen_fn
 
-        dispatch_descriptor = getattr(target.dispatch, identifier)
+        dispatch_collection = getattr(target.dispatch, identifier)
 
-        adjusted_fn = dispatch_descriptor._adjust_fn_spec(fn, named)
+        adjusted_fn = dispatch_collection._adjust_fn_spec(fn, named)
 
         self = self.with_wrapper(adjusted_fn)
 
@@ -230,13 +230,13 @@ class _EventKey(object):
         target, identifier, fn = \
             self.dispatch_target, self.identifier, self._listen_fn
 
-        dispatch_descriptor = getattr(target.dispatch, identifier)
+        dispatch_collection = getattr(target.dispatch, identifier)
 
         if insert:
-            dispatch_descriptor.\
+            dispatch_collection.\
                 for_modify(target.dispatch).insert(self, propagate)
         else:
-            dispatch_descriptor.\
+            dispatch_collection.\
                 for_modify(target.dispatch).append(self, propagate)
 
     @property
index 9ea0dd834a73b814e58e944962688c119a2bbd7f..4d888a3507d88d83c249f2a5191532c4c2d2e8a9 100644 (file)
@@ -1479,8 +1479,9 @@ class AttributeEvents(event.Events):
 
     @staticmethod
     def _set_dispatch(cls, dispatch_cls):
-        event.Events._set_dispatch(cls, dispatch_cls)
+        dispatch = event.Events._set_dispatch(cls, dispatch_cls)
         dispatch_cls._active_history = False
+        return dispatch
 
     @classmethod
     def _accept_with(cls, target):
index c61d93230ab7060a968cac45588104fb14239fbe..9fe6b77f0557e10813ca52bcaa19cedfb0af7c9c 100644 (file)
@@ -2643,7 +2643,7 @@ def configure_mappers():
             if not Mapper._new_mappers:
                 return
 
-            Mapper.dispatch(Mapper).before_configured()
+            Mapper.dispatch._for_class(Mapper).before_configured()
             # initialize properties on all mappers
             # note that _mapper_registry is unordered, which
             # may randomly conceal/reveal issues related to
@@ -2675,7 +2675,7 @@ def configure_mappers():
             _already_compiling = False
     finally:
         _CONFIGURE_MUTEX.release()
-    Mapper.dispatch(Mapper).after_configured()
+    Mapper.dispatch._for_class(Mapper).after_configured()
 
 
 def reconstructor(fn):
index a147685d9d8542ac2be7c50c52efad056adbddb9..253bd77b801fea9fceb4778ea5d1e3fbf5926321 100644 (file)
@@ -230,6 +230,7 @@ class Pool(log.Identified):
                 % reset_on_return)
 
         self.echo = echo
+
         if _dispatch:
             self.dispatch._update(_dispatch, only_propagate=False)
         if _dialect:
index 89379961e7a96f7b45d89aab816020ab03cf75b5..1449bfab0fd278cecd895381cf1b98395e5ad6ae 100644 (file)
@@ -154,25 +154,16 @@ class EventsTest(fixtures.TestBase):
         t2 = self.Target()
         t1.dispatch.event_one(5, 6)
         t2.dispatch.event_one(5, 6)
-        is_(
-            t1.dispatch.__dict__['event_one'],
-            self.Target.dispatch.event_one.
-            _empty_listeners[self.Target]
-        )
+        assert t1.dispatch.event_one in \
+            self.Target.dispatch._empty_listeners[self.Target]
 
         @event.listens_for(t1, "event_one")
         def listen_two(x, y):
             pass
-        is_not_(
-            t1.dispatch.__dict__['event_one'],
-            self.Target.dispatch.event_one.
-            _empty_listeners[self.Target]
-        )
-        is_(
-            t2.dispatch.__dict__['event_one'],
-            self.Target.dispatch.event_one.
-            _empty_listeners[self.Target]
-        )
+        assert t1.dispatch.event_one not in \
+            self.Target.dispatch._empty_listeners[self.Target]
+        assert t2.dispatch.event_one in \
+            self.Target.dispatch._empty_listeners[self.Target]
 
     def test_immutable_methods(self):
         t1 = self.Target()