Как выполнить список функций, а также передать данные соответствующим функциям, вызываемым с помощью asyncio - PullRequest
0 голосов
/ 27 сентября 2018

Раньше я использовал запросы, но с тех пор я перешел на 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@hotmail.com', 'email@outlook.com', 'email@gmail.com']

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

Ответы [ 2 ]

0 голосов
/ 28 сентября 2018

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

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).Эта сопрограмма теперь будет работать в фоновом режиме.Вы можете добавить обратный вызов в будущее, который будет вызван после его завершения, передав один аргумент: сам объект будущего. Посмотрите на будущее в документации asyncio , если вы хотите узнать о них больше (также ознакомьтесь с Сопрограммы и задачи ).

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

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()

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

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

    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))

но, вероятно, лучший способ сделать это - просто заставить асинхронную функцию 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 действительно классный, и я хотел показать его вам,Есть способы его использования, и я могу в значительной степени гарантировать, что вам понадобятся оба этих подхода, чтобы сделать что-то сложное.

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

0 голосов
/ 27 сентября 2018

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))) 

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

...