Использование генераторов Python

Я уже какое-то время использую генераторы 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!

Проблема в том, что a list оценивается сразу после создания. Таким образом, если это список вызовов методов, все вызовы будут выполнены.

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

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

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

Пример решения, используя генератор.

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!

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

Ответить