Как выполнить список функций, а также передать данные соответствующим функциям, вызываемым с помощью asyncio

1

Я ранее пользовался запросами, но с тех пор я перешел на aiohttp + asyncio, чтобы запускать учетные записи параллельно, однако мне не удается объединить логику в моей голове.

class Faked(object):
    def __init__(self):
        self.database = sqlite3.connect('credentials.db')

    async def query_login(self, email):
        print(email)
        cur = self.database.cursor()
        sql_q = """SELECT * from user WHERE email='{0}'""".format(email)
        users = cur.execute(sql_q)
        row = users.fetchone()
        if row is None:
            raise errors.ToineyError('No user was found with email: ' + email + ' in database!')

        self.logger().debug("Logging into account '{0}'!".format(row[0]))
        call_func = await self._api.login(data={'email': row[0],
                                                'password': row[1],
                                                'deviceId': row[2],
                                                'aaid': row[3]})
        return await call_func

    async def send_friend_request(self, uid):
        return await self._api.send_friend_request(uid)


def main(funcs, data=None):
    """
   todo: fill
  :rtype: object
  """
    tasks = []
    if isinstance(funcs, list):
        for func in funcs:
            tasks.append(func)
    else:
        tasks.append(funcs)
    print(tasks)
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(asyncio.gather(*tasks))
    for result in results:
        print(result)
    return results


if __name__ == '__main__':  # for testing purposes mostly
    emails = ['[email protected]', '[email protected]', '[email protected]']

Я просто хочу знать, как ставить в очередь несколько функций, в этом случае query_login и send_friend_request, а также передавать правильные данные указанным функциям, предполагая, что я буду запускать три аккаунта, скажем, приложение для социальных сетей одновременно, это действительно пугает мой разум, хотя у меня есть склонность к чрезмерным последствиям, любая помощь будет очень признательна.

Теги:
asynchronous
python-asyncio

2 ответа

3
Лучший ответ

Python предназначен для упрощения работы с оператором распаковки * или с помощью лямбда. В этой теме есть несколько хороших ответов, которые имеют то, что вам нужно:

Передача функций с аргументами другой функции в Python?

Позвольте пройти через него.

callstack = [] # initialize a list to serve as our stack.
     # See also collections.deque for a queue.

Тогда мы можем определить нашу функцию:

def somefunc(a, b, c): 
    do stuff...

Затем добавьте вызов в стек с аргументами в виде списка.

args = [a, b, c]
callstack.append((somefunc, args)) # append a tuple with the function
            # and its arguments list.

# calls the next item in the callstack
def call_next(callstack):
    func, args = callstack.pop() # unpack our tuple
    func(*args) # calls the func with the args unpacked

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

def call_next(callstack):
    func, args, kwargs = callstack.pop() # unpack our tuple
    func(*args, **kwargs) # calls the func with both args and kwargs unpacked.

Альтернативный способ - просто создать лямбду.

def add(a, b):
    return a + b

callstack = []

callstack.append(lambda: add(1, 2))
callstack.pop()() # pops the lambda function, then calls the lambda function, 
                  # which just calls the function as you specified it.

Вуаля! Все они принадлежат авторам в другой теме. Здесь есть информация: если вы передаете объект в качестве аргумента, он будет передан как ссылка. Будьте осторожны, потому что вы можете изменить объект, прежде чем он будет вызван в вашем стеке.

def add(a, b, c):
    return a + b + c

badlist = [1,2,3]
callstack.append((somefunc, badlist))
badlist = [2, 4, 6]
callstack.append((somefunc, badlist))

while len(callstack) > 0:
    print(call_next(callstack))

# Prints:
12
12

Вы можете обойти это в версии * args с:

# make a shallow copy and pass that to the stack instead.
callstack.append((somefunc, list(badlist))) 

В лямбда-функциях вся вещь оценивается во время вызова, поэтому даже те вещи, которые обычно не относятся к ссылке, ведут себя как ссылки. Вышеприведенный трюк не будет работать, поэтому сделайте любое копирование по мере необходимости, прежде чем создавать свою лямбду.

  • 1
    Вау, спасибо за чрезвычайно информативный ответ! Я ценю прохождение, которое вы сделали больше, чем вы знаете, еще раз спасибо, чувак! Работает как шарм.
0

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

loop = asyncio.get_event_loop()
api = MyAPIToSomeCoolChatProgram()

def my_done_callback(fut):
    exc = fut.exception()
    if exc:
        print(fut.my_custom_attribute, 'raised an exception!')
        import traceback
        traceback.print_exc(exc) # dumps a "Traceback (most recent call last):" message to stderr
    print(fut.my_custom_attribute, 'completed, returned', repr(fut.result()))

fut1 = asyncio.ensure_future(api.send_friend_request(my_best_friend))
fut1.my_custom_attribute = 'fut1 (add a friend)'
fut1.add_done_callback(my_done_callback)

fut2 = asyncio.ensure_future(api.post_text_message('Hello everybody!'))
fut2.my_custom_attribute = 'fut2 (send a message)'
fut2.add_done_callback(my_done_callback)

print('Done creating the futures')
loop.run_forever()

Выход:

  Done creating the futures
  fut1 (add a friend request) completed, returned '200 OK'
  fut2 (send a message) completed, returned '200 OK'

Обратите внимание, что они могут отображаться в любом порядке. Вы можете вызывать сопрограммы из неасинхронного кода путем переноса сопрограммы (возвращаемого значения из функции сопрограммы) в будущем (или, точнее, в Task которая является подклассом Future). Эта сопрограмма теперь будет работать в фоновом режиме. Вы можете добавить обратный вызов в будущее, которое будет вызываться, когда оно заканчивается, передает один аргумент: сам будущий объект. Посмотрите фьючерсы в документации по асинхронному обложению, если вы хотите узнать о них больше (также проверьте Coroutines и Tasks).

В любом случае, эти обратные вызовы могут делать все, что вы хотите, включая запуск других задач.

def when_done_logging_in(self, fut):
    self.login_info = fut.result() # note: calling fut.result() if the login coroutine raised an exception will reraise the exception here.
    next_fut = asyncio.ensure_future(self.send_friend_request(fut.friend_request_to_send))
    # do something with next_fut here (or don't if you don't care about the result)

def login_and_send_friend_request(self, email, friend):
    fut = asyncio.ensure_future(self.query_login(email))
    fut.friend_request_to_send = friend
    fut.add_done_callback(self.when_done_logging_in)

Конечно, вы также можете сделать это с помощью:

 async def login_and_send_friend_request(self, email, friend):
    self.login_info = await self.query_login(email)
    await self.send_friend_request(friend)

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

def __init__(self, whatever_args_you_might_have_here, email):
    ...
    self.login_info = None
    self.email = email

async def send_friend_request(self, uid):
    if self.login_info is None:
        await self.query_login(self.email) # if you end up doing this you should probably make this not take a parameter and just use self.email instead
    do_send_friend_request_stuff()

Конечно, вы можете не знать адрес электронной почты до тех пор, пока объект не будет создан, и в этом случае вы можете либо инициализировать его до "Нет" до тех пор, пока не будет вызвана какая-либо функция входа в систему, либо используйте один из первых двух способов.

Если вы хотите последовательно выполнять список функций, вы можете сделать следующее:

    def execute_coros_in_sequence(list_of_coros):
        fut=asyncio.ensure_future(list_of_coros[0])
        if len(list_of_coros) > 1:
            # there is probably a better way to do this
            fut.remaining_coros=list_of_coros[1:]
            fut.add_done_callback(lambda fut: execute_coros_in_sequence(fut.remaining_coros))

но, вероятно, лучшим способом сделать это было бы просто заставить функцию aync def вызывать их всех, потому что таким образом вы получаете обработку исключений и т.д. без большого избыточного усложнения. Лучший способ сделать это, если вы хотите, чтобы это как будущее (которое вы также можете сохранить как атрибут объекта и запрос, чтобы увидеть, если оно еще сделано), будет следующим:

class API:
     async def login(self):
         pass
     async def logout(self):
         pass
     async def do_fun_stuff(self):
         pass


async def test_api(api):
    api.login()
    api.do_fun_stuff()
    api.logout()

fut=asyncio.create_task(test_part_of_api(API()))

(Кстати, asyncio.ensure_future() сначала проверяет, является ли его аргумент уже будущим, а если нет, вызывает asyncio.create_task().)

но будущее api действительно круто, и я хотел показать его вам. Для этого есть использование, и я могу в значительной степени гарантировать, что вам понадобятся оба этих подхода, чтобы сделать что-то сложное.

Извините за стену текста неорганизованного ответа. Я здесь немного новый. Я просто думаю, что асинчио действительно круто.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню