O módulo selectors oferece uma interface de alto nível para esperar eventos de entrada e saída em vários descritores ao mesmo tempo. Ele é usado principalmente com sockets não bloqueantes, mas em sistemas Unix também pode trabalhar com pipes, FIFOs e alguns dispositivos. Em vez de criar uma thread por conexão, um único loop pode reagir apenas quando cada recurso estiver pronto para leitura ou escrita.
O módulo encapsula primitivas de baixo nível como select, poll, epoll e kqueue. Para a maioria das aplicações, DefaultSelector escolhe automaticamente a implementação mais eficiente disponível.
Quando usar selectors
Use selectors quando você precisa controlar diretamente um loop de eventos, integrar protocolos simples, monitorar muitos sockets ou construir uma camada de rede sem depender de um framework completo. Para aplicações assíncronas comuns, asyncio normalmente oferece uma abstração mais produtiva.
O módulo exige que o código administre buffers, estados parciais, desconexões, timeouts e backpressure. Ele é poderoso, mas não transforma automaticamente uma operação bloqueante em não bloqueante.
Criar o seletor padrão
import selectors
with selectors.DefaultSelector() as seletor:
print(type(seletor).__name__)O context manager garante a liberação do recurso interno. Depois de close(), o seletor não pode ser reutilizado.
Eventos de leitura e escrita
Há duas máscaras principais: EVENT_READ e EVENT_WRITE. Elas podem ser combinadas com o operador bit a bit |.
eventos = selectors.EVENT_READ | selectors.EVENT_WRITE“Pronto para leitura” não significa necessariamente que haverá dados úteis. Em um socket, também pode indicar EOF ou uma desconexão. “Pronto para escrita” indica que o sistema aceita algum envio naquele momento, não que um buffer inteiro será enviado de uma vez.
Registrar um socket
import socket
import selectors
seletor = selectors.DefaultSelector()
servidor = socket.socket()
servidor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
servidor.bind(("127.0.0.1", 9000))
servidor.listen()
servidor.setblocking(False)
seletor.register(
servidor,
selectors.EVENT_READ,
data={"tipo": "servidor"},
)register() associa o objeto, os eventos monitorados e um campo data arbitrário. Esse campo pode guardar um callback, ID de sessão ou objeto de estado.
Aceitar conexões
def aceitar(sock, seletor):
conexao, endereco = sock.accept()
conexao.setblocking(False)
estado = {
"endereco": endereco,
"entrada": bytearray(),
"saida": bytearray(),
}
seletor.register(conexao, selectors.EVENT_READ, estado)O novo socket também precisa ser não bloqueante. Uma conexão deve ser removida do seletor antes de ser fechada.
O loop de eventos
while True:
eventos = seletor.select(timeout=1.0)
for chave, mascara in eventos:
if chave.fileobj is servidor:
aceitar(servidor, seletor)
else:
processar(chave, mascara, seletor)select() devolve pares (SelectorKey, events). O SelectorKey contém o objeto registrado, descritor, máscara solicitada e dados associados.
Timeouts
Com timeout=None, a chamada espera indefinidamente. Um valor positivo limita a espera em segundos. Zero ou um valor negativo apenas consulta os eventos já prontos.
O timeout do seletor não substitui timeouts de protocolo. Para encerrar clientes inativos, registre o último horário de atividade e verifique-o periodicamente.
Receber dados sem bloquear
def receber(sock, estado):
try:
dados = sock.recv(4096)
except BlockingIOError:
return
if dados:
estado["entrada"].extend(dados)
else:
raise ConnectionResetError("cliente encerrou")Mesmo após um evento, trate BlockingIOError, pois o estado pode mudar entre a notificação e a chamada. Um retorno vazio de recv() normalmente indica fechamento ordenado do outro lado.
Buffers e mensagens parciais
TCP é um fluxo de bytes. Uma chamada pode devolver metade de uma mensagem, várias mensagens ou qualquer divisão. O protocolo precisa definir enquadramento, como tamanho prefixado, delimitador ou formato autodescritivo.
def extrair_linhas(buffer):
linhas = []
while b"\n" in buffer:
linha, _, restante = buffer.partition(b"\n")
linhas.append(linha)
buffer[:] = restante
return linhasDefina limites para impedir que um cliente envie dados sem delimitador e consuma memória indefinidamente.
Escrita parcial e backpressure
def enviar(sock, estado):
if not estado["saida"]:
return
try:
enviados = sock.send(estado["saida"])
except BlockingIOError:
return
del estado["saida"][:enviados]send() pode enviar menos bytes que o solicitado. Mantenha o restante no buffer. Monitore EVENT_WRITE somente quando houver dados pendentes; sockets quase sempre estão prontos para escrita, e registrá-los continuamente pode criar um loop ocupado.
Modificar eventos registrados
def atualizar_interesse(sock, estado, seletor):
eventos = selectors.EVENT_READ
if estado["saida"]:
eventos |= selectors.EVENT_WRITE
seletor.modify(sock, eventos, estado)modify() altera máscara e dados de forma mais eficiente que remover e registrar novamente.
Encerrar uma conexão
def fechar(sock, seletor):
try:
seletor.unregister(sock)
except KeyError:
pass
sock.close()Remova o objeto antes do fechamento. Trate erros de conexão individualmente para que um cliente defeituoso não derrube o servidor inteiro.
Diferenças entre plataformas
No Windows, os seletores suportam sockets, mas pipes comuns não funcionam da mesma forma que em Unix. Em Unix, sockets, pipes e FIFOs podem ser suportados, dependendo da implementação.
DefaultSelector melhora portabilidade, mas não elimina diferenças de objetos monitoráveis. Teste o comportamento nas plataformas realmente suportadas. O módulo não está disponível em WASI.
Dados associados e objetos de sessão
Em projetos maiores, use uma dataclass para manter buffers, endereço, estado do protocolo, prazo e métricas.
from dataclasses import dataclass, field
@dataclass
class Sessao:
endereco: tuple
entrada: bytearray = field(default_factory=bytearray)
saida: bytearray = field(default_factory=bytearray)
fechando: bool = FalseSeparar estado e transporte facilita testes. Não armazene dados ilimitados ou segredos desnecessários.
Sinais e interrupções
Desde Python 3.5, o seletor normalmente recalcula o timeout e repete a espera quando um sinal interrompe a chamada e o handler não gera exceção. Ainda assim, o loop deve aceitar uma lista vazia e processar tarefas periódicas.
Uso de CPU
Um loop pode consumir 100% de CPU quando monitora escrita o tempo todo, usa timeout zero sem necessidade ou repete após erros. Registre somente eventos úteis e use um timeout coerente.
Meça quantidade de conexões, bytes em buffers, latência do loop e callbacks lentos. Qualquer processamento demorado bloqueia todas as conexões do mesmo loop.
Trabalho pesado fora do loop
Parsing pequeno pode ocorrer no loop, mas CPU intensiva, consultas bloqueantes e acesso lento a disco devem ser delegados. Ao retornar resultados, acorde o loop por uma técnica segura, como socketpair, fila integrada ou mecanismo do framework.
Exemplo de processamento
def processar(chave, mascara, seletor):
sock = chave.fileobj
estado = chave.data
try:
if mascara & selectors.EVENT_READ:
receber(sock, estado)
for linha in extrair_linhas(estado["entrada"]):
estado["saida"].extend(linha.upper() + b"\n")
if mascara & selectors.EVENT_WRITE:
enviar(sock, estado)
atualizar_interesse(sock, estado, seletor)
except (ConnectionError, OSError):
fechar(sock, seletor)Em produção, inclua limites, logs estruturados, validação do protocolo, cancelamento e encerramento gracioso.
Selectors versus asyncio
asyncio usa conceitos semelhantes e frequentemente utiliza seletores internamente em plataformas compatíveis. Escolha selectors para controle explícito ou integração de baixo nível. Escolha asyncio para tarefas, corrotinas, streams, timeouts e bibliotecas assíncronas.
Erros frequentes
- Esquecer
setblocking(False). - Fechar antes de remover do seletor.
- Esperar que
recv()devolva uma mensagem inteira. - Ignorar escrita parcial.
- Monitorar escrita permanentemente.
- Executar trabalho bloqueante no loop.
- Assumir suporte igual a pipes no Windows e Unix.
Boas práticas
- Use
DefaultSelector. - Mantenha buffers com limites.
- Registre escrita apenas quando necessária.
- Trate erros por conexão.
- Implemente timeouts de protocolo.
- Libere seletor e sockets com context managers.
- Teste desconexões, mensagens fragmentadas e clientes lentos.
Conteúdos relacionados
Veja contextvars, ExitStack, shlex, faulthandler e platform.
Consulte a documentação oficial do selectors e a documentação de sockets.
Conclusão
selectors permite construir loops de I/O eficientes e portáveis com controle direto sobre leitura, escrita e estado de cada conexão. A robustez depende de buffers, backpressure, timeouts, tratamento de erros e da ausência de operações bloqueantes no loop.







