はじめに
iterator, generator
とかlist, range, tuple
などのシーケンスやcollections
, collections.abc
などの概要とtipsをまとめてみました1.
環境
python3.5 (python3.6のときは付記)
すすめかた
iterable, iterator
-> generator
-> container
-> seuquence, mapping
-> 個々のコンテナ(dict, tuple, list, set
) -> collections
モジュール(主にdefaultdict, OrderedDict, Counter, namedtuple
)
の順で。都度collections.abc
モジュールの話も挟みながら進めていきたい。
iterable, iterator
iterable
なオブジェクトとは、__iter__
特殊メソッドが定義されている(もしくは、__getitem__
特殊メソッドが定義されている)オブジェクト2であり、iterator
とは__iter__
と__next__
が定義されているオブジェクトです。__iter__
はiterator
を返す特殊メソッド、__next__
は要素を反復して取り出すことのできる特殊メソッドです。
なぜ、配列構造とは別に、iterator
なんてものがあるんだ?って疑問については、Iterator Pattern
まで戻って考える必要があります。
ここでは、ざっくり簡単にいうと、IterableとIteratorを分離して、データ構造自体と、データの走査方法の役割を分担することで、それぞれの責任を分離でき、疎結合が実現できるからです。
ここで、Pythonでiterable
とiterator
をゼロから自作してみます。iterableなオブジェクトが__iter__
でiteratorを作成し、__next__
でiteratorを走査するという感じです:
(__getitem__
を用いてiterable
を実装する場合の例は、コメント欄の https://qiita.com/knknkn1162/items/17f7f370a2cc27f812ee#comment-27aee9b7b4ae2fe3eed4 にて例が記載されています。ありがとうございます)
# iterableであるために
# __iter__(iteration/iterator protocol : 反復プロトコルというらしい)を実装する
class Foo():
def __init__(self):
self._L = [0,1,2]
def __iter__(self):
return FooIterator(self)
# iteratorであるために__iter__, __next__を実装する必要がある
class FooIterator():
def __init__(self, foo): # fooはiterableなオブジェクト
self._i = 0
self._foo = foo
def __iter__(self):
return self
def __next__(self):
try:
v = self._foo._L[self._i]
self._i += 1
return v
# StopIteration(RuntimeError)を送出するように実装
except IndexError:
raise StopIteration
iterator
を直接操作するときは、iter, next
関数を用います:
foo = Foo() #iterableなオブジェクトを生成
# iteratorを直接操作する
## iter関数は__iter__関数を呼び出し、イテレータを取得する。
iter_foo = iter(foo) # iterableオブジェクトを引数に
print(next(iter_foo)) # 0
print(next(iter_foo)) # 1
print(next(iter_foo)) # 2
print(next(iter_foo)) # StopIteration:が送出され、エラー
一方、iterableオブジェクトから、for文を回す時は、内部で、iteratorが間接的に操作されます。for文はfor elem in iterable_obj
みたいな構造をしていますが、iterable_objが内部で__iter__
を呼び出し、iteratorが生成され、iteratorの__next__
を繰り返すような挙動になっています:
# for文を用いてiteratorを間接的に操作する:
## 1. iterableなオブジェクト(foo)の__iter__関数を呼び出してiterator取得
## 2. iterator の next() メソッドを呼ぶ => StopIterationが呼ばれたら、ループが終了する(当然この後実行時エラーにはならない)
for i in foo:
print(i)
## 3. 処理が終わったら、2を行う
Note) collections.abc
モジュールを用いて(https://docs.python.jp/3/library/collections.abc.html#collections-abstract-base-classes の表を参照)、先ほどのiterator, iterableを書き換えます。(abcとは、abstract base classであり、これを継承しておくことで、必要なメソッドを忘れることなく実装することができます。(型チェッカとしての機能)
iterator, iterableくらいの実装であれば、わざわざ継承する必要もないのですが,Sequence
とかの節でも似たような話をしますし、簡単ですので、ここで触れておきます。
import collections.abc
# iterableであるために
# __iter__(iteration/iterator protocol : 反復プロトコルというらしい)を実装する
class Foo(collections.abc.Iterable):
def __init__(self):
self._L = [0,1,2]
def __iter__(self):
return FooIterator(self)
# __next__を実装する必要がある
## (collections.abc.Iteratorを継承しているので、__iter__は自然と定義される)
class FooIterator(collections.abc.Iterator):
def __init__(self, foo): # fooはiterableなオブジェクト
self._i = 0
self._foo = foo
def __next__(self):
try:
v = self._foo._L[self._i]
self._i += 1
return v
# StopIteration(RuntimeError)を送出する
except IndexError:
raise StopIteration
# 使い方は上記と全く同じ。
generator
前節で自作のiterable
オブジェクトを考えました。しかしながら、普通は上記のようにiterator
クラスを一から実装することはほぼありません。
代わりにgenarator
を用いれば、簡単にiterator
オブジェクトを作成できます。generator
には二つの種類があり、generator expression
とgenerator function
があります。3
generator expression
は
(x * x for x in numbers)
みたいな感じで書きます(内包表記みたいに)
generator function
は
def gen_foo():
yield 1
yield 2
yield 3
yield 4
yield 5
list(gen_foo()) # [1, 2, 3, 4, 5]
for i in gen_foo():
print(i,end="/")
# 1/2/3/4/5/
みたいな感じで、使います。
generatorの使い処
generator
は、よくファイルの読み取り関連で使ったりします.
(全ての内容を一気に読み取ってから、結果を返すと、サイズが大きすぎでクラッシュする可能性があるため)4
# generator functionの例
def lazy_split(file):
"""MeCabでのファイル読み取って、品詞とセットで分かち書きする関数を想定 (file sizeは巨大な場合がある)"""
fi = codecs.open(file, 'r', 'utf-8', 'ignore')
feature_flag = feature
for line in fi:
line = line.strip()
# 最後はEOSなので、読み飛ばす
chunks = self.chasen.parse(line).splitlines()[:-1]
_surface, _yomi, origin, feature = chunk.split('\t')[:4]
yield (origin, feature)
Note) 常に、generatorを使えば良いか、というとそうでもなくて、さほど大きくないサイズのオブジェクトで返り値がgenerator
だと、関数を使用する側に不便を強いる(list(gen_func())[:10])
みたいな感じでまどろっこしい)。generatorはランダムアクセスできないので、結局list
関数を作用させる必要があります。
また、generator function
を用いる時は、関数名にlazy_
などをつけ、遅延処理であることが容易に想像できるようにする方が親切でしょう。
あとは、使うタイミングは限られますが、無限列を作りたいときとかにもジェネレータを用います:
def fib():
a,b = 0,1
yield 0
yield 1
while True:
yield a+b
a, b = b, a+b # tupleならではの使い方
無限列の最初の10個とか取って来たければ、以下のようにすると良いと思います:
#fib()[:10] # 'generator' object is not subscriptable(番号によって要素指定できない)
list(fib())[:10] # fib()が最初に評価されて、無限ループに陥る
# 最初の10個を取って来たければ、以下のようにする:
import itertools
list(itertools.islice(fib(), 10)) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
list(itertools.takewhile(lambda val:val<30,fib())) # [0, 1, 1, 2, 3, 5, 8, 13, 21] # 30より大きい要素で打ち切る
container
container型のオブジェクトは__contains__
を実装するようなオブジェクトです。(例としては、list, dict, tuple, str, collections.defaultdict
など、いわゆる「コンテナ型」で幅広いです)
__contains__
は帰属テスト演算(in
演算子)をサポートする特殊メソッドです。なので、要素の入れ物としての機能を有するオブジェクトと考えれば良いでしょう。collections.abc.Container
クラスを継承することで、コンテナ型としての性質を持つクラスを作成することができます。次節で紹介するSequence
型はContainer
型であり、Container
型としての性質に加えて、インデクサのスライスが定義されていることが一つの大きな特徴です(sequence
の節を参照)。
Note) for
文については、iterator
の節で内部挙動を観察しました。一方、ややこしいのですが、in
演算子自体は__contains__
と一対一対応しているわけではありません。
__contains__
を有するクラスに対しては、以下のような内部挙動になります:
hasattr(list, "__contains__") # listはcontainer型
# isinstance(list(), collections.abc.Container) # でもよい list()はインスタンス化のため。
## 1. list.__init__関数がよばれる
## 2. __contains__ を定義しているのでそのまま、True or Falseを返す
flag = 4 in [1,2,3,4] # ==> True
__contains__
をもたないクラス(例えば、Fooクラスのような「純粋な」iterableなオブジェクトのクラス)でも__iter__
特殊メソッドを持っていさえすれば(つまりIterable
オブジェクトでありさえすれば)、in
演算子を適用できます:
# Fooは上記で定義したiterableなクラス
hasattr(Foo, "__contains__") # False
hasattr(Foo, "__iter__") # True
# iteratorを間接的に操作している:
## 1. Foo.__init__関数がよばれる
## 1.1 __contains__ は定義されていないが __iter__ は定義されている.
## 2. Foo.__iter__が呼ばれて、iterator(itとする)を取得
## 3. it.__next__が呼ばれて、値(valueとする)が返される。StopIterationのエラーが送出されれば、Falseを返す
## 4. value.__eq__が呼ばれて、真偽値を得る。Trueなら、Trueを返す。Falseならば、3に戻って繰り返す。
flag = 2 in Foo() # ==> True
本来は、Container
とIterable
は微妙に違うものです。(要求される特殊メソッドが__contains__
と__iter__
で違う)
しかしながら、Iterableなオブジェクトはin
演算子の挙動がContainer
と同じなため、Container
のように扱えます。また、Container
型だがIterable
でないようなクラスはかなり病的であり、普段ならほぼ考える必要性がないです。
というわけで、Container
とIterable
が一緒くたにされてしまうことも割と多いように感じます。
Container
型の注意点としては、単純な代入a = b
をすると、参照としてコピーされる所です:
dic_a = {"a":1, "b":2}
dic_b = dic_a
dic_b["b"] = 5
print(dic_a) # {'a': 1, 'b': 5} 参照先のコピーなので、dic_aが変わる(という多分想定していないことが起こる!)
- 希望通りの動作にしたければ、
copy.deepcopy
を用いればよいです:
import copy
dic_a = {"a":1, "b":2}
# dic_b.copy()は浅いコピー(再帰的にコピーしない).
dic_b = copy.deepcopy(dic_a) # 深いコピー(ネストしているものも再帰的にコピー)
dic_b["b"] = 5
print(dic_a) # {'a': 1, 'b': 2} # オブジェクトごとコピーしているので、dic_bで変わっても結果が伝播しない!
これぐらいだと、間違えないですよね? でも、ちょっと複雑なことをすると、なかなか根深いバグに繋がったりすることがあります:
# https://docs.python.jp/3/library/itertools.html#itertools.repeat のpython実装を見ると、参照値が戻り値になっている
import itertools
seasons = ['summer', 'winter']
# `{}`はコピーしてくれないけど、間違って使っちゃった
tbls = dict(zip(seasons, itertools.repeat({})))
# summerのvalue(つまり、dict)を更新している(つもり)
tbls["summer"].update({"beach" : 30})
# 意図しない動作(すべてのキーについてvalueが更新されている(ぎゃー)
print(tbls) #{'winter': {'beach': 30}, 'summer': {'beach': 30}}
とか、
lsts = [[]] * 3 # 要素はlist型なので、参照値のコピー
print(lists) #[[], [], []]
lsts[0].append(3)
print(lsts) # [[3], [3], [3]](ぎゃー)
とか。
Note) それぞれの解決法について。
前者はdefaultdict
を用いればよい:
# defaultdictを用いて、初期化にdict()と指定する
tbls = collections.defaultdict(dict)
for elem in seasons:
if elem == "summer":
tbls[elem].update({"beach" : 30})
print(tbls[elem])
後者については、内包表記を使おう!
lsts = [[] for _ in range(3)]
lsts[0].append(3)
print(lsts) # [[3], [], []]
Note) tupleでもvalueそのものはコピーされない(ただし、tuple型のオブジェクトは再代入できず、immutableなのでトリビア的だけど)
tup_a = (1,2,3)
tup_b = tup_a
print(id(tup_a), id(tup_b)) # 4576958936 4576958936 idが同じ。
sequence
sequence
型は、collections.abc.Sequence
クラスで定義されているiterableオブジェクトの型であり(Container型も継承している)、https://docs.python.jp/3/library/stdtypes.html#common-sequence-operations にあるように、共通のシーケンス演算が定義されています。例えば、list, tuple, str, range
がそれに当たります。
# rangeがsequence型なのは、意外だった(「純粋な」iterable objectかと思った)
range(10)
range(10)[2] # 2
range(10)[2:4] # range(2, 4)
ちなみにインデクサのs[i:j:k]
のような記号は、sliceオブジェクトr = slice(i,j,k)
のシンタックスシュガーです:
s = "abcdefg"
# sliceクラスを(明示的に)使用する
s[slice(2,4)] # 'cd'でs[2:4]のシンタックスシュガー
collections.abc.Sequence
を見ると、特殊メソッド__getitem__
(あとは、__len__
)が定義されている必要があります。この場合、__getitem__
を定義しさえすれば、__iter__
や__contains__
は自然と定義されます。(https://qiita.com/knknkn1162/items/17f7f370a2cc27f812ee#comment-e223f3d5ee9bf0381887 の通り、これは使用ではありません。)
dict
も __getitem__
と __len__
もサポートしているので、一見すると、Sequence
型に見えますが、検索の際に整数ではなく任意の immutableな(変更不可な) なキーを使うため、Sequence
型ではなくMapping
型であることに注意します。 (Mapping型については、dict
とか、collections.defaultdict
とか。同様に、抽象クラスのcollections.abc.Mapping
を参考にすれば良いと思います。)
組み込みコンテナ型
tuple, list, dict, set
と、それぞれのtipsを紹介します。
tuple
-
tuple
の作成に関して、間違いやすい点をいくつか。tupleは「丸括弧で作成されるのではなく、カンマによって作成され」ます5:
# 空tupleを作りたいとき(0,1個に対しても、共にfor文で回したい場合とか)
## 空のtupleを作る
a_tuple0 = ()
## 1つのtupleを作る
a_tuple1 = 0, # tupleとして解釈される:
a_tuple1 = (0,) # これでもOK!!
# a_tuple1 = (0) int型になってしまい、tupleになってくれない!
Note) これに関連する注意点として、a_tuple1 = 0,
のような文(右端に意図しないカンマ)が、めっちゃあとで悪さする場合があるので、十分注意(自分の環境ではpyCharmなどのIDEでも警告してくれなかった)
a = do_return_int(accel, bike), #要素がint型のtupleを返す => 意図しない結果に!!
- tupleは値を(当然)getできるが、setはできません(不変な(immutable)オブジェクトであることが保証される.要素数を変更することもできない):
tup = (1,2,3)
tup[0] = 10 # TypeError: 'tuple' object does not support item assignment
もし、tupleを「変更」したければ、新しくオブジェクトを返すしかないです。ちなみに、str
型もimmutableなSequence型なので、c = a + b + c + d
みたいにやると、+
演算子が作用するごとに新規にオブジェクトが生成されます6。
- tupleは不変なオブジェクトのため、
Mapping
型のキーに指定することができます。(list
は変更可能なので、キーにできない) 詳しくは、dict
の節を参照してください。
list
- 要素ごとに演算したい場合は内包表記か
numpy
を用いると良いです:
# リストを要素ごとに演算したい場合は、下のようなことをやってしまいがちなので、注意!!
num = [1,2,3] * 3 # [1, 2, 3, 1, 2, 3, 1, 2, 3] ([3,6,9]でないことに注意)
# 正しくは以下のコード:
[n*3 for n in num] # [3,6,9]
[n for n in range(1,10) if n % 3 == 0] # [3,6,9]
import numpy as np
np.arange(1,4)*3 # array([3, 6, 9])
- 逆順の配列を取得したい時はインデクサのスライスを有効に使いましょう:
a = [1,2,3,4,5]
a[::-1] # [5,4,3,2,1]のように逆順のリストを取得できる
- listのlistをflatにしたかったら、
itertools
を使っていいよ😆
lst = [[1,2,3],[2,3,4],[3,4,5],[4,5,6]]
from itertools import chain
# iteratorを返すので、list化する
list(chain.from_iterable(lst)) # [1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6]
- pandas.DataFrameの要素にlistを突っ込むときは注意:
import pandas as pd
df1 = pd.DataFrame(
{'name': ['Danny', 'Jess', 'Joey', 'D.J.', 'Steph', 'Michelle'],
'age': [29, 24, 29, 10, 5, 0],
'sex': ['m', 'm', 'm', 'f', 'f', 'f']},
columns=['name', 'age', 'sex'])
# リストを突っ込みたいが...
#df1.loc[1,"name"] = [2,3,4] # Must have equal len keys and value when setting with an iterable
こんなエラーが出てしまうのは、df1.loc[1,"name"] = [2,3,4]
の左辺が要素数1を要求しているのに対し、右辺は要素数3になってしまっているためです。
df1.loc[[1,2,3],"name"] = [2,3,4] # OK
df1.loc[[1,2],"name"] = [2,3,4] #左辺と右辺で要素数が異なっているため、エラー
要素にリストを突っ込みたい場合は、代わりに以下のようにすると良いです。
df1.set_value(2, 'name', [2,3,4])
zipとの併用
lst = [("a", 1), ("b", 6), ("c", 9)]
## "*args"はlstをアンパックして、可変長引数(argsはtuple型 (('a', 1), ('b', 6), ('c', 9)) として取り扱う
# zip(("a", 1), ("b", 6), ("c",9)) 9))と解釈されるので、第一要素のtupleと第二要素のtupleに分離される
list(zip(*lst))
# [("a", "b", "c"), (1,6,9)]
dict
- dictは順番を保存しない点に注意。
# dictは順不同であることに注意
dic = {"a":1, "b":6, "c":9}
for key, values in dic.items():
print(key,end ="/")
# c/a/b # 順不同になる。
keyを意図した順番に並べたい場合は、collections.OrderedDict
を使用します(後述):
- dictは存在しないキーにアクセスした場合はKeyErrorが送出され、クラッシュしてしまいます。こういうエラーが起こって欲しくない場合は、
collections.defaultdict
(後述)を使うと、存在しないキーに対してもデフォルト値を自動的に割り当ててくれるため、dictのように条件分岐をする必要なく、非常に便利なクラスです。
- キーワード引数にdict型の引数を一時変数として格納するのも、便利なことがあります:
kwargs = {"argA" : 14, "argB" : [4,5,6], "argC" : "sth"}
kwargs.update({"argD" : 5.64})
# パラメータの個数が多い時は、dict型に**をつけると、キーワード付きの引数群にアンパックされる
func_kwargs1(required_arg, **kwargs)
kwargs.update({"argA" : 0})
## kwargsとして、使い回しできるのもいいところ。
func_kwargs2(other, **kwargs)
- keyとvalueを入れ替えたい時:
{v:k for k, v in dic.items()} # keyとvalueを入れ替える
- valueがNoneのkeyを排除したい
{key:value for key, value in header_dict.items() if value is not None}
とすれば良い。
以下の場面で使用した:
# 取りうるキーワードは決まっているから、極力kwargsにはしたくない。
def get_http_headers(*, user_agent=None, lang=None, referer=None, cookie=None, evaluate=None):
header_list = [
"X-Forward-User-Agent",
"X-Forward-Referer",
"X-Forward-Cookie",
"X-Forward-Accept-Language",
"X-Forward-X-Evaluate",
]
headers = zip(header_list, [user_agent, referer, cookie, lang, evaluate])
# valueがNoneのkeyを排除したい
header_dict = {key:value for key, value in headers if value is not None}
return header_dict
Note) *
引数については、https://docs.python.jp/3/reference/compound_stmts.html#function-definitions の
“*” や “*identifier” の後のパラメタはキーワード専用パラメータで、キーワード引数を使ってのみ渡されます。
に記述がある。http://qiita.com/junkoda/items/bfd35793c5cd33c600bc も参考にすること。
- dictのupdate関数は破壊的な関数です.これを防ぐには、以下のように書きます:
d = {"a" : 1, "b" : 2}
# destructive method!!
d.update({"c" : 3})
d1 = {"a" : 1, "b" : 2}
d2 = {"c" : 3}
# non destructive
e = {**d1, **d2} # {'a': 1, 'b': 2, 'c': 3}
- dictのキーとしてlist型は使用できません。なぜなら、listはmutable7なシーケンス型だから(後述)。かわりにimmutableな型である、tupleをdictのkeyとして用いれば、OK!
# 2語のペアのワード群を抜き出す
twins = extract_page_words(mp, sentence, window = 1)
# twinとこのペアのスコアを辞書にしたい!!
dic = {}
for twin in twins:
# twinsがlist型だと、unhashable type: 'list'となり、エラー
# tuple型はイミュータブルなので、(つまり、keyが不変な値なので)OK
dic.update({twin : get_score(twin)})
Note) ただし、タプルでも、直接、あるいは間接的に変更可能なオブジェクトを含むタプルはキーにできません。(今回は、str型でimmutableなので、OK)
- もし、immutableなdictを使いたければ、
frozendict
を用います。(個人的に使ったことないので、使用法については省略)
Note) MappingProxy
とfrozendict
の違いについては、https://stackoverflow.com/questions/41795116/difference-between-mappingproxytype-and-pep-416-frozendict を参照のこと。(MappingProxy
はobj.__dict__
の型で登場したりします) MappingProxy
はあくまで実体のproxy(代理)なので、実体が別にあります。実体がmutableで変化すれば、MappingProxyで覗き見る値も変化します。一方で、frozendict
は不変なオブジェクトです。
zipとの併用
# dictをlistのペアから作成する(非常によく使う!!)
seasons = ['summer', 'winter']
tbls = dict(zip(seasons, itertools.repeat(1)))
# zipの引数にdicを突っ込むことはあんまりないが..
list(zip(*dic.items())) # [('c', 'a', 'b'), (9, 1, 6)] keyは順不同
set
個人的にはset
はlist
の重複を取りのぞく時と、二つの集合の共通集合を手に入れるときくらいしか、使わないです。
# 重複を取り除く
lst = [3, 4, 3, 2, 5, 4]
lst_uniq = list(set(li)) # [2,3,4,5] 順番は保存されない
# 共通集合を取得
list_a = [1,2,3,4]
list_b = [3,2,6,5]
set(list_a).intersection(list_b) # {2,3} 共通集合
Note) set
とは直接は関係ないけれど、重複除去つながりで。 pandas.DataFrame
で、重複している行を削除するには、以下のようにします:
import pandas as pd
# 重複行を削除
df1.drop_duplicates()
# "colA"が重複していたら、最初の該当行を残して削除
#new_df = df1.drop_duplicates("colA", keep="first") # keepのdefaultは ‘first’
# indexの重複している行を削除
new_df = df1[~df1.index.duplicated()]
collections
度々触れたように、pythonでは組み込みのコンテナ型の他にも、色々便利なクラスをcollections
モジュールに用意してくれています:
OrderedDict
組み込み型のdict
は順番を保持しないので、格納した順番通りにデータを取得したい時は、OrderedDict
を用います。
ただし、sortしたオブジェクトを突っ込まないと、意図した順に保持してくれません:
import collections
dic = {"a":1, "b":6, "c":9}
# ただし、直接突っ込んでも並びは順不同
collections.OrderedDict(dic) # OrderedDict([('a', 1), ('c', 9), ('b', 6)]) で意図した並びでない!!
# sortしたオブジェクトを突っ込むと意図した並びが帰ってくる
## sorted(dic.items(), key=lambda t: t[0]) # [('a', 1), ('b', 6), ('c', 9)]を利用する
d = collections.OrderedDict(sorted(dic.items(), key=lambda t: t[0]))
d = collections.OrderedDict(sorted(dic.items(), key=lambda t: t[0]))
for key, values in d.items():
print(key,end="/")
# a/b/c/ ## 意図した順番に返ってくる
defaultdict
defaultdict
は初期化を伴うdict
で、キーの存在の有無を気にせず、インデクサに好きなものを突っ込めるので、使い勝手が非常に良いです.
ただし、初期化は、ちょっと癖があります。特に、 defaultdict
をnestする場合は、結構ハマりやすいです。
from collections import defaultdict
# int is the function which arity is 0
di = defaultdict(int)
#ordereddictの入れ子の初期化方法
## defaultdict constructor is the function
# d = defaultdict(defaultdict(int)) # TypeError: first argument must be callable or None
d = defaultdict(lambda : defaultdict(int)) # 引数を関数オブジェクトとしよう!!
print(d["b"]) # defaultdict(<class 'int'>, {})
d["b"]["colA"] = 1
print(d) # defaultdict(<function <lambda> at 0x1116cde18>, {'b': defaultdict(<class 'int'>, {'colA': 1})})
Counter
Counter
はOrderedDict
の初期化をもうちょっとリッチにしたバージョン。使い勝手良さそうと思って使ってみると、Counter
同士の足し算とかできるけど、Counter
とint
の掛け算とかできない。pandasのSeries
(Series
とnumber
の四則演算が可能)とdefaultdict
(キーの存在有無が不要)でだいたい事足りるため、案外使う場面は少ない。
ということで、Counter
クラスは個人的にはあんまり使い勝手よくない..。また、generator
をコンストラクタの引数に持っていけないところも、微妙><
import collections
data = [("空", "名詞"), ("よう", "名詞"), ("よう", "形容詞"), ("空", "名詞")]
collections.Counter(data) # Counter({('空', '名詞'): 2, ('よう', '名詞'): 1, ('よう', '形容詞'): 1})
# MeCabの分かちがきの場面などで、fileを引数に取り、lazyにdataを取ってくるみたいな関数
def lazy_split():
data = [("空", "名詞"), ("よう", "名詞"), ("よう", "形容詞"), ("空", "名詞")]
for d in data:
yield data
# generatorは引数に取れないので、使えるようであんまり使い勝手はよくないかも。
collections.Counter(lazy_split())
namedtuple
tupleのリッチ版。インデックスやイテレータだけでなく属性名によるアクセスもできる。tupleに構造をもたせたいとかに使うと良いと思います。特にハマる点はなかったはず。
まとめ
iterable, iterator
からはじまりcontainer
の個々のtipsまで説明しました。collections.abc
が背後にあったりして、いままで何となーくわかっていたものが、より明確になって良かったな、と思いました😇
参考url
- https://docs.python.jp/3/glossary.html 用語集
- https://docs.python.jp/3/library/collections.abc.html#collections-abstract-base-classes collections.abcの一覧表
- https://docs.python.jp/3/reference/datamodel.html#special-method-names 特殊メソッド
- http://diveintopython3-ja.rdy.jp/special-method-names.html 特殊メソッドのわかりやすい解説
- http://nvie.com/posts/iterators-vs-generators/ iterator, container, genaratorの最初の図がわかりやすい
- https://docs.python.jp/3/library/stdtypes.html#common-sequence-operations 共通のシーケンス演算
-
タイトル変えました ↩
-
https://docs.python.jp/3/glossary.html#term-iterable を参照のこと。ただし、https://docs.python.org/3.5/library/collections.abc.html#collections.abc.Iterable で書いてあるように、
__getitem__
で定義すると、isinstance
メソッドでIterableを判定できないことに注意してください。(iter(obj)
で判断するよう書いてあります) ↩ -
関係性については、 http://nvie.com/posts/iterators-vs-generators/ の最初の図がわかりやすいです。 ↩
-
Effective Python 項目17 : 「引数に対してイテレータを使うときには、確実さを尊ぶ」 も参照。 ↩
-
mutableな文字列を扱いたければ、
bytearray
を使います。 ↩ -
mutableは「変更可能な」という意味。C++だと、mutable修飾子とかいうのがあります。 ↩