『Fluent Python』読書ノート-Dictionaries and Sets

8076 ワード

概要
ゞ    在collections.ゞabcでは、dictおよび類似のタイプのインタフェースを定義するために、MappingおよびMutableMappingが定義されています.一般的な特殊なタイプのマッピング(mappings)はdictまたはcollectionsから継承される.UserDict.
Hashable
標準ライブラリ内のマッピングタイプはdictから継承されるため、keyはhashableでなければならないという制限があります.オブジェクトがhashableである3点:1.ライフサイクル全体でhash値が変化しないhash値があります(メソッドは__hash__()が必要です).2.他のオブジェクトと比較することができる(1つの__eq__()方法が必要である).hashableのオブジェクトは、比較が等しい場合は同じhash値を持つ必要があります.一般的なhashableのタイプ:1.原子の可変タイプ(immutable)はstr,bytes,numericタイプなどhashableである.2.frozensetは常にhashableであり、その要素の要求はすべてhashableでなければならないからである.3.tupleすべての要素がhashableであればhashableです.4.ユーザー定義タイプのデフォルトはhashableです.hash値はid()であり、等しくないからです.オブジェクトがカスタム__eq__メソッドを実装している場合、すべてのプロパティがimmutableのオブジェクトである場合にのみhashableの内部状態を考慮する必要があります.
>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unhashable type: 'list'
>>> class A:
...     def __init__(self):
...         pass
... 
>>> a = A()
>>> b = A()
>>> hash(a)
-9223372036571095159
>>> hash(b)
283680646
>>> a == b
False
__hash__()および__eq__のカスタマイズについては、pythonのマニュアルに関連する内容を詳しく参照してください.ここでもう一つの問題はis==の違いである.isはオブジェクトのidが同じかどうかを比較し、同じインスタンスオブジェクトであるかどうか、同じメモリアドレスであるかどうかを意味する.一方、==は、2つのオブジェクトの内容が同じかどうかを比較し、デフォルトではオブジェクトの__eq__メソッドが呼び出されます.objectから継承された__eq__メソッドは、2つのオブジェクトのidを比較し、結果はisと同じである.しかし、多くのPythonのオブジェクトはobjectの__eq__メソッドを上書きし、コンテンツの関連比較を定義するので、オブジェクト属性の値を比較します.
>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True
>>> b == a
True
>>> b = a[:]
>>> b is a
False
>>> b == a
True

   数字型用isは比較的特殊であり、[-5,256]区間内のsmall_ints、pythonはキャッシュされ、新しいオブジェクトは作成されません.
>>> a = 256
>>> b = 256
>>> a is b
True
>>> a == b
True
>>> a = 257
>>> b = 257
>>> a is b
False
>>> a == b
True

   文字列isを使用した結果も必ずしも同じとは限らない.
>>> c = 'abc.com'
>>> b = 'abc.com'
>>> c is b
False
>>> c == b
True
>>> c = 'efg'
>>> d = 'efg'
>>> c is d
True
>>> c == d
True

   tuple,list,setのis==の結果は異なります.
>>> a = (1, 2, 3)
>>> b = (1, 2, 3)
>>> a == b
True
>>> a is b
False
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a == b
True
>>> a is b
False
>>> a = set([1, 2, 3])
>>> b = set([1, 2, 3])
>>> a == b
True
>>> a is b
False

dict Comprehensions
ジルコニウムジルコニウムdictの作成には、次のような方法があります.
>>> a = dict(one=1, two=2, three=3)
>>> b = {'one': 1, 'two': 2, 'three': 3}
>>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
>>> d = dict([('two', 2), ('one', 1), ('three', 3)])
>>> e = dict({'three': 3, 'one': 1, 'two': 2})
>>> a==b==c==d==e
True
>>> DIAL_CODES = [
... (86, 'China'),
... (91, 'India'),
... (81, 'Japan'),
... ]
>>> country_code = {country: code for code, country in DIAL_CODES}
>>> country_code
{'China': 86, 'India': 91, 'Japan': 81}

欠落した値の処理
同じ論理のいくつかの実現方式.
>>> my_dict = {}
>>> key = 'a'
>>> if key not in my_dict:
...     my_dict[key] = []
... 
>>> my_dict[key].append("value")
>>> my_dict
{'a': ['value']}
>>> my_dict = {}
>>> key = 'a'
>>> my_list = my_dict.get(key, [])
>>> my_list.append("value")
>>> my_dict[key] = my_list
>>> my_dict
{'a': ['value']}
>>> my_dict = {}
>>> key = 'a'
>>> my_list = my_dict.setdefault(key, [])
>>> my_list.append("value")
>>> my_dict
{'a': ['value']}
>>> my_dict = collections.defaultdict(list)
>>> key = 'a'
>>> my_dict[key].append("value")
>>> my_dict
defaultdict(, {'a': ['value']})
>>> class ListDict(dict):
...     def __missing__(self, key):
...         return []
... 
>>> a = ListDict()
>>> my_list = a[key]
>>> my_list
[]
>>> my_list.append("value")
>>> a[key] = my_list
>>> a
{'a': ['value']}

前の3つの方法ではdictを直接使用すると、setdefaultは前の2つの方法よりも効率的になります.欠落した値の処理をより柔軟に行う必要がある場合は、第4の方法(default_dict)と第5の方法(dictを継承)を使用します.   欠落値処理の本質は__missing__という特殊な方法の呼び出しであり、この方法は__getitem__(d[k]のような形式)が欠落値に遭遇した場合にのみ呼び出され、get,__contains__(in方法)などの他のdictの方法には影響しない.したがって,欠落値を処理する際には,他の方法も対応する処理が必要である.
その他のタイプのdict
collections.OrderedDict:keyの挿入順序に従ってkeysを維持し、予測可能な順序でkeyを遍歴することができます.popitemの最初の要素または最後の要素も使用できます.collections.ChainMap:いくつかのdictを一緒に検索し、検索はdictの順に行い、いずれかのdictでkeyを見つけると検索に成功します.たとえば、変数を検索する場合は、ローカル変数を検索してからグローバル変数を検索し、最後にシステム変数を検索できます.また、コマンドラインパラメータでは、ユーザーが入力した変数を検索してから、変数のデフォルト値を検索できます.collections.Counter:keyごとにカウントを維持し、updateメソッドでkey増加カウントを更新し、most_common移動回数が最も多いn個のkeyを返すことができます.
Subclassing UserDict
一般に、新しいmappingタイプを作成するには、UserDictを継承することによって、dictを継承するよりも、UserDictを継承するほうが簡単です.主な原因はbuilt-inオブジェクトのいくつかの実装が近道を歩むため、メソッドを再ロードする必要がありますが、継承MappingProxyTypeにはこれらの問題はありません.具体的な原因は、後続の章で詳しく説明します.
Immutable Mappings
元のmappingの動的認識図は、元のmappingの更新がこのmappingproxyで見られることを意味するが、元のmappingを更新することはできない.
>>> from types import MappingProxyType
>>> d={1:'A'}
>>> d_proxy = MappingProxyType(d)
>>> d_proxy
mappingproxy({1: 'A'})
>>> d_proxy[1]
'A'
>>> d_proxy[2] = 'x'
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'mappingproxy' object does not support item assignment
>>> d[2] = 'B'
>>> d_proxy
mappingproxy({1: 'A', 2: 'B'})
>>> d_proxy[2]
'B'

Set
    setは、重量除去に使用できる一意のオブジェクトの集合です.setに格納されている要素はhashableでなければなりませんが、set自体はhashableではありませんが、frozensetはhashableです.
>>> l = ['spam', 'spam', 'eggs', 'spam']
>>> set(l)
{'spam', 'eggs'}
>>> hash(set(l))
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unhashable type: 'set'

   setは、a | bがunionを表し、a & bがintersectionを表し、a - bがdifferenceを表すような接尾辞オペレータを提供します.    はsetを作成して{}を使用しますが、空のセットはset()を使用して作成する必要があります.そうしないとdictが作成されます.
>>> s={1}
>>> type(s)

>>> s={}
>>> type(s)

>>> s = {i for i in range(10)}
>>> s
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
>>> type(s)


dict and set Under the Hood
ジルコニウムコアはhash tableであり、dictとsetの要素がhashableである必要がある理由でもある.ただしjavaと比較してpythonはhash競合を解決するためにオープンアドレス法を使用し、javaはリンク法を使用します.オープン・アドレス・メソッドを使用すると、メモリのオーバーヘッドが大きくなり、競合が解決できない場合は、メモリを拡張し、要素の再分布が必要になります.しかしkeyの迅速な検索に適しています.