selectors module
Overview
The selectors module provides a high-level interface for I/O multiplexing — the ability to monitor many sockets or file objects simultaneously and react when they become readable or writable. It sits above the select module and automatically picks the most efficient implementation available on your platform (epoll on Linux, kqueue on macOS/BSD, poll on older systems).
The key abstraction is BaseSelector and its concrete implementations. You register file objects with a selector, then call select() which blocks until at least one registered object is ready for I/O.
Available Selector Classes
| Class | Underlying primitive | Best for |
|---|---|---|
DefaultSelector | Platform’s best option | Most users — auto-selects the right implementation |
SelectSelector | select.select() | Portable; works everywhere but slower |
PollSelector | select.poll() | Linux; better scalability than select |
EpollSelector | select.epoll() | Linux only; best performance on Linux |
KqueueSelector | select.kqueue() | macOS/BSD; handles many connections efficiently |
DevpollSelector | select.devpoll() | Solaris-derived systems |
For almost all cases, use DefaultSelector:
import selectors
sel = selectors.DefaultSelector()
Event Constants
Two bitwise constants define what to wait for:
import selectors
selectors.EVENT_READ # file descriptor has data to read
selectors.EVENT_WRITE # file descriptor is ready to accept data
You can combine them with | (bitwise OR):
events = selectors.EVENT_READ | selectors.EVENT_WRITE
Registering and Unregistering
register(fileobj, events, data=None)
Register a file object (socket, file descriptor integer, or any object with a fileno() method) to be monitored:
import selectors
import socket
sel = selectors.DefaultSelector()
server = socket.socket()
server.bind(("localhost", 5000))
server.listen(100)
server.setblocking(False)
sel.register(server, selectors.EVENT_READ, data=accept)
# Returns a SelectorKey namedtuple
The data parameter attaches arbitrary opaque data to the registration — typically a callback function.
unregister(fileobj)
Stop monitoring a file object. The file object must be unregistered before closing, otherwise the behavior is undefined:
key = sel.unregister(client_socket)
# key is the SelectorKey that was associated with this socket
The select() Method
Block until one or more registered file objects are ready, or the timeout expires:
events = sel.select(timeout=None)
# events: list of (SelectorKey, event_mask) tuples
timeout=None— block indefinitely until something is readytimeout=0— non-blocking; check what’s ready right nowtimeout=5.0— wait up to 5 seconds
The return value is a list of (key, mask) tuples:
for key, mask in sel.select():
callback = key.data # the data you attached during register()
fileobj = key.fileobj # the socket or file object
events = mask # EVENT_READ and/or EVENT_WRITE that are ready
callback(fileobj, mask)
The SelectorKey Namedtuple
Every registration returns a SelectorKey with four fields:
key.fileobj # the registered file object or fd
key.fd # the underlying file descriptor (int)
key.events # bitmask of events being monitored
key.data # the opaque data you attached (e.g., a callback)
Modifying Registration
modify(fileobj, events, data=None)
Change the events or data for a registered file object, without unregistering and re-registering:
key = sel.modify(client, selectors.EVENT_READ, new_callback)
This is equivalent to unregister() + register(), but often implemented more efficiently.
Complete Echo Server Example
import selectors
import socket
sel = selectors.DefaultSelector()
def accept(sock, mask):
conn, addr = sock.accept()
print(f"accepted {conn} from {addr}")
conn.setblocking(False)
# Register the new connection for read events, with read as callback
sel.register(conn, selectors.EVENT_READ, read)
def read(conn, mask):
data = conn.recv(1024)
if data:
print(f"echoing {data!r} to {conn}")
conn.sendall(data)
else:
print(f"closing {conn}")
sel.unregister(conn)
conn.close()
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("localhost", 12345))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)
print("Echo server running on localhost:12345")
while True:
events = sel.select()
for key, mask in events:
callback = key.data
callback(key.fileobj, mask)
Run this and connect with nc localhost 12345 — type anything and it echoes back.
Closing the Selector
Always close the selector when done to free resources:
with selectors.DefaultSelector() as sel:
# ... use the selector
# selector is automatically closed on exit from the with block
Or explicitly:
sel.close()
Common Use Cases
Event-driven TCP servers. Instead of threading or spawning a process per connection, a single thread loops over select() and dispatches to callbacks. This scales to thousands of concurrent connections.
IRC bots, chat servers, lightweight network tools. The selector loop pattern is the foundation of any non-blocking, single-threaded networking code.
Mixing socket and pipe I/O. Register both sockets and pipes (on Unix) with the same selector to wait on all I/O sources uniformly.
Gotchas
Unregister before closing. If you close a file object while it’s still registered, the behavior is undefined — may crash or produce spurious events. Always unregister() first.
Different primitives behave differently. epoll and kqueue are level-triggered, while select on Windows only works with sockets (not pipes). DefaultSelector hides these differences, but if you need specific behavior, check which implementation is in use:
import selectors
sel = selectors.DefaultSelector()
print(type(sel).__name__) # e.g., EpollSelector, KqueueSelector
Callback runs on the selector thread. The select() loop is single-threaded. If a callback blocks, no other file objects get served. For CPU-intensive work, hand off to a thread pool:
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=4)
def read(conn, mask):
data = conn.recv(1024)
if data:
# Offload CPU work to a thread pool
executor.submit(process_andRespond, conn, data)
Windows limitations. On Windows, selectors only supports sockets — not pipes, FIFOs, or regular files. On Unix, all of these work.
Data attached is per-registration, not per-file-object. If you register the same socket twice (not typical), the second registration will raise KeyError. Use modify() to update existing registrations.
See Also
- /reference/modules/socket-module/ — low-level networking primitives used alongside selectors
- /guides/python-contextvars/ — context variables for thread-safe state in async and threaded servers
- /tutorials/asyncio-tutorial/ — asyncio uses selectors internally under the hood