LINUX.ORG.RU

Странная ошибка в асинхронном коде

 , ,


1

1
class Request(object):
    def __init__(self, url: str, host: str, port: int, session: ClientSession, data: bytes,
                 ids_to_remove: list, callback):
        super().__init__()

        self.__url = url
        self.__host = host
        self.__port = port
        self.__session = session
        self.__data = data
        self.__ids_to_remove = ids_to_remove
        self.__callback = callback

    async def send(self):
        try:
            async with self.__session.post('http://{}:{}{}'.format(self.__host, self.__port, self.__url),
                                           data=self.__data) as r:

                if r.status == int(HTTPStatus.OK):
                    await self.__callback(self.__ids_to_remove)
        except Exception as e:
            print(e)

    def ids_to_remove(self):
        return self.__ids_to_remove


class ConnectionsPool(object):
    def __init__(self, host: str, port: int, max_connections: int, remove_ids_callback):
        super().__init__()

        self.__max_connections = max_connections

        self.__host = host
        self.__port = port
        self.__remove_ids_callback = remove_ids_callback

        self.__sending_futures = []
        self.__session = ClientSession(connector=TCPConnector(verify_ssl=False, limit=max_connections))

    async def send(self, url: str, data: bytes, ids_to_remove: List):
        if len(self.__sending_futures) > self.__max_connections:
            await asyncio.wait(self.__sending_futures, return_when=asyncio.FIRST_COMPLETED)

        request = Request(url, self.__host, self.__port, self.__session, data, ids_to_remove,
                          self.__remove_ids_callback)
        future = asyncio.ensure_future(request.send())
        future.request = request
        future.add_done_callback(self.future_done)
        self.__sending_futures.append(future)

    def future_done(self, future):
        self.__sending_futures.remove(future)

    def ids_to_remove(self):
        result = []
        for f in self.__sending_futures:
            result += f.request.ids_to_remove()
        return result
root: 'Task' object has no attribute 'ids_to_remove' (abstract_retranslator.py:31)
Traceback (most recent call last):
  File "/srv/backend/common/retranslation/abstract_retranslator.py", line 29, in retranslate
    await self._retranslate()
  File "/srv/backend/common/retranslation/http_retranslator.py", line 135, in _retranslate
    async for event in self.events(connections_pool.ids_to_remove()):
  File "/srv/backend/common/retranslation/http_retranslator.py", line 70, in ids_to_remove
    result += f.request.ids_to_remove()
AttributeError: 'Task' object has no attribute 'ids_to_remove'

Как f.request может стать Task? Или я неправильно готовлю асинхронный код на Пайтоне?

python 3.5

future = asyncio.ensure_future(request.send())
self.__sending_futures.append(future)

https://docs.python.org/3/library/asyncio-task.html#asyncio.ensure_future

Schedule the execution of a coroutine object: wrap it in a future. Return a Task object.

Вообще я понимаю о чем ты, но все же ощущение что дело в этом

Dred ★★★★★ ()
Последнее исправление: Dred (всего исправлений: 1)
Ответ на: комментарий от panter_dsd

я же написал, понял о чем ты. Вообще, что-то подсказывает, что это не весь код.

Dred ★★★★★ ()

Ты даже не знаешь как пишется слово «асинхронный», куда тебе в код лезть?

anonymous ()

Тут всё настолько запутанно что без поллитра не разберёшься. Не надо так писать. Не делегируй в Request вообще никаких collback и ids_to_remove. При этом ids_to_remove передан как аргумент, но есть такой метод. Что заставило так писать?

true_admin ★★★★★ ()

Ну и да, как выше писали - код говно. Что ты сделать то пытался? Ничего не понятно.

ei-grad ★★★★★ ()
Ответ на: комментарий от true_admin

У меня данные нужно отправить и, если они отправленны корректно, нужно их удалить. При этом нужно знать какие данные сейчас отправляются. Чем решение некорретно?

panter_dsd ★★★★ ()
Ответ на: комментарий от ei-grad

Ок. Сделаю self.__sending_futures словарем, где ключом будет таска, а значением реквест. Помониторю, поможет ли.

panter_dsd ★★★★ ()
Ответ на: комментарий от true_admin

При этом ids_to_remove передан как аргумент, но есть такой метод

Эм, а что в этом такого? Тут никакой коллизии имен нету.

panter_dsd ★★★★ ()
Ответ на: комментарий от panter_dsd

Чем решение некорретно?

Тем что никто кроме тебя не понимает как это работает. Ты пишешь код не для себя, а для тех людей которые его будут потом поддерживать (в т.ч. прямо сейчас на форуме мы это делаем). Код гораздо больше читают чем пишут.

В данном случае можно тупо сделать на одной очереди (массиве). Если данные не смогли отправиться то тупо возвращаешь запрос в хвост очереди или же в отдельную очередь недоставленных запросов. Кода меньше, читать проще, производительность выше, тестировать легче.

Эм, а что в этом такого? Тут никакой коллизии имен нету.

Полная неразбериха что откуда берётся для человека который видит это в первый раз. Дело не в формальном соотв. требованиям («коллизий нет»). Не гонись за формальными показателями, гораздо важнее на сколько удобно с кодом работать людям. Поэтому лучше писать сразу правильно.

true_admin ★★★★★ ()
Ответ на: комментарий от true_admin

Тем что никто кроме тебя не понимает как это работает. Ты пишешь код не для себя, а для тех людей которые его будут потом поддерживать (в т.ч. прямо сейчас на форуме мы это делаем). Код гораздо больше читают чем пишут.

Что конкретно непонятно в этом коде? Названия методов/параметров описывают свое назначение. Алгоритм понять тоже просто - сейчас непредвзято перечитал. Да, хранить в таске реквест было некрасивым решением, это я исправлю.

Полная неразбериха что откуда берётся для человека который видит это в первый раз. Дело не в формальном соотв. требованиям («коллизий нет»). Не гонись за формальными показателями, гораздо важнее на сколько удобно с кодом работать людям. Поэтому лучше писать сразу правильно.

Если бы мы говорили про c++, то да. Но в Пайтоне есть self при обращении к мемберу/методу класса, поэтому не понять что откуда достаточно сложно.

В данном случае можно тупо сделать на одной очереди (массиве). Если данные не смогли отправиться то тупо возвращаешь запрос в хвост очереди или же в отдельную очередь недоставленных запросов. Кода меньше, читать проще, производительность выше, тестировать легче.

Если бы все было так просто, сделал бы проще, но сам алгоритм работы сложнее.

panter_dsd ★★★★ ()
Последнее исправление: panter_dsd (всего исправлений: 3)
Ответ на: комментарий от panter_dsd

Что конкретно непонятно в этом коде?

1) запутанная логика. Что оно делает мне не понятно. И тебе тоже не понятно раз ты создал тему. Чистый код решает бОльшую часть вопросов сам по себе.

2) много сущностей со схожими именами (*ids_to_remove*).

3) слишком много кода для такой простой задачи. По крайней мере в данном примере Request.send() можно заменить обычной функцией. Будет проще, нагляднее, и не будет рябить в глазах от подчёркиваний в self.__yet_another_private field.

4) Я сколько не программирую вообще не было необходимости в add_done_callback и прочее. Сделай нормальный пайплайн на очередях, не плоди коллбэки и кучу «скрытой» логики.

сам алгоритм работы сложнее.

Повод переписать всё заново. Если оно нечитаемо тут, то реальный код ещё хуже.

true_admin ★★★★★ ()
Ответ на: комментарий от panter_dsd

Oh, shi~, я понял, def ids_to_remove() это геттер который тут не используется, но используется в полном коде. Назови хотя бы get_ids_to_remove(). Ну или @property добавь.

true_admin ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.