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


80

これはPythonを学ぶ2日目です(C ++といくつかのOOPの基本を知っています)。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ます。これは正しい説明ですか?



5
あなたはに保存し42ましたspam、今あなたspamはチーズに保存したという意味ですcheese = 42、その後あなたはに置き換えましspam100、あなたは編集cheeseしなかったので、それcheeseはまだ42です。

15
Jonasが彼の答えで指摘しているように、不変性はここでは赤いニシンです。リストのような可変オブジェクトを使用した場合でも、まったく同じ結果が得られます。重要なのは、変数を新しい参照に再割り当てすることです。
ダニエルローズマン2017


27
@mini:Python変数をバケットではなくラベルと考える方が好きです。ラベルspamを番号42に貼り付けました。次に、ラベルをcheese付けたものにラベルを貼り付けましたspam(ラベル自体の上ではなく、気を付けてください)。そして、あなたは皮をむいspamているのオフラベルをし、数100の上に置いた
ティムPederick

回答:


81

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

あなたが書いたときにこのようにspam = 100、あなたが以前にオブジェクトを指していた、「ポインタを割り当てる」というこの手段は42、オブジェクトを指すように100

以前は、cheeseポイントされたのと同じオブジェクトをポイントするように割り当てられてspamいましたが、それはたまたま42その時点でした。を変更していないのでcheese、それでもを指し42ます。

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


2
jsのオブジェクトと同じ
Dushyant Bangal 2017

7
不変性は、参照を値であるかのように安全に扱うことができることを意味するため、重要です。可変オブジェクトを値であるかのように扱う方がリスクが高くなります。
プラグウォッシュ2017

21

私の見方では、言語のさまざまな見方があります。

  • 「言語弁護士」の視点。
  • 「実用的なプログラマー」の視点。
  • 「実装者」の視点。

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

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

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

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

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


20

多かれ少なかれ変数をポインタとして使用できるのは正しいことです。ただし、サンプルコードはこれが実際にどのように機能しているかを説明するのに大いに役立ちます。

まず、次の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

18

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

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

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

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

CPython実装の基礎となるCコードでは、すべてのPythonオブジェクトがであるPyObject*ため、データへのポインター(ポインターへのポインターや直接渡される値がない)しかない場合は、Cのように機能すると考えることができます。

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


1
これを「名前による呼び出し」と呼ぶ場合の問題は、「名前による呼び出し」と呼ばれる、まったく異なる意味を持つパラメーター受け渡し規則がすでに存在することです。名前による呼び出しでは、パラメーター式は関数がパラメーターを使用するたびに評価され、関数がパラメーターを使用しない場合は評価されません。
user2357112は、2017

11

spam = 100Pythonを実行するときは、メモリ内にもう1つのオブジェクトを作成しますが、既存のオブジェクトは変更しません。だからあなたはまだcheese42とspam100へのポインタを持っています


8

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


整数はスタック上にある値オブジェクトですよね?
Gert Kommer 2017

はい、new Class()C ++の構文を使用して作成するオブジェクトのようなものです。さらに、Pythonでは、すべてobjectクラス/サブクラスのインスタンスです。
bakatrouble

4
少なくともCPythonの@GertKommerでは、すべてのオブジェクトがヒープ上に存在します。「値オブジェクト」の区別はありません。オブジェクトだけがあり、すべてがオブジェクトです。これが、Py_Objectのオーバーヘッド全体があるため、Pythonのバージョンにもよりますが、一般的なintのサイズが約28バイトである理由です。small-intは、CPython最適化としてキャッシュされます。
juanpa.arrivillaga 2017

8

@DeepSpaceがコメントで述べたように、Ned Batchelderは、ブログで変数(名前)と値への割り当てをわかりやすく説明し、そこからPyCon 2015、Pythonの名前と値に関する事実と神話について講演しました。それは、あらゆるレベルの習熟度のPythonistaにとって洞察に満ちたものになる可能性があります。


1

Pythonでは、変数オブジェクトへの参照を保持しますオブジェクトは、値と保持する割り当てられたメモリのチャンクであるヘッダ。オブジェクトのヘッダーには、そのタイプと、このオブジェクトがソースコードで参照される回数を示す参照カウンターが含まれているため、ガベージコレクションはオブジェクトを収集できるかどうかを識別できます。

これで、変数に値を割り当てると、Pythonは実際には、オブジェクトに割り当てられたメモリ位置へのポインタである参照を割り当てます。

# x holds a reference to the memory location allocated for  
# the object(type=string, value="Hello World", refCounter=1)

x = "Hello World" 

これで、異なるタイプのオブジェクトを同じ変数に割り当てるときに、実際には参照を変更して、異なるオブジェクト(つまり、異なるメモリ位置)を指すようにします。変数に別の参照(したがってオブジェクト)を割り当てるまでに、ガベージコレクターは、ソースコード内の他の変数によって参照されていないと想定して、前のオブジェクトに割り当てられたスペースをすぐに再利用します。

# x holds a reference to the memory location allocated for  
# the object(type=string, value="Hello World", refCounter=1)

x = "Hello World" 

# Now x holds the reference to a different object(type=int, value=10, refCounter=1)
# and object(type=string, value="Hello World", refCounter=0) -which is not refereced elsewhere
# will now be garbage-collected.
x = 10

今あなたの例に来て、

spam object(type = int、value = 42、refCounter = 1)への参照を保持します:

>>> spam = 42

これでcheese、object(type = int、value = 42、refCounter = 2)への参照も保持されます。

>>> cheese = spam

これで、スパムは別のオブジェクトへの参照を保持します(type = int、value = 100、refCounter = 1)

>>> spam = 100
>>> spam
100

しかし、チーズはオブジェクトを指し続けます(type = int、value = 42、refCounter = 1)

>>> cheese
42

0

を格納spam = 42すると、メモリ内にオブジェクトが作成されます。そして、あなたが割り当てたcheese = spam、それはによって参照されるオブジェクトに割り当てるspamにはcheese。そして最後に、変更するとspam = 100spamオブジェクトのみが変更されます。だからcheese = 42


7
「次に、cheese = spamを割り当てると、メモリ内に別のオブジェクトが作成されます」いいえ、そうではありません。によって参照さspamれるオブジェクトをに割り当てますcheese。新しいオブジェクトは作成されません。
juanpa.arrivillaga 2017

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.