Pythonのmap関数の使い方です。
「map関数」の使い方は簡単ですが、説明に使われる用語が難しいです。
「map関数」まわりの用語解説を中心にまとめてみます。
なお、実行環境は、Python 3系です。
map関数は、Python 2系と仕様が変わっていますので、ご注意ください。
map関数: map(function, iterable, …)
map関数は、Pythonの組み込み関数です。
第一引数に「任意の関数」、第二引数以降に「イテレート可能なオブジェクト」を取ります。
組み込み関数: すぐに使える関数(ライブラリをインポートする「import 〇〇」が不要)
function: 任意の関数
iterable: イテレート可能なオブジェクト(≒配列)
戻り値は、配列の各要素に対して、任意の関数を実行した結果を要素とするmapオブジェクトです。
配列のように各要素がバラバラになったデータではなく、各要素を取り出す必要がある塊になったデータ(mapオブジェクト)として返ってきます。
これは、使用例のコードで確認します。
「イテレート可能なオブジェクト」とは
「イテレート可能なオブジェクト」には、以下のようなものがあります。
オブジェクト | 例 |
文字列(str) | ‘12345’ |
リスト(list) | [1, 2, 3, 4, 5] |
タプル(tuple) | (1, 2, 3, 4, 5) |
辞書(dict) | {‘A’: 1, ‘B’: 2} |
「イテレート」(iterate)とは、繰り返し処理することです。
「イテレート可能」(iterable)とは、オブジェクトから順に要素が取り出せることです。
具体例として、for文を使って、オブジェクトから順に要素を取り出してみます。
# 文字列
for i in '12345':
print(i)
1
2
3
4
5
文字列「’12345’」から、順に各要素が取り出せました。
# リスト
for i in [1, 2, 3, 4, 5]:
print(i)
1
2
3
4
5
リスト「 [1, 2, 3, 4, 5] 」から、順に各要素が取り出せました。
リストは、各要素の区切り「,」がはっきりしているので、わかりやすいです。
これで、オブジェクトから順に要素が取り出せること「イテレート可能」のイメージが掴めたと思います。
「イテレータ」とは
「イテレータ」とは、連続する一連のデータへのアクセスを提供するオブジェクトです。
map関数の戻り値「mapオブジェクト」は、イテレータです。
イテレート可能なオブジェクトを、イテレータに変換してみます。
# 文字列をイテレータに変換
itr = iter('12345')
itr
<str_iterator at 0x1e764e09860>
文字列「’12345’」が、イテレータに変換されました。
イテレータのままでは、塊になったデータなので、各要素を取り出せません。
next関数を使用して、各要素を取り出します。
next(itr)
'1'
1つ目の要素が取り出せました。
next関数を続けて使用すると、順に次の要素が取り出せます。
最後の要素「’5’」を取り出すと、イテレータ「itr」は空になります。
更に、next関数を使用すると、エラーが返ります。
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-217-6693cc261707> in <module>
----> 1 next(itr)
StopIteration:
イテレータの特徴は、連続する一連のデータを順に1回だけ取り出せるところです。
「イテレート可能なオブジェクト」の具体例で、for文を使用しました。
for文のinで使用した文字列やリストは、for文の実行時に「イテレータ」に変換されます。
ループ毎に、イテレータから順に要素が取り出され、イテレータが空になるとループが終了します。
次は、 list関数を使用して、各要素を取り出します。
list関数では、すべての要素が順に取り出されます。
# 文字列をイテレータに変換
itr = iter('12345')
# list関数で、各要素を取り出す
list(itr)
['1', '2', '3', '4', '5']
list関数で、イテレータ「itr」からすべての要素を取り出しました。
イテレータ「itr」は空になっているので、再度取り出すことはできません。
# list関数で、空のイテレータから各要素を取り出す
list(itr)
[]
空のリストが返ってきました。
イテレータからデータを取り出せるのは、1回だけだからです。
これで、「イテレータ(mapオブジェクト)」から順に要素を取り出すイメージが掴めたと思います。
イテレータは、次の要素だけを1回だけ取り出せるオブジェクトです。
リストのように要素を自由に取り出すことができませんが、メモリが節約できるメリットがあります。
map関数の使用例
map関数の使い方です。
map関数は、2つ以上の引数をとります。map(function, iterable, …)
第一引数が「任意の関数」、第二引数以降が「イテレート可能なオブジェクト」です。
第三引数があるときは、第一引数に、2つの引数を取る関数を指定します。
第二引数以降:「イテレート可能なオブジェクト」
map関数の第二引数以降は、 「イテレート可能なオブジェクト」 を指定します。
文字列(str)
第一引数に指定する関数は、組み込み関数「int」です。
組み込み関数「int」 は、文字列を整数に変換します。
string = '56789'
number = map(int, string)
number
<map at 0x1e764dfe978>
第二引数に指定した文字列「’56789’」から順に各要素を取り出し、整数に変換します。
戻り値はイテレータの「mapオブジェクト」です。
リスト関数を使用して、各要素を取り出します。
list(number)
[5, 6, 7, 8, 9]
次は、第一引数に、組み込み関数「str」を指定します。
組み込み関数「str」 は、引数に指定したオブジェクトを文字列に変換します。
string = '56789'
string_split = map(str, string)
list(string_split)
['5', '6', '7', '8', '9']
リスト(list)
第一引数に指定する関数は、組み込み関数「len」です。
組み込み関数「len」 は、引数に指定した文字列の文字数が取得できます。
list_str = ['A', 'Aa', 'Aaa', 'Aaaa', 'Aaaaa']
length = map(len, list_str)
list(length)
[1, 2, 3, 4, 5]
第二引数に指定したリストから、順に各要素を取り出し、文字数を取得しました。
これで、「イテレート可能なオブジェクト」に「任意の関数」を実行するイメージが掴めたと思います。
第一引数:「任意の関数」
map関数の第一引数は、 「任意の関数」 を指定します。
関数の種類は、以下のようなものがあります。
・組み込み関数
・lambda式(無名関数)
・def文で定義した関数
lambda式やdef文で定義した関数を使用する具体例として、長方形の面積を求めてみます。
lambda式 (無名関数)
vertical = [3, 4, 5, 6, 7]
horizontal = [5, 4, 3, 2, 1]
area = map(lambda v, h: v * h, vertical, horizontal)
list(area)
[15, 16, 15, 12, 7]
第二引数と、第三引数に指定したリストから、順に各要素を取り出し、lambda式で面積を求めました。
lambda式の解説は、以下をご覧ください。
【Python入門】lambda(無名関数、ラムダ式)がわからない
def文で定義した関数
vertical = [3, 4, 5, 6, 7]
horizontal = [5, 4, 3, 2, 1]
# 面積を求める関数を定義
def square(h, w):
return h * w
area = map(square, vertical, horizontal)
list(area)
[15, 16, 15, 12, 7]
第二引数と、第三引数に指定したリストから、順に各要素を取り出し、def文で定義した関数を使用して面積を求めました 。
def文で定義した関数を使用すると、後日にコードを確認する際の可読性が低下します。
(独自に定義したsquare関数を確認しないと、コードが理解できない。)
map関数には、lambda式を使用したほうが良いでしょう。
もちろん、if文を組み合わせるような複雑な関数を定義するときは、無理やりlambda式を使用するより、def文で定義することをおすすめします。
map関数の書き換え例
長方形の面積を求める例を書き換えてみます。
for文
vertical = [3, 4, 5, 6, 7]
horizontal = [5, 4, 3, 2, 1]
# 空のリストを用意
area = []
# リストに追加するメソッド
area_add = area.append
for v, h in zip(vertical, horizontal):
area_add(v * h)
print(area)
[15, 16, 15, 12, 7]
長方形の面積は、イテレータではなく、リストとして取得しています。
空のリストを用意するのと、各要素の計算結果をリストに追加する処理が必要になります。
当然のように、処理は遅くなります。
余談ですが、for文が遅くなる理由に、append属性の参照があります。
以下のように、ループの中でappend属性を参照しない方が良いです。
# ループの中でappend
for v, h in zip(vertical, horizontal):
area.append(v * h)
リスト内包表記
vertical = [3, 4, 5, 6, 7]
horizontal = [5, 4, 3, 2, 1]
area = [v * h for v, h in zip(vertical, horizontal)]
print(area)
[15, 16, 15, 12, 7]
長方形の面積は、イテレータではなく、リストとして取得しています。
リスト内包表記の基本構文は、以下のようになります。
リスト内包表記: [function(引数) for 引数 in iterable]
リスト内包表記は、一般的にはmap関数やfor文より処理が速いです。
ジェネレータ式
戻り値として、イテレータの方が良いとき(データが大きく、メモリを節約したいときなど)は、ジェネレータ式を使用します。
リスト内包表記の角括弧「[]」を、丸括弧「()」にしたものです。
vertical = [3, 4, 5, 6, 7]
horizontal = [5, 4, 3, 2, 1]
area = (v * h for v, h in zip(vertical, horizontal))
area
<generator object <genexpr> at 0x000001E764DEBA20>
長方形の面積は、リストではなく、ジェネレータイテレータとして取得しています。
mapオブジェクトと同じように、リスト関数を適用して要素を取り出します。
(全要素をメモリにのせたら、イテレータにした意味がありませんが、あしからず。)
list(area)
[15, 16, 15, 12, 7]
まとめ
- map関数まわりの用語(イテレートなど)が理解できた
- map関数で、イテレート可能なオブジェクトに、同じ処理をかけることができた
- map関数の戻り値が、イテレータ(mapオブジェクト)であることが理解できた
- map関数と同じ処理を、リスト内包表記やジェネレータ式で記述できた
早くメモリの消費が意識できるレベルになりたいです。