LoginSignup
48
42

More than 5 years have passed since last update.

PythonのIterableを順に追っていく

Last updated at Posted at 2017-06-13

はじめに

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

# 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 expressiongenerator 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

本来は、ContainerIterableは微妙に違うものです。(要求される特殊メソッドが__contains____iter__で違う)
しかしながら、Iterableなオブジェクトはin演算子の挙動がContainerと同じなため、Containerのように扱えます。また、Container型だがIterableでないようなクラスはかなり病的であり、普段ならほぼ考える必要性がないです。
というわけで、ContainerIterableが一緒くたにされてしまうことも割と多いように感じます。


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) MappingProxyfrozendictの違いについては、https://stackoverflow.com/questions/41795116/difference-between-mappingproxytype-and-pep-416-frozendict を参照のこと。(MappingProxyobj.__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

個人的にはsetlistの重複を取りのぞく時と、二つの集合の共通集合を手に入れるときくらいしか、使わないです。

# 重複を取り除く
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

CounterOrderedDictの初期化をもうちょっとリッチにしたバージョン。使い勝手良さそうと思って使ってみると、Counter同士の足し算とかできるけど、Counterintの掛け算とかできない。pandasのSeries(Seriesnumberの四則演算が可能)と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


  1. タイトル変えました 

  2. 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)で判断するよう書いてあります) 

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

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

  5. http://docs.python.jp/3/reference/expressions.html に書いてある 

  6. mutableな文字列を扱いたければ、bytearrayを使います。 

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

48
42
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
48
42