PythonのIterableを順に追っていく

# はじめに

`iterator, generator`とか`list, range, tuple`などのシーケンスや`collections`, `collections.abc`などの概要とtipsをまとめてみました[^title].

[^title]: タイトル変えました

## 環境

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__`特殊メソッドが定義されている)オブジェクト[^iterable]であり、`iterator`とは`__iter__`と`__next__`が定義されているオブジェクトです。`__iter__`は`iterator`を返す特殊メソッド、`__next__`は要素を反復して取り出すことのできる特殊メソッドです。

[^iterable]: 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)`で判断するよう書いてあります)

なぜ、配列構造とは別に、`iterator`なんてものがあるんだ？って疑問については、`Iterator Pattern`まで戻って考える必要があります。
ここでは、ざっくり簡単にいうと、IterableとIteratorを分離して、データ構造自体と、データの走査方法の役割を分担することで、それぞれの責任を分離でき、疎結合が実現できるからです。

ここで、Pythonで`iterable`と`iterator`をゼロから自作してみます。iterableなオブジェクトが`__iter__`でiteratorを作成し、`__next__`でiteratorを走査するという感じです：
(`__getitem__`を用いて`iterable`を実装する場合の例は、コメント欄の https://qiita.com/knknkn1162/items/17f7f370a2cc27f812ee#comment-27aee9b7b4ae2fe3eed4 にて例が記載されています。ありがとうございます:bow:)

```python
# 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`関数を用います：

```python
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__`を繰り返すような挙動になっています：


```python
# 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`とかの節でも似たような話をしますし、簡単ですので、ここで触れておきます。

```python
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`があります。[^relation]

[^relation]: 関係性については、 http://nvie.com/posts/iterators-vs-generators/ の最初の図がわかりやすいです。

`generator expression`は 


```python
(x * x for x in numbers)
```

みたいな感じで書きます(内包表記みたいに)

`generator function`は

```python
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`は、よくファイルの読み取り関連で使ったりします.
(全ての内容を一気に読み取ってから、結果を返すと、サイズが大きすぎでクラッシュする可能性があるため)[^ch_17]

```python
# 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_`などをつけ、遅延処理であることが容易に想像できるようにする方が親切でしょう。




あとは、使うタイミングは限られますが、無限列を作りたいときとかにもジェネレータを用います：

```python
def fib():
    a,b = 0,1
    yield 0
    yield 1
    while True:
        yield a+b
        a, b = b, a+b # tupleならではの使い方
```

無限列の最初の10個とか取って来たければ、以下のようにすると良いと思います：

```python
#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より大きい要素で打ち切る
```


[^ch_17]: Effective Python 項目17 : 「引数に対してイテレータを使うときには、確実さを尊ぶ」 も参照。

## container

container型のオブジェクトは`__contains__`を実装するようなオブジェクトです。(例としては、`list, dict, tuple, str, collections.defaultdict`など、いわゆる「コンテナ型」で幅広いです) 
 `__contains__`は帰属テスト演算(`in`演算子)をサポートする特殊メソッドです。なので、要素の入れ物としての機能を有するオブジェクトと考えれば良いでしょう。`collections.abc.Container`クラスを継承することで、コンテナ型としての性質を持つクラスを作成することができます。次節で紹介する`Sequence`型は`Container`型であり、`Container`型としての性質に加えて、インデクサのスライスが定義されていることが一つの大きな特徴です(`sequence`の節を参照)。

Note) `for`文については、`iterator`の節で内部挙動を観察しました。一方、ややこしいのですが、`in`演算子自体は`__contains__`と一対一対応しているわけではありません。

`__contains__`を有するクラスに対しては、以下のような内部挙動になります：

```python
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`演算子を適用できます：

```python
# 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`をすると、**参照としてコピーされる**所です：


```python
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`を用いればよいです：

```python
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で変わっても結果が伝播しない!
```

これぐらいだと、間違えないですよね？ でも、ちょっと複雑なことをすると、なかなか根深いバグに繋がったりすることがあります：


```python
# 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}}
```

とか、

```python
lsts = [[]] * 3 # 要素はlist型なので、参照値のコピー
print(lists) #[[], [], []]
lsts[0].append(3)
print(lsts) # [[3], [3], [3]](ぎゃー)
```

とか。

Note) それぞれの解決法について。

前者は`defaultdict`を用いればよい：

```python
# defaultdictを用いて、初期化にdict()と指定する
tbls = collections.defaultdict(dict)
for elem in seasons:
    if elem == "summer":
        tbls[elem].update({"beach" : 30})
        
    print(tbls[elem])
```
  
後者については、内包表記を使おう!
  
```python
lsts = [[] for _ in range(3)]
lsts[0].append(3)
print(lsts) # [[3], [], []]
```

---


Note) tupleでもvalueそのものはコピーされない(ただし、tuple型のオブジェクトは再代入できず、immutableなのでトリビア的だけど)

```python
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`がそれに当たります。

```python
# 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)`のシンタックスシュガーです：

```python
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は「丸括弧で作成されるのではなく、カンマによって作成され」ます[^tuple]：

[^tuple]: http://docs.python.jp/3/reference/expressions.html に書いてある

```python
# 空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でも警告してくれなかった)

```python
a = do_return_int(accel, bike), #要素がint型のtupleを返す => 意図しない結果に!!
```

----

+ tupleは値を(当然)getできるが、setはできません(不変な(immutable)オブジェクトであることが保証される.要素数を変更することもできない)：

```python
tup = (1,2,3)
tup[0] = 10 # TypeError: 'tuple' object does not support item assignment
```

もし、tupleを「変更」したければ、新しくオブジェクトを返すしかないです。ちなみに、`str`型もimmutableなSequence型なので、`c = a + b + c + d`みたいにやると、`+`演算子が作用するごとに新規にオブジェクトが生成されます[^bytearray]。

[^bytearray]: mutableな文字列を扱いたければ、`bytearray`を使います。

---

+ tupleは不変なオブジェクトのため、`Mapping`型のキーに指定することができます。(`list`は変更可能なので、キーにできない) 詳しくは、`dict`の節を参照してください。

### list

+ 要素ごとに演算したい場合は内包表記か`numpy`を用いると良いです：

```python
# リストを要素ごとに演算したい場合は、下のようなことをやってしまいがちなので、注意!!
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])
```

---

+ 逆順の配列を取得したい時はインデクサのスライスを有効に使いましょう：

```python
a = [1,2,3,4,5]
a[::-1] # [5,4,3,2,1]のように逆順のリストを取得できる
```

----

+ listのlistをflatにしたかったら、`itertools`を使っていいよ😆

```python
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を突っ込むときは注意：

```python
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になってしまっているためです。

```python
df1.loc[[1,2,3],"name"] = [2,3,4] # OK
df1.loc[[1,2],"name"] = [2,3,4] #左辺と右辺で要素数が異なっているため、エラー
```

要素にリストを突っ込みたい場合は、代わりに以下のようにすると良いです。

```python
df1.set_value(2, 'name', [2,3,4])
```

#### zipとの併用

```python
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は順番を保存しない点に注意。

```python
# 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型の引数を一時変数として格納するのも、便利なことがあります：

```python
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を入れ替えたい時：

```python
{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}`とすれば良い。
以下の場面で使用した：

```python
# 取りうるキーワードは決まっているから、極力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関数は破壊的な関数です.これを防ぐには、以下のように書きます：

```python
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はmutable[^meaning_of_mutable]なシーケンス型だから(後述)。かわりにimmutableな型である、tupleをdictのkeyとして用いれば、OK!  

[^meaning_of_mutable]: mutableは「変更可能な」という意味。C++だと、mutable修飾子とかいうのがあります。

```python
# 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との併用

```python
# 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`の重複を取りのぞく時と、二つの集合の共通集合を手に入れるときくらいしか、使わないです。

```python
# 重複を取り除く
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`で、重複している行を削除するには、以下のようにします：

```python
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したオブジェクトを突っ込まないと、意図した順に保持してくれません：

```python
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する場合は、結構ハマりやすいです。

```python
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`をコンストラクタの引数に持っていけないところも、微妙><


```python
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 共通のシーケンス演算