Рубрики
Без рубрики

Использование генераторов Python, чтобы избежать дополнительных вызовов служб

Как использовать генераторы Python для уменьшения количества вызовов других служб.

Автор оригинала: Gabriel Saldanha.

Я уже некоторое время использую генераторы Python, имея дело с большими Django Querysets , читая большие файлы Excel и имея возможность экономить память с помощью генераторов, стал частью моей повседневной работы. Однако в эти дни я столкнулся с другой проблемой, когда я хотел избежать дополнительных звонков в службы и решил ее с помощью генераторов. Вот о чем этот пост.

Предположим, вы хотите проверить, зарегистрирован ли данный user_email в любой из следующих социальных сетей: Facebook, Github или Twitter.

def has_facebook_account(user_email):
    print('calling Facebook service')
    return False

def has_github_account(user_email):
    print('calling Github service')
    return True

def has_twitter_account(user_email):
    print('calling Twitter service')
    return True

def has_social_media_account(user_email):
    print('Checking social media apps...')
    # Python's `any` receives an Iterable
    # and returns True when the first truthy clause is found.
    response = any([
        has_facebook_account(user_email),  # This is False
        has_github_account(user_email),  # This is True
        has_twitter_account(user_email),  # This is True
    ])
    print('Done!')
    return response

Что не так с описанным выше подходом? Ну, если вы запустите его локально, вы увидите следующий вывод:

>>> has_social_media_account('fake@email.com')
Checking social media apps...
calling Facebook service  # This is False, keep going...
calling Github service  # This is True! I can stop now...
calling Twitter service  # Oh no!
Done!

Проблема в том, что список оценивается сразу же после его создания (в отличие от ленивой оценки ). Таким образом, если это список вызовов методов, все вызовы будут выполнены.

На данный момент я думал, что проблема в том, что я на самом деле вызывал методы при инициализации списка. Так что, возможно, сохранение только ссылок на методы и использование понимания списка сделают свое дело:

def has_social_account(user_email):
    calls = [has_facebook_account, has_github_account, has_twitter_account]  # Refs
    return any([call(user_email) for call in calls])

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

Как я это решил? С помощью генератора.

def has_social_account(user_email):
    calls = [has_facebook_account, has_github_account, has_twitter_account]
    return any((call(user_email) for call in calls))  # Note the ( ) instead of [ ]

>>> has_social_account('fake@email.com')
calling Facebook service  # This is False, keep going...
calling Github service  # Aw yeah!

(вызов(user_email) для вызовов в вызовах) вычисляется как генератор (не полностью построенный список), который затем используется любым . любой(...) будет перебирать элементы генератора, оценивая по одному за раз . Таким образом, никакие дополнительные вызовы не выполняются один раз any вычисляет второй элемент ( has_github_account(user_email) -> True ), функция any вычисляется сама, возвращая True и не вызывая третий метод службы ( has_twitter_account ).

Вот суть, если вам интересно .

Пожалуйста, дайте мне знать в комментариях ниже, если что-то не ясно и/или если вы хотите получить более подробную статью о генераторах Python.