[Python-checkins] cpython: Issue #19842: Refactor BaseSelector to make it an actual usable ABC.

charles-francois.natali python-checkins at python.org
Sun Dec 1 11:05:00 CET 2013


http://hg.python.org/cpython/rev/f48f302f54aa
changeset:   87677:f48f302f54aa
user:        Charles-François Natali <cf.natali at gmail.com>
date:        Sun Dec 01 11:04:17 2013 +0100
summary:
  Issue #19842: Refactor BaseSelector to make it an actual usable ABC.

files:
  Lib/asyncio/test_utils.py  |   14 +++
  Lib/selectors.py           |  112 +++++++++++++++---------
  Lib/test/test_telnetlib.py |    7 +-
  3 files changed, 86 insertions(+), 47 deletions(-)


diff --git a/Lib/asyncio/test_utils.py b/Lib/asyncio/test_utils.py
--- a/Lib/asyncio/test_utils.py
+++ b/Lib/asyncio/test_utils.py
@@ -142,9 +142,23 @@
 
 class TestSelector(selectors.BaseSelector):
 
+    def __init__(self):
+        self.keys = {}
+
+    def register(self, fileobj, events, data=None):
+        key = selectors.SelectorKey(fileobj, 0, events, data)
+        self.keys[fileobj] = key
+        return key
+
+    def unregister(self, fileobj):
+        return self.keys.pop(fileobj)
+
     def select(self, timeout):
         return []
 
+    def get_map(self):
+        return self.keys
+
 
 class TestLoop(base_events.BaseEventLoop):
     """Loop for unittests.
diff --git a/Lib/selectors.py b/Lib/selectors.py
--- a/Lib/selectors.py
+++ b/Lib/selectors.py
@@ -64,7 +64,7 @@
 
 
 class BaseSelector(metaclass=ABCMeta):
-    """Base selector class.
+    """Selector abstract base class.
 
     A selector supports registering file objects to be monitored for specific
     I/O events.
@@ -78,12 +78,7 @@
     performant implementation on the current platform.
     """
 
-    def __init__(self):
-        # this maps file descriptors to keys
-        self._fd_to_key = {}
-        # read-only mapping returned by get_map()
-        self._map = _SelectorMapping(self)
-
+    @abstractmethod
     def register(self, fileobj, events, data=None):
         """Register a file object.
 
@@ -95,18 +90,9 @@
         Returns:
         SelectorKey instance
         """
-        if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):
-            raise ValueError("Invalid events: {!r}".format(events))
+        raise NotImplementedError
 
-        key = SelectorKey(fileobj, _fileobj_to_fd(fileobj), events, data)
-
-        if key.fd in self._fd_to_key:
-            raise KeyError("{!r} (FD {}) is already "
-                           "registered".format(fileobj, key.fd))
-
-        self._fd_to_key[key.fd] = key
-        return key
-
+    @abstractmethod
     def unregister(self, fileobj):
         """Unregister a file object.
 
@@ -116,11 +102,7 @@
         Returns:
         SelectorKey instance
         """
-        try:
-            key = self._fd_to_key.pop(_fileobj_to_fd(fileobj))
-        except KeyError:
-            raise KeyError("{!r} is not registered".format(fileobj)) from None
-        return key
+        raise NotImplementedError
 
     def modify(self, fileobj, events, data=None):
         """Change a registered file object monitored events or attached data.
@@ -133,19 +115,8 @@
         Returns:
         SelectorKey instance
         """
-        # TODO: Subclasses can probably optimize this even further.
-        try:
-            key = self._fd_to_key[_fileobj_to_fd(fileobj)]
-        except KeyError:
-            raise KeyError("{!r} is not registered".format(fileobj)) from None
-        if events != key.events:
-            self.unregister(fileobj)
-            key = self.register(fileobj, events, data)
-        elif data != key.data:
-            # Use a shortcut to update the data.
-            key = key._replace(data=data)
-            self._fd_to_key[key.fd] = key
-        return key
+        self.unregister(fileobj)
+        return self.register(fileobj, events, data)
 
     @abstractmethod
     def select(self, timeout=None):
@@ -164,14 +135,14 @@
         list of (key, events) for ready file objects
         `events` is a bitwise mask of EVENT_READ|EVENT_WRITE
         """
-        raise NotImplementedError()
+        raise NotImplementedError
 
     def close(self):
         """Close the selector.
 
         This must be called to make sure that any underlying resource is freed.
         """
-        self._fd_to_key.clear()
+        pass
 
     def get_key(self, fileobj):
         """Return the key associated to a registered file object.
@@ -179,14 +150,16 @@
         Returns:
         SelectorKey for this file object
         """
+        mapping = self.get_map()
         try:
-            return self._fd_to_key[_fileobj_to_fd(fileobj)]
+            return mapping[fileobj]
         except KeyError:
             raise KeyError("{!r} is not registered".format(fileobj)) from None
 
+    @abstractmethod
     def get_map(self):
         """Return a mapping of file objects to selector keys."""
-        return self._map
+        raise NotImplementedError
 
     def __enter__(self):
         return self
@@ -194,6 +167,57 @@
     def __exit__(self, *args):
         self.close()
 
+
+class _BaseSelectorImpl(BaseSelector):
+    """Base selector implementation."""
+
+    def __init__(self):
+        # this maps file descriptors to keys
+        self._fd_to_key = {}
+        # read-only mapping returned by get_map()
+        self._map = _SelectorMapping(self)
+
+    def register(self, fileobj, events, data=None):
+        if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):
+            raise ValueError("Invalid events: {!r}".format(events))
+
+        key = SelectorKey(fileobj, _fileobj_to_fd(fileobj), events, data)
+
+        if key.fd in self._fd_to_key:
+            raise KeyError("{!r} (FD {}) is already "
+                           "registered".format(fileobj, key.fd))
+
+        self._fd_to_key[key.fd] = key
+        return key
+
+    def unregister(self, fileobj):
+        try:
+            key = self._fd_to_key.pop(_fileobj_to_fd(fileobj))
+        except KeyError:
+            raise KeyError("{!r} is not registered".format(fileobj)) from None
+        return key
+
+    def modify(self, fileobj, events, data=None):
+        # TODO: Subclasses can probably optimize this even further.
+        try:
+            key = self._fd_to_key[_fileobj_to_fd(fileobj)]
+        except KeyError:
+            raise KeyError("{!r} is not registered".format(fileobj)) from None
+        if events != key.events:
+            self.unregister(fileobj)
+            key = self.register(fileobj, events, data)
+        elif data != key.data:
+            # Use a shortcut to update the data.
+            key = key._replace(data=data)
+            self._fd_to_key[key.fd] = key
+        return key
+
+    def close(self):
+        self._fd_to_key.clear()
+
+    def get_map(self):
+        return self._map
+
     def _key_from_fd(self, fd):
         """Return the key associated to a given file descriptor.
 
@@ -209,7 +233,7 @@
             return None
 
 
-class SelectSelector(BaseSelector):
+class SelectSelector(_BaseSelectorImpl):
     """Select-based selector."""
 
     def __init__(self):
@@ -262,7 +286,7 @@
 
 if hasattr(select, 'poll'):
 
-    class PollSelector(BaseSelector):
+    class PollSelector(_BaseSelectorImpl):
         """Poll-based selector."""
 
         def __init__(self):
@@ -306,7 +330,7 @@
 
 if hasattr(select, 'epoll'):
 
-    class EpollSelector(BaseSelector):
+    class EpollSelector(_BaseSelectorImpl):
         """Epoll-based selector."""
 
         def __init__(self):
@@ -358,7 +382,7 @@
 
 if hasattr(select, 'kqueue'):
 
-    class KqueueSelector(BaseSelector):
+    class KqueueSelector(_BaseSelectorImpl):
         """Kqueue-based selector."""
 
         def __init__(self):
diff --git a/Lib/test/test_telnetlib.py b/Lib/test/test_telnetlib.py
--- a/Lib/test/test_telnetlib.py
+++ b/Lib/test/test_telnetlib.py
@@ -114,7 +114,6 @@
 class MockSelector(selectors.BaseSelector):
 
     def __init__(self):
-        super().__init__()
         self.keys = {}
 
     def register(self, fileobj, events, data=None):
@@ -123,8 +122,7 @@
         return key
 
     def unregister(self, fileobj):
-        key = self.keys.pop(fileobj)
-        return key
+        return self.keys.pop(fileobj)
 
     def select(self, timeout=None):
         block = False
@@ -137,6 +135,9 @@
         else:
             return [(key, key.events) for key in self.keys.values()]
 
+    def get_map(self):
+        return self.keys
+
 
 @contextlib.contextmanager
 def test_socket(reads):

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list