私の知る限り、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
べきかわからない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)
小さなintはインターンされていますが、それはこの説明にとって重要ではありません
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
40
、メモリ内の同じオブジェクトを参照していることになります。このタイプを確認するには、a、b = 256とテストしa is b
ます。ここで、a、b = 257で試してください。参照: stackoverflow.com/a/1136852/3973834 および codementor.io/python/tutorial/…–
変数はポインターではありません。変数に割り当てるときは、名前をオブジェクトにバインドします。その時点から、その名前がリバウンドされるまで、名前を使用してオブジェクトを参照できます。
最初の例では、名前i
は値にバインドされています5
。異なる値を名前にバインドしj
てもに影響はないi
ため、後で印刷するときに値のi
値は引き続き5
です。
2番目の例では、i
とj
を同じリストオブジェクトにバインドします。リストの内容を変更すると、リストの参照に使用する名前に関係なく、変更を確認できます。
「両方のリストが変更されました」と言った場合は正しくないことに注意してください。リストは1つだけですが、それを参照する2つの名前(i
とj
)があります。
関連ドキュメント
ドキュメントから:
名前はオブジェクトを指します。名前は、名前バインディング操作によって導入されます。プログラムテキスト内の名前の各出現は、使用を含む最も内側の機能ブロックで確立されたその名前のバインディングを参照します。
あなたがするとき
i = 5
j = i
それは行うことと同じです:
i = 5
j = 5
j
を指しておらずi
、割り当て後、j
それi
が存在することを知りません。 割り当て時に指しj
ていたものi
にバインドされているだけです。
同じ行で割り当てを行った場合、次のようになります。
i = j = 5
そして、結果はまったく同じになります。
したがって、後で行う
i = 3
どのような変更されませんj
を指している-あなたはそれを交換することができます-j = 3
どのような変更ではないでしょうi
を指しています。
したがって、これを行うと:
i = [1,2,3]
j = i
これを行うのと同じです:
i = j = [1,2,3]
したがってi
、j
両方が同じリストを指します。次に、例でリストを変更します。
i[0] = 5
Pythonリストは変更可能なオブジェクトであるため、ある参照からリストを変更し、別の参照からリストを見ると、同じリストであるため、同じ結果が表示されます。
TLDR: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`
重要なのは、「ポインタとしての名前」はオブジェクトを格納しないということです。定義しませんでした*i = five
が、i = &five
。名前とオブジェクトは互いに独立して存在します。
名前は、メモリ内の既存のオブジェクトのみを指します。
名前から名前に割り当てるとき、オブジェクトは交換されません!を定義するときj = i
、これはと同等j = &five
です。どちらi
もj
他に接続されていません。
+- name i -+ -\
\
--> + <five> -+
/ | 5 |
+- name j -+ -/ +----------+
その結果、一方の名前のターゲットを変更しても、もう一方の名前には影響しません。その特定の名前が指しているものだけを更新します。
Pythonには、属性参照()、サブスクリプション()、スライス()など、他の種類の名前のような要素もあります。オブジェクトを直接参照する名前とは異なり、3つすべてがオブジェクトの要素を間接的に参照します。i.j
i[j]
i[:j]
サンプルコードには、名前(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 # ???
CPythonlist
は、内部でPyObject*
ポインターのC配列を使用します。これも、名前とオブジェクトが別々の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
、j = 3
投稿の残りの部分でそれらを反転します。繰り返しになりますが、これはOPの質問に正義を示し、内部で何が起こっているかを実際に説明する唯一の回答imoです。
それらは完全なポインタではなく、オブジェクトへの参照です。オブジェクトは、変更可能または不変のいずれかです。不変オブジェクトは、変更されるとコピーされます。可変オブジェクトはインプレースで変更されます。整数は不変のオブジェクトであり、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.
j=3
ラベルj
が適用されなくなる(ポイントする)ようi
に設定すると、整数をポイントし始め3
ます。名前i
は、最初に設定した値を引き続き参照しています5
。
'='記号の左側にある変数には、 '='の右側の値が割り当てられます。
i = 5
j = i
--- jには5があります
j = 3
--- jは3(5の値を上書き)ですが、iに関しては何も変更されていません
print(i)
-つまり、これは5を出力します
割り当てはオブジェクトを変更しません。変数が指す場所を変更するだけです。ある変数が指す場所を変更しても、別の変数が指す場所は変わりません。
あなたはおそらくリストと辞書が可変型であるという事実を考えているでしょう。実際のオブジェクトをインプレースで変更する演算子があり、それらの1つを使用すると、同じオブジェクトを指すすべての変数の変更が表示されます。
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を値として認識します。
(実際の実装では、数値はおそらく参照型ではありません。変数には、値を指すのではなく、直接値の表現が含まれている可能性が高くなります。ただし、実装の詳細によって、不変型が関係するセマンティクスは変更されません。 。)
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 ++では、メモリオブジェクトの代わりにメモリポインタが返されます(もちろんポインタ構文を使用する場合)。これにより、参照されるアドレスの変更に関してより柔軟になります。