これはpython(C++とOOPの基礎を知っています)を学ぶ2日目であり、Pythonの変数に関して若干の混乱があります。
現在私がそれらをどのように理解しているかを以下に示します。
Python変数は、オブジェクト(可変または不変)の参照(またはポインター?)です。 num = 5
のようなものがある場合、不変オブジェクト5
がメモリのどこかに作成され、名前とオブジェクトの参照ペアnum
が特定のネームスペースに作成されます。 a = num
があると、何もコピーされませんが、両方の変数が同じオブジェクトを参照し、a
が同じ名前空間に追加されます。
これが私の本Pythonで退屈なものを自動化するが私を混乱させるところです。初心者向けの本なので、オブジェクト、名前空間などについては触れず、次のコードの説明を試みます。
>>> spam = 42
>>> cheese = spam
>>> spam = 100
>>> spam
100
>>> cheese
42
それが提供する説明はC++の本の説明とまったく同じであり、オブジェクトへの参照/ポインターを扱っているので私は満足していません。したがって、この場合、3行目では、整数は不変であるため、spam
にはメモリ内の別の場所へのまったく新しいポインター/参照が割り当てられていると考えられます(つまり、最初に指していたメモリは変更されていません)。したがって、cheese
によって参照される初期オブジェクトを参照するspam
があります。これは正しい説明ですか?
C++開発者は、Python変数をポインターと考えることができます。
したがって、spam = 100
を記述するとき、これは、以前にオブジェクト42
を指していた「ポインターを割り当て」て、オブジェクト100
を指すことを意味します。
以前、cheese
は、spam
が指していたのと同じオブジェクトを指すように割り当てられていましたが、たまたま42
でした。 cheese
を変更していないため、まだ42
を指します。
この場合、不変性はそれとは何の関係もありません。ポインターの割り当ては、ポイントされているオブジェクトについて何も変更しないためです。
私がそれを見る方法には、言語の異なる見解があります。
言語弁護士の観点からは、python変数は常にオブジェクトを「ポイント」します。ただし、JavaやC++とは異なり、== <=> =などの動作は、変数が指すオブジェクトのランタイムタイプに依存します。さらに、pythonでは、メモリ管理は言語によって処理されます。
実用的なプログラマーの観点から、整数、文字列、タプルなどは、まっすぐな値ではなく不変のオブジェクトであるという事実を、無関係な詳細として扱うことができます。例外は、大量の数値データを格納する場合、小さなオブジェクトへの参照でいっぱいになる配列ではなく、値を直接格納できる型(たとえば、numpy配列)を使用することです。
実装者の観点からは、ほとんどの言語には、指定された動作が正しい場合、実際に内部で行われている方法に関係なく実装が正しいという、ある種のas-ifルールがあります。
そうです、あなたの説明は言語弁護士の観点からは正しいです。あなたの本は、実用的なプログラマーの観点から正しいです。実装が実際に行うことは、実装によって異なります。 cpythonでは、整数は実際のオブジェクトですが、小さな値の整数は新しく作成されるのではなくキャッシュプールから取得されます。他の実装(pypyやjythonなど)が何をするのかわかりません。
*可変オブジェクトと不変オブジェクトの違いに注意してください。可変オブジェクトでは、他のコードがそれを変更する可能性があるため、「値のように」扱うことに注意する必要があります。不変オブジェクトでは、このような懸念はありません。
多かれ少なかれ、変数をポインターとして使用できるのは正しいことです。ただし、コード例は、これが実際に機能しているhowの説明に大いに役立ちます。
最初に、 id
関数を多用します。
オブジェクトの「アイデンティティ」を返します。これは、このオブジェクトの存続期間中に一意で一定であることが保証されている整数です。オーバーラップしないライフタイムを持つ2つのオブジェクトは、同じid()値を持つ場合があります。
これは、マシン上で異なる絶対値を返す可能性があります。
この例を考えてみましょう:
>>> foo = 'a string'
>>> id(foo)
4565302640
>>> bar = 'a different string'
>>> id(bar)
4565321816
>>> bar = foo
>>> id(bar) == id(foo)
True
>>> id(bar)
4565302640
あなたはそれを見ることができます:
fooの値を変更すると、別のIDに割り当てられます。
>>> foo = 42
>>> id(foo)
4561661488
>>> foo = 'oh no'
>>> id(foo)
4565257832
興味深い所見として、整数には暗黙的に最大256個のこの機能があります。
>>> a = 100
>>> b = 100
>>> c = 100
>>> id(a) == id(b) == id(c)
True
ただし、256を超えると、これは当てはまりません。
>>> a = 256
>>> b = 256
>>> id(a) == id(b)
True
>>> a = 257
>>> b = 257
>>> id(a) == id(b)
False
ただし、a
をb
に割り当てると、実際にはidが前に示したものと同じになります。
>>> a = b
>>> id(a) == id(b)
True
Pythonは参照渡しでも値渡しでもありません。 Python変数はポインターではなく、参照でも値でもありません。 Python変数は名前です 。
同じフレーズタイプ、または場合によっては「オブジェクトによるパス」が必要な場合は、「エイリアスによるパス」と考えてください。これは、可変であるが、変数(エイリアス)はその1つの変数のみを変更します。
役立つ場合:C変数は、値を書き込むボックスです。 Python名は、値に付けるタグです。
Python変数の名前は、グローバル(またはローカル)名前空間のキーであり、実質的には辞書です。基になる値はメモリ内のオブジェクトです。割り当てにより、そのオブジェクトに名前が付けられます。ある変数を別の変数に割り当てると、両方の変数が同じオブジェクトの名前になります。 1つの変数の再割り当ては、他の変数を変更せずに、その変数によって名前が付けられるオブジェクトを変更します。タグを移動しましたが、前のオブジェクトまたはその上の他のタグは変更していません。
CPython実装の基礎となるCコードでは、すべてのPythonオブジェクトはPyObject*
であるため、データへのポインターのみがある場合(ポインターへのポインターなし、直接渡された値)。
Pythonは値がポインターである値渡しであると言うこともできますし、参照がコピーであるPythonが参照渡しであると言うこともできます。
spam = 100
pythonを実行すると、メモリにもう1つのオブジェクトを作成しますが、既存のオブジェクトは変更しません。したがって、ポインターcheese
は42に、spam
は100にまだあります。
コメントで@DeepSpaceが言及したように、Ned Batchelderはブログの変数(名前)と値への割り当てを分かりやすく説明し、PyCon 2015で講演を行いました Pythonの名前と値に関する事実と神話 。どんなレベルの熟練者であっても、Pythonistaにとって洞察に満ちたものになります。
spam = 100
行で起こっているのは、前の値(値42
を持つint
型のオブジェクトへのポインター)を別のオブジェクトへの別のポインター(int
型、値100
)に置き換えることです
spam = 42
を保存すると、メモリ内にオブジェクトが作成されます。次に、cheese = spam
を割り当てます。spam
によって参照されるオブジェクトをcheese
に割り当てます。最後に、spam = 100
を変更すると、spam
オブジェクトのみが変更されます。 cheese = 42
。