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

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

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

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


person ChaimKut    schedule 27.05.2013    source источник
comment
Почему вопросы вообще не являются хорошими вопросами для SO. Возможно, вы можете переписать это как? :-)   -  person Lennart Regebro    schedule 27.05.2013
comment
@msw: Нет. Это просто замаскированный вопрос «почему». Хороший вопрос: как использовать сокет в качестве менеджера контекста? Текущий вопрос, на который я мог бы правильно ответить. Это или нет, или да. Не очень полезно.   -  person Lennart Regebro    schedule 27.05.2013
comment
@msw: Во-первых, это просто еще одно подтверждение того, почему вопросы плохие, поскольку они могут вызвать бесконечное почему бы и не спуститься в ад. :-) Во-вторых, я думаю, вы неправильно понимаете, что такое менеджеры контекста. Ответ «сокет не похож на файл» не имеет смысла в качестве ответа на вопрос «почему» в этом случае. Совершенно неважно, что это не похоже на файл. Контекстные менеджеры предназначены не только для файлов.   -  person Lennart Regebro    schedule 27.05.2013
comment
@msw: я не изменил смысла, я сделал хороший вопрос.   -  person Lennart Regebro    schedule 27.05.2013
comment
@msw: обратите внимание, что закрытие на самом деле является временным состоянием; улучшите вопрос, и его можно снова открыть. Улучшить вопрос до его закрытия — еще лучшая идея.   -  person Martijn Pieters    schedule 27.05.2013


Ответы (3)


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

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

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() менеджером контекста, но документация не была обновлена, чтобы отразить это до в цикле Python 3.5 в 2016 году. См. socket class в исходном коде, который добавляет методы __enter__ и __exit__.

person Martijn Pieters    schedule 27.05.2013
comment
Python3 with/as кажется очень полезным, я не могу придумать причину, чтобы оставить это вне документации, вы случайно не знаете, почему его там нет? - person Ryan Haining; 01.04.2014
comment
@RyanHaining: отправитель исправления просто забыл задокументировать его должным образом. - person Martijn Pieters; 01.04.2014
comment
@RyanHaining: патч обновил socket.create_connection(), но он как бы скрыт . - person Martijn Pieters; 01.04.2014
comment
что означает первое, где говорится, что вы можете сначала делать другие вещи с сокетом? Мне кажется, что пользовательский диспетчер контекста (т.е. socketcontext) позволяет делать то же самое, что и contextlib.closing. В чем разница?? - person allyourcode; 31.05.2016
comment
@allyourcode: contextlib.closing() будет вызывать только close в сокете. Пользовательский диспетчер контекста может, скажем, привязаться к открытому порту, сначала установить тайм-аут или другие параметры сокета, или отправить закрывающий пакет для конкретного протокола или что-то подобное при выходе из контекста. - person Martijn Pieters; 31.05.2016
comment
Это должно быть with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as s: - person Lunulata; 08.11.2017
comment
@Lunulata: действительно; Я также исправил другую ссылку. Спасибо! - person Martijn Pieters; 09.11.2017
comment
В документация по Python фактически заявлена ​​явная поддержка протокола диспетчера контекста. . Не уверен, что это было в 13-м, но по крайней мере сейчас. - person MikeVe; 07.10.2020
comment
@MikeVe: это было не в 2013 году. Обратите внимание, что в 2014 году мы обсуждали это в комментариях здесь, как отправитель патча забыл обновить документы. В конечном итоге документы были исправлены в 2016 году. - person Martijn Pieters; 07.10.2020

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

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

>>> 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
person Lennart Regebro    schedule 27.05.2013

Пожалуйста, взгляните на следующие фрагменты для сокетов 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 в именах методов.

person mieciu    schedule 16.07.2015