Автор оригинала: 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.