私の知る限り、Pythonの変数は単なるポインタです。
このルールに基づいて、このコード スニペットの結果は次のようになると推測できます。
i = 5
j = i
j = 3
print(i)
でしょう3
。
しかし、私にとって予想外の結果が得られました5
。
さらに、私のPythonの本はこの例をカバーしています:
i = [1,2,3]
j = i
i[0] = 5
print(j)
結果は になります[5,2,3]
。
私は何を間違って理解していますか?
私の知る限り、Pythonの変数は単なるポインタです。
このルールに基づいて、このコード スニペットの結果は次のようになると推測できます。
i = 5
j = i
j = 3
print(i)
でしょう3
。
しかし、私にとって予想外の結果が得られました5
。
さらに、私のPythonの本はこの例をカバーしています:
i = [1,2,3]
j = i
i[0] = 5
print(j)
結果は になります[5,2,3]
。
私は何を間違って理解していますか?
それらを参照と呼びます。彼らはこのように働きます
i = 5 # create int(5) instance, bind it to i
j = i # bind j to the same int as i
j = 3 # create int(3) instance, bind it to j
print i # i still bound to the int(5), j bound to the int(3)
小さな整数はインターンされますが、それはこの説明にとって重要ではありません
i = [1,2,3] # create the list instance, and bind it to i
j = i # bind j to the same list as i
i[0] = 5 # change the first item of i
print j # j is still bound to the same list as i
変数はポインターではありません。変数に代入すると、名前がオブジェクトにバインドされます。その時点から、その名前が再バインドされるまで、その名前を使用してオブジェクトを参照できます。
最初の例では、 namei
は value にバインドされています5
。名前に異なる値をバインドj
しても には影響しないi
ため、後で出力するとき、値i
の値はそのまま5
です。
i
2 番目の例では、 と の両方を同じリスト オブジェクトj
にバインドします。リストの内容を変更すると、リストを参照するために使用する名前に関係なく、変更を確認できます。
「両方のリストが変更された」と言った場合、それは正しくないことに注意してください。リストは 1 つしかありませんが、それを参照する 2 つの名前 (i
とj
) があります。
関連ドキュメント
TLDR: Python名は、自動逆参照機能を備えたポインターのように機能しますが、明示的なポインター操作は許可されません。他のターゲットは、ポインタと同様に動作する間接参照を表します。
Python 言語仕様では、実際の名前などは定義されておらず、その動作のみが定義されています。ただし、動作はポインタで説明できます。
CPython 実装は内部で型のポインターをPyObject*
使用します。そのため、名前のセマンティクスをポインター操作に変換することができます。重要なのは、実際のオブジェクトから名前を分離することです。
Python コードの例には、名前 ( i
) とオブジェクト ( 5
) の両方が含まれています。
i = 5 # name `i` refers to object `5`
j = i # ???
j = 3 # name `j` refers to object `3`
これは、別の名前とオブジェクトを使用して大まかに C コードに変換できます。
int three=3, five=5; // objects
int *i, *j; // names
i = &five; // name `i` refers to position of object `5`
j = i; // name `j` refers to referent of `i`
j = &three; // name `j` refers to position of object `3`
重要な部分は、「names-as-pointers」はオブジェクトを保存しないということです! を定義しませんでし*i = five
たが、i = &five
. 名前とオブジェクトは互いに独立して存在します。
名前は、メモリ内の既存のオブジェクトのみを指します。
名前から名前に割り当てる場合、オブジェクトは交換されません! を定義するj = i
と、これは と同等j = &five
です。どちらi
もj
他に接続されていません。
+- name i -+ -\
\
--> + <five> -+
/ | 5 |
+- name j -+ -/ +----------+
その結果、一方の名前のターゲットを変更しても、もう一方の名前には影響しません。その特定の名前が指すものだけを更新します。
Python には、他の種類の名前のような要素もあります: 属性参照 ( i.j
)、サブスクリプション ( i[j]
)、およびスライス ( i[:j]
)。オブジェクトを直接参照する名前とは異なり、3 つすべてがオブジェクトの要素を間接的に参照します。
サンプル コードには、名前 ( i
) とサブスクリプション ( i[0]
) の両方が含まれています。
i = [1,2,3] # name `i` refers to object `[1, 2, 3]`
j = i # name `j` refers to referent of `i`
i[0] = 5 # ???
CPythonは内部でポインタlist
の C 配列を使用します。PyObject*
これは、別の名前とオブジェクトを使用して、大まかに C コードに変換できます。
typedef struct{
int *elements[3];
} list; // length 3 `list` type
int one = 1, two = 2, three = 3, five = 5;
list values = {&one, &two, &three}; // objects
list *i, *j; // names
i = &values; // name `i` refers to object `[1, 2, 3]`
j = i; // name `j` refers to referent of `i`
i->elements[0] = &five; // leading element of `i` refers to object `5`
重要な部分は、名前を変更していないことです。i->elements[0]
両方の名前が指すオブジェクトの要素である を変更しました。
既存の複合オブジェクトの値が変更される場合があります。
名前によってオブジェクトの値を変更しても、名前は変更されません。i
との両方j
が同じオブジェクトを参照しており、その値を変更できます。
+- name i -+ -\
\
--> + <values> -+
/ | elements | --> [1, 2, 3]
+- name j -+ -/ +-----------+
中間オブジェクトは、それが指すものを直接変更し、複数の名前から参照できるという点で、ポインターと同様に動作します。
それらはポインターではありません。それらはオブジェクトへの参照です。オブジェクトは可変または不変のいずれかです。不変オブジェクトは、変更されるとコピーされます。変更可能なオブジェクトはインプレースで変更されます。整数は不変オブジェクトであり、i および j 変数によって参照されます。リストは変更可能なオブジェクトです。
あなたの最初の例では
i = 5
# The label i now references 5
j = i
# The label j now references what i references
j = 3
# The label j now references 3
print i
# i still references 5
あなたの2番目の例では:
i = [1, 2, 3]
# 'i' references a list object (a mutable object)
j = i
# 'j' now references the same object as 'i' (they reference the same mutable object)
i[0] = 5
# Sets first element of references object to 5
print j
# Prints the list object that 'j' references. It's the same one as 'i'.
代入はオブジェクトを変更しません。変数が指す場所を変更するだけです。ある変数が指す場所を変更しても、別の変数が指す場所は変わりません。
おそらく、リストと辞書が変更可能な型であるという事実を考えているでしょう。実際のオブジェクトをその場で変更する演算子があり、それらのいずれかを使用すると、同じオブジェクトを指すすべての変数が変更されます。
x = []
y = x
x.append(1)
# x and y both are now [1]
しかし、代入はポインタを移動するだけです:
x = [2]
# x now points to new list [2]; y still points to old list [1]
辞書やリストとは異なり、数字は不変です。もしそうならx = 3; x += 2
、数字の 3 を数字の 5 に変換していません。x
代わりに、変数が 5 を指しているだけです。3 はまだ変更されておらず、それを指す変数は引き続き 3 を値として認識します。
(実際の実装では、数値はおそらくまったく参照型ではありません。変数には、値を指すのではなく、実際には値の表現が直接含まれている可能性が高くなります。しかし、その実装の詳細は、不変型が関係するセマンティクスを変更しません。 .)
j=3
ラベルj
が適用されなくなった (ポイントしない)を設定i
すると、整数 をポイントし始め3
ます。この名前i
は、最初に設定した値を参照しています5
。
Python では、返されるメモリ ピース自体を含むすべてがオブジェクトです。つまり、新しいメモリ チャンクが作成されると (作成したものに関係なく: int、str、カスタム オブジェクトなど)、新しいメモリ オブジェクトが作成されます。あなたの場合、これは新しい(メモリ)オブジェクトを作成し、新しいアドレスを持つ3への割り当てです。
以下を実行すると、私の言いたいことが簡単にわかります。
i = 5
j = i
print("id of j: {}", id(j))
j = 3
print("id of j: {}", id(j))
IMO、メモリに関しては、これがCとPythonの重要な理解/違いです。C/C++ では、メモリ オブジェクトの代わりにメモリ ポインタ (もちろんポインタ構文を使用する場合) が返されるため、参照アドレスの変更に関してより柔軟に対応できます。