Python Language
MutableとPythonの不変(およびHashable)
サーチ…
変更不可能なものと不変なもの
Pythonには2種類の型があります。不変型および可変型。
不動産
不変型のオブジェクトは変更できません。オブジェクトを変更しようとすると、コピーが作成されます。
このカテゴリには、整数、浮動小数点数、コンプレックス、文字列、バイト、タプル、範囲、およびフーチェンセットが含まれます。
このプロパティを強調表示するには、 id
組み込んだものを試してみましょう。この関数は、パラメータとして渡されたオブジェクトの一意の識別子を返します。 idが同じ場合、これは同じオブジェクトです。変更された場合、これは別のオブジェクトです。 (これは実際にオブジェクトのメモリアドレスだと言われますが、それらには注意してください、彼らは力の暗い側からです...)
>>> a = 1
>>> id(a)
140128142243264
>>> a += 2
>>> a
3
>>> id(a)
140128142243328
さて、1は3ではない...ニュースを破る...たぶんそうではない。しかし、より複雑な型、特に文字列の場合、この動作はしばしば忘れられます。
>>> stack = "Overflow"
>>> stack
'Overflow'
>>> id(stack)
140128123955504
>>> stack += " rocks!"
>>> stack
'Overflow rocks!'
ああ!見る?我々はそれを変更することができます!
>>> id(stack)
140128123911472
いいえ、変数stack
で指定された文字列を変更することはできstack
、実際には、連結結果を格納する新しいオブジェクトを作成しています。その過程で、古いオブジェクトはどこにも行きませんので、私たちは欺かれています。別の状況では、それはもっと明らかだったでしょう:
>>> stack = "Stack"
>>> stackoverflow = stack + "Overflow"
>>> id(stack)
140128069348184
>>> id(stackoverflow)
140128123911480
この場合、最初の文字列を保持したい場合はコピーが必要です。しかし、それは他のタイプのためにとても明らかですか?
運動
今、不変型がどのように動作するかを知っていれば、以下のコードで何を言いますか?それは賢明ですか?
s = ""
for i in range(1, 1000):
s += str(i)
s += ","
ミュータブル
変更可能な型のオブジェクトは変更することができ、 その場で変更されます。暗黙のコピーは行われません。
このカテゴリには、リスト、辞書、バイテリア、セットが含まれます。
私たちの小さなid
関数で遊んでいきましょう。
>>> b = bytearray(b'Stack')
>>> b
bytearray(b'Stack')
>>> b = bytearray(b'Stack')
>>> id(b)
140128030688288
>>> b += b'Overflow'
>>> b
bytearray(b'StackOverflow')
>>> id(b)
140128030688288
(補足すると、ASCIIデータを含むバイトを使用してポイントを明確にしますが、バイトはテキストデータを保持するようには設計されていないことを忘れないでください。
私たちは何を持っていますか?私たちはbytearrayを作成し、それを修正し、 id
を使用して、これが修正された同じオブジェクトであることを保証することができます。それのコピーではありません。
もちろん、オブジェクトが頻繁に変更される場合、変更可能な型は不変型よりはるかに良い仕事をします。残念なことに、このプロパティの現実は、多くの場合、最も痛いときに忘れられます。
>>> c = b
>>> c += b' rocks!'
>>> c
bytearray(b'StackOverflow rocks!')
はい...
>>> b
bytearray(b'StackOverflow rocks!')
Waiiit a second ...
>>> id(c) == id(b)
True
確かに。 c
はb
コピーではありません。 c
はb
です。
運動
ここでは、どのような副作用が可変型によって暗示されているのかをよく理解しています。この例で何がうまくいかないのか説明できますか?
>>> ll = [ [] ]*4 # Create a list of 4 lists to contain our results
>>> ll
[[], [], [], []]
>>> ll[0].append(23) # Add result 23 to first list
>>> ll
[[23], [23], [23], [23]]
>>> # Oops...
引数として変更可能で不変
開発者がmutabilityを考慮する必要がある主要なユースケースの1つは、引数を関数に渡すときです。これは非常に重要です。これは、スコープに属していないオブジェクトを修正する機能を決定するため、つまり機能に副作用がある場合に機能を決定するためです。これは、関数の結果をどこで利用可能にしなければならないかを理解するためにも重要です。
>>> def list_add3(lin):
lin += [3]
return lin
>>> a = [1, 2, 3]
>>> b = list_add3(a)
>>> b
[1, 2, 3, 3]
>>> a
[1, 2, 3, 3]
ここで間違いは、関数のパラメータとしてのlin
ローカルで変更できると考えることです。代わりに、 lin
および参照同じオブジェクト。 a
このオブジェクトは変更可能であるため、変更はインプレースで行われます。つまり、 lin
とa
両方で参照されるオブジェクトが変更されます。 lin
は実際に返される必要はありません。すでにこのオブジェクトへの参照がaの形でa
です。 a
とb
endは同じオブジェクトを参照します。
これはタプルでは同じではありません。
>>> def tuple_add3(tin):
tin += (3,)
return tin
>>> a = (1, 2, 3)
>>> b = tuple_add3(a)
>>> b
(1, 2, 3, 3)
>>> a
(1, 2, 3)
関数の開始時にtin
およびリファレンス同じオブジェクト。 a
しかし、これは不変のオブジェクトです。関数はそれを修正しようとするときに、 tin
一方で、変更を加えた新しいオブジェクトを受け取り、元のオブジェクトへの参照を保持します。 a
この場合、 tin
返すことは必須であるか、新しいオブジェクトが失われます。
運動
>>> def yoda(prologue, sentence):
sentence.reverse()
prologue += " ".join(sentence)
return prologue
>>> focused = ["You must", "stay focused"]
>>> saying = "Yoda said: "
>>> yoda_sentence = yoda(saying, focused)
注: reverse
動作がインプレースで行われます。
この機能についてどう思いますか?副作用はありますか?リターンは必要ですか?コールの後、何をsaying
ですか? focused
?関数が同じパラメータで再度呼び出されるとどうなりますか?