Pythonの変数は、私が知る限り、単なるポインターです。
このルールに基づいて、このコードスニペットの結果は次のようになります。
i = 5
j = i
j = 3
print(i)
だろう 3
。しかし、私は予期せぬ結果を得ました。それは5
。
さらに、my 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
変数はポインターではありません。変数に割り当てると、オブジェクトの名前がbindingになります。その時点から、名前が再バインドされるまで、名前を使用してオブジェクトを参照できます。
最初の例では、名前i
は値_5
_にバインドされています。名前j
に異なる値をバインドしても、i
には影響しません。したがって、後でi
の値を印刷しても、値はまだ_5
_です。
2番目の例では、i
とj
の両方をsameリストオブジェクトにバインドします。リストの内容を変更すると、リストの参照に使用する名前に関係なく、変更を確認できます。
「両方のリストが変更された」と言った場合は正しくないことに注意してください。リストは1つだけですが、それを参照する2つの名前(i
とj
)があります。
関連ドキュメント
docs から:
名前はオブジェクトを指します。名前は、名前バインディング操作によって導入されます。 プログラムテキスト内の名前の各オカレンスは、使用を含む最も内側の機能ブロックで確立されたその名前のバインディングを参照します。
するとき
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リストは可変オブジェクトです。そのため、ある参照からリストを変更し、別の参照からリストを見ると、同じリストであるため同じ結果が表示されます。
それらは完全なポインタではなく、オブジェクトへの参照です。オブジェクトは、可変または不変のいずれかです。不変オブジェクトは、変更されるとコピーされます。可変オブジェクトはインプレースで変更されます。整数は不変オブジェクトであり、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.
TLDR:Python names自動デ/参照機能を備えたポインターのように機能しますが、明示的なポインター操作は許可されません。他のターゲットは、ポインターと同様に動作するインダイレクションを表します。
CPython実装では、内部で _PyObject*
_型のポインター を使用します。そのため、名前のセマンティクスをポインター操作に変換することができます。重要なのは、実際のobjectsからnamesを分離することです。
例Pythonコードには、名前(i
)とオブジェクト(_5
_)の両方が含まれます。
_i = 5 # name `i` refers to object `5`
j = i # ???
j = 3 # name `j` refers to object `3`
_
これは、separateの名前とオブジェクトを使用して、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
_を定義しました。名前とオブジェクトは互いに独立して存在します。
名前のみpoint toメモリ内の既存のオブジェクト。
名前から名前に割り当てるとき、オブジェクトは交換されません! _j = i
_を定義するとき、これは_j = &five
_と同等です。 i
もj
も他方に接続されていません。
_+- name i -+ -\
\
--> + <five> -+
/ | 5 |
+- name j -+ -/ +----------+
_
その結果、 一方の名前のターゲットを変更しても他方には影響しません 。特定の名前が指すもののみを更新します。
Pythonには 他の種類の名前のような要素 :属性参照(_i.j
_)、サブスクリプション(_i[j]
_)、およびスライシング(_i[:j]
_)もあります。オブジェクトを直接参照する名前とは異なり、3つすべては間接的にelements ofオブジェクトを参照します。
サンプルコードには、名前(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
は、内部で_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]
_を変更しました。
既存の複合オブジェクトの値は変更される場合があります。
オブジェクトの値を変更する場合through name、名前は変更されません。 i
とj
の両方は、同じオブジェクトを参照していますが、その値は変更できます。
_+- name i -+ -\
\
--> + <values> -+
/ | elements | --> [1, 2, 3]
+- name j -+ -/ +-----------+
_
中間オブジェクトbehavesポインターに似ており、それが指すものを直接変更し、複数の名前から参照することができます。
「=」記号の左側にある変数には、「=」の右側にある値が割り当てられます
_i = 5
_
_j = i
_ --- jは5
_j = 3
_ --- jは3(5の値を上書きします)ですが、iに関しては何も変更されていません
print(i)
--これにより5
j=3
を設定すると、ラベルj
はi
に適用(ポイント)されなくなり、整数3
をポイントし始めます。名前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++では、メモリオブジェクトの代わりにメモリポインタが返されます(もちろん、ポインタ構文を使用する場合)。これにより、参照先アドレスの変更に関して柔軟性が高まります。