Pythonで、短絡評価して最初に現れたNone以外の値を返す関数の書き方について考えてみました。
まずは例として3つの式、yahoo()
、google()
、そしてask()
を順番に評価し、最初に現れたNone以外の値を返す関数get_answer()
を実行する get_answer.py を普通に書いてみました。動作はPython 2.7.3と Python 3.2.3で確認しています。
#!/usr/bin/env python
# vim:fileencoding=utf-8
from __future__ import print_function
yahoo = lambda *_: print("in yahoo()") or None
google = lambda *_: print("in google()") or 0
ask = lambda *_: print("in ask()") or "draw"
def get_answer(question):
answer = yahoo(question)
if answer is None:
answer = google(question)
if answer is None:
answer = ask("sagami", question)
return answer
if __name__ == '__main__': # pragma: nocover
print("Answer is {}.".format(get_answer("1 - 1")))
これを実行すると、
in yahoo()
in google()
Answer is 0.
のように表示されます。google(question)
を評価してNone以外の値である0が得られましたので、ask("sagami", question)
は評価されていません。
if文のネストを浅くしたいのであれば、行数は若干増えますが、
def get_answer(question):
answer = yahoo(question)
if answer is not None:
return answer
answer = google(question)
if answer is not None:
return answer
return ask("sagami", question)
のように書くこともできます。しかし、この関数を
def get_answer(question):
return (yahoo(question) or
google(question) or
ask("sagami", question))
や
def get_answer(question):
for answer in (yahoo(question),
google(question),
ask("sagami", question)):
if answer is not None:
return answer
return None
のように書くことはできません。前者を実行すると、
in yahoo()
in google()
in ask()
Answer is draw.
となって、None以外の値である0が無視されてしまいますし、後者を実行すると、
in yahoo()
in google()
in ask()
Answer is 0.
となって、評価する必要がないask("sagami", question)
まで評価されてしまいます。
そこで色々悩んだ結果、デコレータ@anyone
を使うことで、
@anyone
def get_answer(question):
yield yahoo(question)
yield google(question)
yield ask("sagami", question)
と書く方法を思いつきました。@anyone
の中身は、
def anyone(function):
def _(*args, **kwargs):
for value in function(*args, **kwargs):
if value is not None:
return value
return None
return _
です。get_answer()
はシンプルに書けるようになったと思うのですがいかがでしょうか?
他に何かもっと良い方法があれば、教えていただけると助かります。
以下は、@anyone
を使って書き直した get_answer.py です。
#!/usr/bin/env python
# vim:fileencoding=utf-8
from __future__ import print_function
yahoo = lambda *_: print("in yahoo()") or None
google = lambda *_: print("in google()") or 0
ask = lambda *_: print("in ask()") or "draw"
def anyone(function):
def _(*args, **kwargs):
for value in function(*args, **kwargs):
if value is not None:
return value
return None
return _
@anyone
def get_answer(question):
yield yahoo(question)
yield google(question)
yield ask("sagami", question)
if __name__ == '__main__': # pragma: nocover
print("Answer is {}.".format(get_answer("1 - 1")))