Python: setとsetのcopyについて

Python

list, dictionaryのcopyを見て来たので今回はsetについて。

今回はsetの簡単な把握から。

setとは

まず最初に公式チュートリアルによると下記の通り。

iterable から要素を取り込んだ、新しい set もしくは frozenset オブジェクトを返します。 集合の要素は ハッシュ可能 なものでなくてはなりません。

setはlistやdictionaryとは異なり、iterable(反復可能なオブジェクト)から要素を取り込んだオブジェクト。

iterableとは

iterableとは公式チュートリアルによると下記の説明がある。

(反復可能オブジェクト) 要素を一度に 1 つずつ返せるオブジェクトです。 反復可能オブジェクトの例には、(liststrtuple といった) 全てのシーケンス型や、 dict や ファイルオブジェクト といった幾つかの非シーケンス型、 あるいは Sequence 意味論を実装した __iter__() メソッドか __getitem__() メソッドを持つ任意のクラスのインスタンスが含まれます。

1つずつにして返す事のできる要素をいくつかまとめた集合としてのもの(オブジェクト)であり、例としてlist、 str、 tupple、dictを上げている。

以下、例で考える。

list型

>>> lst = [1, 2, 4, 6, 2, 3, 5, 7, 14, 16, 84, 133, 5]
>>> set(lst)
{1, 2, 3, 4, 5, 6, 7, 133, 14, 16, 84}

上記の様に、listであるlst = [1, 2, 4, 6, 2, 3, 5, 7, 14, 16, 84, 133, 5]は、要素(1、2、4、6…)を1つずつ返せるオブジェクト。set(lst)で重複の無いset{1, 2, 3, 4, 5, 6, 7, 133, 14, 16, 84}を返す。

この時重複した要素は1つにまとめられ、並びは要素の大小順番と無関係。

str型

>>> st = "Apple Juice"
>>> set(st)
{'p', 'J', 'A', 'u', 'i', 'e', 'l', 'c', ' '}

>>> stj = "おにぎりコロコロ"
>>> set(stj)
{'コ', 'に', 'ぎ', 'お', 'り', 'ロ'}

上記の様に、str型(文字列)もその要素(一文字)を1つずつ返せるオブジェクト。英単語も日本語もそれぞれ一文字が1つの要素として扱われる。

>>> st2 = ["Apple", "Juice", "Apple", "Jhon"]
>>> set(st2)
{'Juice', 'Jhon', 'Apple'}

上記の様に同じ文字でも1単語ずつを要素にしたlist型のオブジェクトにした場合は、単語でまとめたsetとして出力する。

一旦リストになっていても、これをjoin等でまとめてstr型にすると、下記の様にstr型としてまとめたsetを出力する。

>>> set("".join(st2))
{'p', 'J', 'A', 'u', 'o', 'i', 'e', 'l', 'h', 'n', 'c'}

dict型(dictionary)

次にdict型(dictionary)。tupple型は基本的にlist型と似た型と言う理解(←ざっくり過ぎ)。

>>> dc = {"a":1, "b":2, "c":3, "d":2}
>>> dc
{'a': 1, 'b': 2, 'c': 3, 'd': 2}
>>> set(dc)
{'c', 'a', 'b', 'd'}

dict型をsetにした場合、key(この場合”a”, “b”, “c”, “d”)のみが順不同に並ぶ。

集合の要素はハッシュ可能なものでなければならない、と言う事なのでdictionaryのvalueは集合になり得ないと今は理解しておく。

>>> dc2 = {"a":1, "b":2, "c":3, "c":4, "d":2}
>>> dc
{'a': 1, 'b': 2, 'c': 4, 'd': 2}

ちなみに、このdic2の様に、dict型については同じkeyを入れようとしても、後のkeyでvalueを上書きするだけ(今回は”c”:3から”c”:4に上書き)と言う理解。

setを使う理由の1つは要素の重複を無くす事なので、そもそもkeyに重複が無いdict型をsetで使う事は殆ど無さそう、と言うのが現在の理解。

setのcopy

setがどう言うものかなんとなくわかった(?)所で、setのcopyも見てみる。

参照と浅いコピー

従前通り参照と浅いコピーを試してみる為にst1~st5を準備。

>>> import copy
>>> st1 = set(lst)
>>> st2 = st1                         # 参照
>>> st3 = st1.copy()     # 浅いコピー
>>> st4 = copy.copy(st1) # 浅いコピー
>>> st5 = set(st1)             # 浅いコピー

>>> st1
{1, 2, 3, 4, 5, 6, 7, 133, 14, 16, 84}
>>> st2
{1, 2, 3, 4, 5, 6, 7, 133, 14, 16, 84}
>>> st3
{1, 2, 3, 4, 5, 6, 7, 133, 14, 16, 84}
>>> st4
{1, 2, 3, 4, 5, 6, 7, 133, 14, 16, 84}
>>> st5
{1, 2, 3, 4, 5, 6, 7, 133, 14, 16, 84}

setにはインデックス(リストの○番目の様な場所)による変更はできない。

addで追加、removeで選択してそのものを削除する。

>>> st1.add(10)        # st1に10を追加
>>> st1.remove(5)    # st1から5を削除

>>> st1
{1, 2, 3, 4, 6, 7, 133, 10, 14, 16, 84} # オリジナル
>>> st2
{1, 2, 3, 4, 6, 7, 133, 10, 14, 16, 84} # 参照
>>> st3
{1, 2, 3, 4, 5, 6, 7, 133, 14, 16, 84}  # 浅いコピー
>>> st4
{1, 2, 3, 4, 5, 6, 7, 133, 14, 16, 84}  # 浅いコピー
>>> st5
{1, 2, 3, 4, 5, 6, 7, 133, 14, 16, 84}  # 浅いコピー

listやdictionaryと同様に参照データは追加や削除を反映するが、浅いコピーはオリジナルのまま。

深いコピー?

深いコピーの実験の為に、listを含むsetを作ってみる。

>>> lst = [[1, 2], [1, 2], 2, 3, 2]
>>> st7 = set(lst)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

あれ?listを含むsetが作れない。

良く見たら”TypeError: unhashable type: ‘list’ “と言うエラーが出ている。

ここで公式チュートリアルの説明をもう一度。

iterable から要素を取り込んだ、新しい set もしくは frozenset オブジェクトを返します。 集合の要素は ハッシュ可能 なものでなくてはなりません。

そう、setの要素はハッシュ可能なものである必要があるが、listはunhashable typeと言う事で、listを含んだsetは作れない事になる。

あれ?そうすると深いデータを持つsetってどうすれば作れるのだろうか?

Pythonコード研究所のセットをコピーする copy deepcopyによると

組み込みオブジェクト以外のオブジェクトをセットの要素にした場合は、深いコピーを使う必要が出てきます。

とあるので、先で何か使う事がありそう。

結論

とは言え、今回のsetのcopyについての結論は

参照と浅いコピーの違いはlistやdictionaryと同様だけど、とりあえず深いコピーを気にする必要は無さそう。

情報を共有して下さっている皆様に感謝。

【50%還元】Kindle本ポイントキャンペーン

Amazonさんがこちらで2022年05月26日(木)23時59分(日本時間)まで50%還元のポイントキャンペーンだそうです。
欲しいものがあったら今がタイミング。

コメント

タイトルとURLをコピーしました