Как использовать сокет в Python в качестве менеджера контекста?

Похоже, что было бы естественно делать что-то вроде:

with socket(socket.AF_INET, socket.SOCK_DGRAM) as s:

но Python не реализует диспетчер контекста для сокета. Могу ли я легко использовать его в качестве менеджера контекста, и если да, то как?

Ответ 1

Модуль socket довольно низкоуровневый, что дает вам почти прямой доступ к функциям библиотеки C.

Вы всегда можете использовать contextlib.contextmanager decorator, чтобы создать свой собственный:

import socket
from contextlib import contextmanager

@contextmanager
def socketcontext(*args, **kw):
    s = socket.socket(*args, **kw)
    try:
        yield s
    finally:
        s.close()

with socketcontext(socket.AF_INET, socket.SOCK_DGRAM) as s:

или используйте contextlib.closing() для достижения такого же эффекта:

from contextlib import closing

with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as s:

но декоратор contextmanager() дает вам возможность делать другие вещи с помощью сокета в первую очередь.

Python 3.x делает socket() менеджером контекста, хотя документация не упоминает об этом. См. socket class в исходном коде, который добавляет методы __enter__ и __exit__.

Ответ 2

Модуль сокетов - это всего лишь оболочка интерфейса сокетов BSD. Он низкоуровневый и на самом деле не пытается предоставить вам удобный или простой в использовании API Pythonic. Вы можете использовать что-то более высокоуровневое.

Тем не менее, на самом деле он реализует диспетчер контекстов:

>>> with socket.socket() as s:
...   print(s)
... 
<socket.socket object, fd=3, family=2, type=1, proto=0>

Но вам нужно использовать Python 3.

Для совместимости с Python 2 вы можете использовать contextlib.

from contextlib import closing
import socket

with closing(socket.socket()) as s:
    print s

Ответ 3

Обратите внимание на следующие фрагменты: для сокетов TCP и UDP

import socket
from contextlib import contextmanager


@contextmanager
def tcp_connection_to(*args, **kwargs):
    s = socket.create_connection(*args, **kwargs)
    yield s
    s.close()


@contextmanager
def udp_connection():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    yield s
    s.close()

Чтобы вы могли использовать их следующим образом:

MY_SERVER = ('localhost', 5000)   # Yes, we need tuple here
some_data = bytes("Hello.")

with tcp_connection_to(MY_SERVER) as conn:
    conn.send(some_data)

with udp_connection() as conn:
    conn.sendto(some_data, MY_SERVER)

Я также попытался подчеркнуть разницу в поведении и подходе к терминам "соединение" между TCP и UDP в именах методов.