Pythonには豊富な標準ライブラリが用意されており、これらを使いこなすことで効率的なプログラミングが可能になります。
本記事では、特に初心者の方々がつまずきやすいイテレータの操作やコレクションの取り扱いについて、実践的な例を交えながら解説していきます。
sorted/reversedによるソート操作から、itertools、collectionsなどの便利なモジュールまで、Pythonの基本的かつ重要な機能を解説していきます。
ソート
イテラブルのソートや逆順に関する操作を解説します。
sorted/reversed
・soreted(list)とreversed(list)は非破壊的処理で別リストでソートした値を返します。
sortedの戻り値はリスト型になり、reversedは専用のイテレーターで返却されるためlist型に変換します。辞書やタプルも操作することができます。
# sorted----------------
# sorted(リスト数字)
seq = [3, 5, 2, 1, 4]
seq_rs = sorted(seq)
# >>> [1, 2, 3, 4, 5]
# sorted(リスト数字)
seq = [3, 5, 2, 1, 4]
seq_rs = sorted(seq, reverse=True)
# >>> [5, 4, 3, 2, 1]
# sorted(リスト文字)
seq = ["orange", "apple", "grape", "banana", "melon"]
seq_rs = sorted(seq)
# >>> ['apple', 'banana', 'grape', 'melon', 'orange']
# sorted(文字)⇒1文字ごと分割されます。
seq = "orange"
seq_rs = sorted(seq)
# >>> ['a', 'e', 'g', 'n', 'o', 'r']
# sorted(辞書)⇒キーが並び替えられる
seq = {"c": 3, "b": 2, "a": 1}
seq_rs = sorted(seq)
# >>> ['a', 'b', 'c']
# sorted(辞書)⇒キー情報で並び替えられる
seq = {"c": 1, "b": 2, "a": 3}
seq_rs = sorted(seq.items())
# >>> [('a', 1), ('b', 2), ('c', 3)]
# reversed-------------
# reversed(リスト数字)
seq = [3, 5, 2, 1, 4]
seq_rs = list(reversed(seq))
print(seq_rs)
# >>> [4, 1, 2, 5, 3]
sort/reverse
・list.sort()とlist.reverse()は破壊的処理で元のリストをソートします。リスト型のみに対応しています。
seq = [3, 5, 2, 1, 4]
seq.sort()
# >>> [1, 2, 3, 4, 5]
seq = [3, 5, 2, 1, 4]
seq.reverse()
# >>> [4, 1, 2, 5, 3]
Operatorモジュール
・引数にkeyを指定する際に、operatorモジュールを使用できます。itemgetter(‘key’)をkeyに指定することで、特定のタプルや辞書をキーにソートをかけられます。attrgetter(‘atr’)の場合は、クラスの属性でソートをかけられます。
# opratorモジュール----
from operator import itemgetter, attrgetter
# タプルの第二要素でソートする
seq = [(1, 5, 10), (10, 1, 5), (5, 10, 1)]
seq_rs = sorted(seq, key=itemgetter(2))
print(seq_rs)
# >>> [(5, 10, 1), (10, 1, 5), (1, 5, 10)]
# 辞書のage要素でソートする
seq = [
{"name": "tarou", "age": 5},
{"name": "hanako", "age": 10},
{"name": "jiro", "age": 8},
]
seq_rs = sorted(seq, key=itemgetter("age"))
print(seq_rs)
# >>> [{'name': 'tarou', 'age': 5}, {'name': 'jiro', 'age': 8}, {'name': 'hanako', 'age': 10}]
# オブジェクトの要素でソートする
from datetime import date
seq = [date(1995, 5, 10), date(2000, 10, 1), date(1990, 1, 30)]
seq_rs = sorted(seq, key=attrgetter("month", "day"))
print(seq_rs)
# >>> [datetime.date(1990, 1, 30), datetime.date(1995, 5, 10), datetime.date(2000, 10, 1)]
件数カウント – collections
Counter
・Counter(list)は、リストや文字列に含まれる要素ごとにカウントしてくれ、要素がない場合は0を返却します。
・.elements()で要素数分を出力し、.most_common(num)で要素の多いものを出力します。
・演算子でカウンタの要素を足し引きすることも可能です。
# Counter------
from collections import Counter
# 文字列のカウント
seq = "fjoaejfioaweofjawioifdgjfidgiasdjfiaejojwfooa"
c = Counter(seq)
# >>> Counter({'f': 7, 'j': 7, 'o': 7, 'a': 6, 'i': 6, 'e': 3, 'w': 3, 'd': 3, 'g': 2, 's': 1})
c["f"]
# >>> 7
c["x"]
# >>> 0
# リストのカウント
seq=["orange","apple","banana","orange","apple","banana","apple","apple","banana"]
c = Counter(seq)
# >>> Counter({'apple': 4, 'banana': 3, 'orange': 2})
# elementsメソッド(要素の数分返却する)
list(c.elements())
# >>> ['orange', 'orange', 'apple', 'apple', 'apple', 'apple', 'banana', 'banana', 'banana']
# most_commonメソッド(要素の多いものを返す,引数は取得数)
c.most_common(2)
# >>> [('apple', 4), ('banana', 3)]
# 演算子でカウンタ同士の要素を足し引き出来る
c2 = Counter(["orange", "apple", "orange"])
c2 + c
# >>> Counter({'apple': 5, 'orange': 4, 'banana': 3})
defaultdict
・Defaultdict(list)は、要素がないものを指定された場合にエラーではなくデフォルト値を返却します。
要素をカテゴリごとに集計する場合などに使用されます。
# defaultdict---------------
from collections import defaultdict
# int:要素がない場合は0を返す
dd = defaultdict(int, orange=1, grape=2)
dd["banana"]
# >>> 0
# list:要素がない場合は[]を返す
dd = defaultdict(list, orange=1, grape=2)
dd["banana"]
# >>> []
# 要素のカテゴリごとの集計に利用される
data = [
("drink", "coffee"),
("food", "hamburger"),
("drink", "beer"),
("food", "hotdog"),
("drink", "coke"),
]
dd = defaultdict(list)
for k, v in data:
dd[k].append(v)
dd.items()
# >>> dict_items([('drink', ['coffee', 'beer', 'coke']), ('food', ['hamburger', 'hotdog'])])
定数の定義 – enum
Enum
・Class Classname(enum.Enum)では、定数を定義できます。利用シーンとしては、特定の定数型を引数で渡したいケースなどで使用します。
・@enum.uniqueのデコレータをつけると定数の値の重複は許しません。
・定数の値が同じ場合、重複する場合は、イテレーターで取得できません。
import enum
# Enumクラス
@enum.unique
class Fruits(enum.Enum):
ORANGE = 1
BANANA = 2
APPLE = enum.auto()
Fruits.ORANGE.name
# >>> ORANGE
Fruits.APPLE.value
# >>> 3
# 関数の引数にEnumクラスを指定する
def func(fruits: Fruits) -> None:
pass
イテレータの操作 – itertools
chain
・chain(list,str,dict)では、複数のイテレーターを一つに連結することができます。
import itertools
# chain
seq = itertools.chain(["a", "b", "c"], "ABC", range(3))
list(seq)
# >>> ['a', 'b', 'c', 'A', 'B', 'C', 0, 1, 2]
groupby
・groupby(str)では、グループ化して文字列:文字列リストで返却してくれます。連続していない場合は、同じグループとしてみなされないので、あらかじめソートしておく必要がある。第二引数に要素を比較して計算する関数を設定できます。
# groupby
seq = itertools.groupby(sorted("abbcccaaaabbc"))
for k, v in seq:
print(k, list(v))
# >>> a ['a', 'a', 'a', 'a', 'a']
# >>> b ['b', 'b', 'b', 'b']
# >>> c ['c', 'c', 'c', 'c']
islice
・islice(iterable,stop,start,step)では、イテレータの任意の範囲を指定することができます。スライス[:5]と機能はほぼ同じです。
# islice
num = list(range(10))
seq = itertools.islice(num, 3, 6)
list(seq)
# >>> [3, 4, 5]
zip/zip_longest
・zip(list,list,list)では、指定したいイテラブルの要素を1つずつ取得します。長さが異なる場合は、短い方の処理が終了した場合、全体の処理が終了します。長い方に合わせたい場合は、zip_longest()を使用します。値がないところはNoneとなりますが、fillvalue引数に値を指定することもできます。
# zip
num = [1, 2, 3, 4, 5]
fruits = ["orange", "banana", "apple"]
drinks = ("beer", "coffee", "juice", "coke")
for v in zip(num, fruits, drinks):
print(v)
# >>> (1, 'orange', 'beer')
# >>> (2, 'banana', 'coffee')
# >>> (3, 'apple', 'juice')
# zip_longest
num = [1, 2, 3, 4, 5]
fruits = ["orange", "banana", "apple"]
drinks = ("beer", "coffee", "juice", "coke")
for v in itertools.zip_longest(num, fruits, drinks, fillvalue="★"):
print(v)
# >>> (1, 'orange', 'beer')
# >>> (2, 'banana', 'coffee')
# >>> (3, 'apple', 'juice')
# >>> (4, '★', 'coke')
# >>> (5, '★', '★')
product/permutations/combinations/combinations_with_repalcement
・product(iter,iter)では複数のイテレーターオブジェクトを指定し、全ての要素の組み合わせを返します。
・permutations()では、複数のイテレーターオブジェクトを指定し、要素の組み合わせを返しますが、productとは異なり、同じ要素の組み合わせはない順列となります。
・combinations()では、permutationsと異なり、重複なしの組み合わせとなります。
・combinations_with_replacement()では、重複の値も含めた組み合わせとなります。
# product
seq = list(itertools.product("ABCD", repeat=2))
seq2 = [i[0] + i[1] for i in seq]
# >>> ['AA', 'AB', 'AC', 'AD', 'BA', 'BB', 'BC', 'BD', 'CA', 'CB', 'CC', 'CD', 'DA', 'DB', 'DC', 'DD']
# permutations
seq = list(itertools.permutations("ABCD", r=2))
seq2 = [i[0] + i[1] for i in seq]
# >>> ['AB', 'AC', 'AD', 'BA', 'BC', 'BD', 'CA', 'CB', 'CD', 'DA', 'DB', 'DC']
# combinations
seq = list(itertools.combinations("ABCD", r=2))
seq2 = [i[0] + i[1] for i in seq]
# >>> ['AB', 'AC', 'AD', 'BC', 'BD', 'CD']
# combination_with_replacement
seq = list(itertools.combinations_with_replacement("ABCD", r=2))
seq2 = [i[0] + i[1] for i in seq]
# >>> ['AA', 'AB', 'AC', 'AD', 'BB', 'BC', 'BD', 'CC', 'CD', 'DD']
オブジェクトのコピー – copy
copy/deepcopy
・通常、変数に変数を代入したり上書きする場合は、元の変数も上書きされてしまいます。参照渡しが、copy(variable)を使用することで、オブジェクトをコピーし、元の変数とは別のものを作成します。
・deepcopy(variable)では、子オブジェクトまで再帰的にコピーされます。
import copy
# copy
seq = [[1, 2], [3, 4], [5, 6]]
seq_cp = copy.copy(seq)
seq_cp[0] = [9, 8]
# >>> [[1, 2], [3, 4], [5, 6]]
# deepcopy
seq = [[1, 2], [3, 4], [5, 6]]
seq_cp = copy.deepcopy(seq)
seq_cp[0][0] = 9
# >>> [[1, 2], [3, 4], [5, 6]]