受信したメールの受信日付、送信元Adress、件名が以下のような形式でリストに格納されているとします。
received_mails = [
("2018-01-01", "alice@example.com", "subject1"),
("2018-02-02", "bob@example.com" , "subject2"),
("2018-03-03", "chris@example.com", "subject3"),
("2018-04-04", "alice@example.com", "subject4"),
("2018-05-05", "bob@example.com" , "subject5"),
]
受信したメールを送信元Adressによって振り分けたい場合、言い換えればSQLでいうところのGROUP-BYのようなことをしたい場合、pythonではitertools.groupby
が利用できます。
def display_mails(mails):
return ':'.join(f'[DATE={mail[0]} ADDRESS={mail[1]} SUBJECT={mail[2]}]' for mail in mails)
for address, mails in itertools.groupby(received_mails, lambda mail : mail[1]):
print(address, display_mails(mails))
この結果は次の通りです。要するに送信元Addressによる振り分けができておらず、想定とは違う結果になっています。
alice@example.com [DATE=2018-01-01 ADDRESS=alice@example.com SUBJECT=subject1]
bob@example.com [DATE=2018-02-02 ADDRESS=bob@example.com SUBJECT=subject2]
chris@example.com [DATE=2018-03-03 ADDRESS=chris@example.com SUBJECT=subject3]
alice@example.com [DATE=2018-04-04 ADDRESS=alice@example.com SUBJECT=subject4]
bob@example.com [DATE=2018-05-05 ADDRESS=bob@example.com SUBJECT=subject5]
そこでitertools.groupby
のドキュメントを読んでみたところ、groupby
の対象になるイテレータはソート済みである必要があるとのことでした。
同じキーをもつような要素からなる iterable 中のグループに対して、キーとグループを返すようなイテレータを作成します。key は各要素に対するキー値を計算する関数です。キーを指定しない場合や None にした場合、key 関数のデフォルトは恒等関数になり要素をそのまま返します。通常、iterable は同じキー関数でソート済みである必要があります。
groupby() の操作は Unix の uniq フィルターと似ています。 key 関数の値が変わるたびに休止または新しいグループを生成します (このために通常同じ key 関数でソートしておく必要があるのです)。この動作は SQL の入力順に関係なく共通の要素を集約する GROUP BY とは違います。
今回の例であれば、次のように書き直す必要があるということです。
get_adress = lambda mail : mail[1]
for address, mails in itertools.groupby(sorted(received_mails, key=get_adress), get_adress):
print(address, display_mails(mails))
この実行結果は以下のようになり、確かに送信元Adressでgroup by処理ができていることがわかります。
alice@example.com [DATE=2018-01-01 ADDRESS=alice@example.com SUBJECT=subject1]:[DATE=2018-04-04 ADDRESS=alice@example.com SUBJECT=subject4]
bob@example.com [DATE=2018-02-02 ADDRESS=bob@example.com SUBJECT=subject2]:[DATE=2018-05-05 ADDRESS=bob@example.com SUBJECT=subject5]
chris@example.com [DATE=2018-03-03 ADDRESS=chris@example.com SUBJECT=subject3]
good.py
ではget_address
関数をlambda
を利用して定義しました。これはdef
を使って、以下のように定義することも可能です。
def get_address(mail):
return mail[1]
lambda
にしろdef
にしろ「リストやタプルのn番目の値を取得する」関数が欲しいわけで、こうした用途においてはoperator.itemgetter
を利用することも可能です。たとえばgood.py
をoperator.itemgetter
で書き換えると、次のようになります。
get_address = operator.itemgetter(1)
for address, mails in itertools.groupby(sorted(received_mails, key=get_adress), get_adress):
print(address, display_mails(mails))
あるいは他の例を考えてみると、たとえばreceived_mails
から件名だけを取り出して、リストに格納したい場合、以下のような3通りの書き方ができるわけです。
subjects = [mail[2] for mail in received_mails]
subjects = list(map(lambda mail : mail[2], received_mails))
subjects = list(map(operator.itemgetter(2), received_mails))