これの背後にあるロジックが何であるか疑問に思っていますか?表面的には、「x = x + 1」のような単純なことを行うたびに、新しいアドレスを取得して古いアドレスを破棄する必要があるというのは、一種の非効率的なようです。
5 に答える
Python変数(Pythonでは識別子または名前と呼ばれます)は値への参照です。関数は、id()
名前ではなく、その値に対して何かを言います。
多くの値は変更できません。整数、文字列、浮動小数点数はすべてその場で変更されません。1
別の整数に追加すると、新しい整数が返され、古い値への参照が置き換えられます。
Pythonの名前は、値に関連付けられたラベルとして見ることができます。値をバルーンとして想像する場合、その名前に割り当てるたびに、ラベルを新しいバルーンに再結合します。気球に他のラベルが貼られていない場合、気球は風に流されて、二度と見られなくなります。このid()
関数は、そのバルーンに固有の番号を提供します。
風船としての価値観についてもう少し話している私の以前の回答を参照してください。
これは非効率に見えるかもしれません。多くの頻繁に使用される小さな値の場合、Pythonは実際にはインターンと呼ばれるプロセスを使用し、再利用のためにこれらの値のスタッシュをキャッシュします。None
はそのような値であり、小さな整数や空のタプル(()
)も同様です。このintern()
関数を使用して、頻繁に使用する予定の文字列で同じことを行うことができます。
ただし、値は、参照カウント(「ラベル」の数)が0に低下した場合にのみクリーンアップされることに注意してください。値のロードは、特にインターンされた整数とシングルトンで、常にあらゆる場所で再利用されます。
基本型は不変であるため、変更するたびに、再度インスタンス化する必要があります
...これは、特にスレッドセーフ機能の場合はまったく問題ありません。
=
オペレーターはオブジェクトを変更せず、完全に異なるオブジェクトに名前を割り当てます。オブジェクトにはIDが付いている場合と付いていない場合があります。
あなたの例では、整数は不変です。1つに何かを追加して、同じIDを維持する方法はありません。
そして、実際には、小さな整数は少なくともcPythonでインターンされているので、次のようにします。
x = 1
y = 2
x = x + 1
次にx
、y
同じIDを持つ可能性があります。
Python では、int や文字列などの「プリミティブ」型は不変です。つまり、変更することはできません。
@Woobleがコメントしたように、Pythonは実際には非常に効率的です。 «非常に短い文字列と小さな整数がインターンされます。»: 2 つの変数が同じ ( small ) 不変値を参照する場合、それらの ID は同じです (重複する不変値を減らします)。
>>> a = 42
>>> b = 5
>>> id(a) == id(b)
False
>>> b += 37
>>> id(a) == id(b)
True
不変型を使用する背後にある理由は、これらの値への同時アクセスに対する安全なアプローチです。
結局のところ、それはデザインの選択に依存します。
必要に応じて、別の実装ではなく実装をより活用できます。
たとえば、Python では不変である型が不変である、多少似た言語である Ruby では、別の哲学が見られます。
正確に言うと、代入は参照x=x+1
しているオブジェクトを変更しません。x
値が である別のオブジェクトを x が指すようにするだけですx+1
。
背後にあるロジックを理解するには、値セマンティクスと参照セマンティクスの違いを理解する必要があります。
値セマンティクスを持つオブジェクトは、その ID ではなく、その値のみが重要であることを意味します。参照セマンティクスを持つオブジェクトは、その ID に焦点を当てています (Python では、ID は から返されid(obj)
ます)。
通常、値のセマンティクスは、オブジェクトの不変性を意味します。逆に、オブジェクトが変更可能 (つまり、インプレース変更) である場合、それは参照セマンティクスを持っていることを意味します。
この不変性の背後にある理論的根拠を簡単に説明しましょう。
参照セマンティクスを持つオブジェクトは、元のアドレス/ID を失うことなく、その場で変更できます。これは、それ自体を他のオブジェクトと区別できるようにする参照セマンティクスを持つオブジェクトのアイデンティティであるという点で理にかなっています。
対照的に、値セマンティクスを持つオブジェクトは、それ自体を変更するべきではありません。
まず、これは理論的には可能で合理的です。値 (ID ではなく) のみが重要であるため、変更が必要な場合は、別の値を持つ別の ID に安全に交換できます。これは参照透過性と呼ばれます。これは、参照セマンティクスを持つオブジェクトでは不可能であることに注意してください。
第二に、これは実際には有益です。OPが考えたように、古いオブジェクトが変更されるたびに破棄するのは非効率的ですが、ほとんどの場合、そうでない場合よりも効率的です。1 つには、Python (またはその他の言語) には、作成するオブジェクトを少なくするためのインターン/キャッシュ スキームがあります。さらに、値セマンティクスのオブジェクトが変更可能になるように設計されている場合、ほとんどの場合、より多くのスペースが必要になります。
たとえば、Date には値のセマンティクスがあります。変更可能に設計されている場合、内部フィールドから日付を返すメソッドはハンドルを外部に公開するため、危険です (たとえば、外部はパブリック インターフェイスに頼らずにこの内部フィールドを直接変更できます)。同様に、何らかの関数/メソッドへの参照によって日付オブジェクトを渡す場合、このオブジェクトはその関数/メソッドで変更される可能性があり、これは期待どおりではない可能性があります。この種の副作用を回避するには、防御的プログラミングを行う必要があります: 内部の日付フィールドを直接返す代わりに、そのクローンを返します。参照渡しではなく、値渡しを行っています。これは、追加のコピーが作成されることを意味します。ご想像のとおり、必要以上に多くのオブジェクトを作成する可能性が高くなります。さらに悪いことに、これらの追加のクローン作成により、コードがより複雑になります。
一言で言えば、不変性は値のセマンティクスを強制します。通常、オブジェクトの作成が少なくなり、副作用や面倒が少なくなり、テストしやすくなります。さらに、不変オブジェクトは本質的にスレッドセーフであるため、ロックが少なくなり、マルチスレッド環境での効率が向上します。
これが、数値、文字列、日付、時刻などの値セマンティクスの基本的なデータ型がすべて不変である理由です (まあ、C++ の文字列は例外です。そのconst string&
ため、文字列が予期せず変更されるのを避けるためのものが非常に多くあります)。Date
教訓として、Java は値セマンティック クラス, Point
,Rectangle
をDimension
ミュータブルとして設計する際に間違いを犯しました。
ご存知のように、OOP のオブジェクトには、状態、動作、およびアイデンティティの 3 つの特性があります。値セマンティクスを持つオブジェクトは、ID がまったく問題にならないという点で、典型的なオブジェクトではありません。通常、それらは受動的であり、他の実際の能動的オブジェクト (つまり、参照セマンティクスを持つもの)を記述するために主に使用されます。これは、値のセマンティクスと参照のセマンティクスを区別するための良いヒントです。