web-dev-qa-db-ja.com

Pythonで1つの変数の値を別の変数に割り当てるとどうなりますか?

これは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があります。これは正しい説明ですか?

73
Ruslan Mushkaev

C++開発者は、Python変数をポインターと考えることができます。

したがって、spam = 100を記述するとき、これは、以前にオブジェクト42を指していた「ポインターを割り当て」て、オブジェクト100を指すことを意味します。

以前、cheeseは、spamが指していたのと同じオブジェクトを指すように割り当てられていましたが、たまたま42でした。 cheeseを変更していないため、まだ42を指します。

この場合、不変性はそれとは何の関係もありません。ポインターの割り当ては、ポイントされているオブジェクトについて何も変更しないためです。

79
Jonas Adler

私がそれを見る方法には、言語の異なる見解があります。

  • 「言語弁護士」の視点。
  • 「実践的なプログラマー」の視点。
  • 「実装者」パースペクティブ。

言語弁護士の観点からは、python変数は常にオブジェクトを「ポイント」します。ただし、JavaやC++とは異なり、== <=> =などの動作は、変数が指すオブジェクトのランタイムタイプに依存します。さらに、pythonでは、メモリ管理は言語によって処理されます。

実用的なプログラマーの観点から、整数、文字列、タプルなどは、まっすぐな値ではなく不変のオブジェクトであるという事実を、無関係な詳細として扱うことができます。例外は、大量の数値データを格納する場合、小さなオブジェクトへの参照でいっぱいになる配列ではなく、値を直接格納できる型(たとえば、numpy配列)を使用することです。

実装者の観点からは、ほとんどの言語には、指定された動作が正しい場合、実際に内部で行われている方法に関係なく実装が正しいという、ある種のas-ifルールがあります。

そうです、あなたの説明は言語弁護士の観点からは正しいです。あなたの本は、実用的なプログラマーの観点から正しいです。実装が実際に行うことは、実装によって異なります。 cpythonでは、整数は実際のオブジェクトですが、小さな値の整数は新しく作成されるのではなくキャッシュプールから取得されます。他の実装(pypyやjythonなど)が何をするのかわかりません。

*可変オブジェクトと不変オブジェクトの違いに注意してください。可変オブジェクトでは、他のコードがそれを変更する可能性があるため、「値のように」扱うことに注意する必要があります。不変オブジェクトでは、このような懸念はありません。

20
plugwash

多かれ少なかれ、変数をポインターとして使用できるのは正しいことです。ただし、コード例は、これが実際に機能している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/barは異なるオブジェクトを指すため、異なるIDを持ちます
  • Barがfooに割り当てられると、それらのIDは同じになります。これは、どちらもC++ポインターを作成するときに表示されるメモリ内の同じ場所を指すことに似ています。

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

ただし、abに割り当てると、実際にはidが前に示したものと同じになります。

>>> a = b
>>> id(a) == id(b)
True
19
enderland

Pythonは参照渡しでも値渡しでもありません。 Python変数はポインターではなく、参照でも値でもありません。 Python変数は名前です

同じフレーズタイプ、または場合によっては「オブジェクトによるパス」が必要な場合は、「エイリアスによるパス」と考えてください。これは、可変であるが、変数(エイリアス)はその1つの変数のみを変更します。

役立つ場合:C変数は、値を書き込むボックスです。 Python名は、値に付けるタグです。

Python変数の名前は、グローバル(またはローカル)名前空間のキーであり、実質的には辞書です。基になる値はメモリ内のオブジェクトです。割り当てにより、そのオブジェクトに名前が付けられます。ある変数を別の変数に割り当てると、両方の変数が同じオブジェクトの名前になります。 1つの変数の再割り当ては、他の変数を変更せずに、その変数によって名前が付けられるオブジェクトを変更します。タグを移動しましたが、前のオブジェクトまたはその上の他のタグは変更していません。

CPython実装の基礎となるCコードでは、すべてのPythonオブジェクトはPyObject*であるため、データへのポインターのみがある場合(ポインターへのポインターなし、直接渡された値)。

Pythonは値がポインターである値渡しであると言うこともできますし、参照がコピーであるPythonが参照渡しであると言うこともできます。

16
David Heyman

spam = 100 pythonを実行すると、メモリにもう1つのオブジェクトを作成しますが、既存のオブジェクトは変更しません。したがって、ポインターcheeseは42に、spamは100にまだあります。

10

コメントで@DeepSpaceが言及したように、Ned Batchelderはブログの変数(名前)と値への割り当てを分かりやすく説明し、PyCon 2015で講演を行いました Pythonの名前と値に関する事実と神話 。どんなレベルの熟練者であっても、Pythonistaにとって洞察に満ちたものになります。

8
pylang

spam = 100行で起こっているのは、前の値(値42を持つint型のオブジェクトへのポインター)を別のオブジェクトへの別のポインター(int型、値100)に置き換えることです

8
bakatrouble

spam = 42を保存すると、メモリ内にオブジェクトが作成されます。次に、cheese = spamを割り当てます。spamによって参照されるオブジェクトをcheeseに割り当てます。最後に、spam = 100を変更すると、spamオブジェクトのみが変更されます。 cheese = 42

1