Python変数はポインタですか?それとも彼らは何ですか?


82

私の知る限り、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]

私は何を間違って理解していますか?



5
どうあるiべきかわからない3
Tooniis 2018年

@Tooniis jは、私が指しているものとは別のメモリブロックを指すようになりました。あなたはおそらく今までに理解しているでしょうが、誰かがこれを見た場合に備えて
SwaroopJoshi20年

回答:


89

それらを参照と呼びます。彼らはこのように働きます

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

3
こんにちはジョン、「小さなintがインターンされている」とはどういう意味ですか?ありがとう!
yuqli

6
@yuqli Pythonでは、数字を含め、すべてがオブジェクトです。小さい数(-5,256)が頻繁に使用されるため、CPythonに「インターン」またはキャッシュされます。したがって、入力するたびに40、メモリ内の同じオブジェクトを参照していることになります。このタイプを確認するには、a、b = 256とテストしa is bます。ここで、a、b = 257で試してください。参照: stackoverflow.com/a/1136852/3973834 および codementor.io/python/tutorial/…–
Evan

3
私の経験では、それらを「名前」と呼ぶことは、Python開発者の間でより一般的です。「参照」という用語には不要なCの手荷物が含まれており、おそらくPython(言語)をCPython(実装)に偏らせすぎています。CPython(実装)はたまたま参照カウントを使用しています。
WIM

33

変数はポインターではありません。変数に割り当てるときは、名前をオブジェクトにバインドします。その時点から、その名前がリバウンドされるまで、名前を使用してオブジェクトを参照できます。

最初の例では、名前iは値にバインドされています5。異なる値を名前にバインドしjてもに影響はないiため、後で印刷するときに値のi値は引き続き5です。

2番目の例では、ij同じリストオブジェクトにバインドします。リストの内容を変更すると、リストの参照に使用する名前に関係なく、変更を確認できます。

「両方のリストが変更されました」と言った場合は正しくないことに注意してください。リストは1つだけですが、それを参照する2つの名前(ij)があります。

関連ドキュメント


15

Python変数はオブジェクトにバインドされた名前です

ドキュメントから:

名前はオブジェクトを指します。名前は、名前バインディング操作によって導入されます。プログラムテキスト内の名前の各出現は、使用を含む最も内側の機能ブロックで確立されたその名前のバインディングを参照します。

あなたがするとき

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]

したがってij両方が同じリストを指します。次に、例でリストを変更します。

i[0] = 5

Pythonリストは変更可能なオブジェクトであるため、ある参照からリストを変更し、別の参照からリストを見ると、同じリストであるため、同じ結果が表示されます。


9

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です。どちらij他に接続されていません。

+- name i -+ -\
               \
                --> + <five> -+
               /    |        5 |
+- name j -+ -/     +----------+

その結果、一方の名前のターゲットを変更しても、もう一方の名前には影響しません。その特定の名前が指しているものだけを更新します。


Pythonには、属性参照()、サブスクリプション()、スライス()など、他の種類の名前のような要素もあります。オブジェクトを直接参照する名前とは異なり、3つすべてオブジェクトの要素を間接的に参照します。i.ji[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]両方の名前が指すオブジェクトの要素を変更しました。

既存の複合オブジェクトの値は変更される可能性があります。

オブジェクトの値を変更する場合によって名前、名前が変更されません。両方ij、まだ値が、我々は変更することができ、同じオブジェクトを参照してください。

+- name i -+ -\
               \
                --> + <values> -+
               /    |  elements | --> [1, 2, 3]
+- name j -+ -/     +-----------+

中間オブジェクト、それが指すものを直接変更し、複数の名前から参照できるという点で、ポインターと同様に動作します。


1
私はこの答えが本当に好きですが、あなたの例ではiとのj割り当てを逆にしたと思います。から始めてi = 5j = 3投稿の残りの部分でそれらを反転します。繰り返しになりますが、これはOPの質問に正義を示し、内部で何が起こっているかを実際に説明する唯一の回答imoです。
jeremyradcliff19年

1
@jeremyradcliff頭を上げてくれてありがとう。今すぐ修正する必要があります。もう少し見逃した場合はお知らせください。
MisterMiyagi

7

それらは完全なポインタではなく、オブジェクトへの参照です。オブジェクトは、変更可能または不変のいずれかです。不変オブジェクトは、変更されるとコピーされます。可変オブジェクトはインプレースで変更されます。整数は不変のオブジェクトであり、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.

「不変オブジェクトは、変更されるとコピーされます。」それは少し自己矛盾です。
PM2Ring19年

1

j=3ラベルjが適用されなくなる(ポイントする)ようiに設定すると、整数をポイントし始め3ます。名前iは、最初に設定した値を引き続き参照しています5


1

'='記号の左側にある変数には、 '='の右側の値が割り当てられます。

i = 5

j = i --- jには5があります

j = 3 --- jは3(5の値を上書き)ですが、iに関しては何も変更されていません

print(i)-つまり、これは5を出力します


1

割り当てはオブジェクトを変更しません。変数が指す場所を変更するだけです。ある変数が指す場所を変更しても、別の変数が指す場所は変わりません。

あなたはおそらくリストと辞書が可変型であるという事実を考えているでしょう。実際のオブジェクトをインプレースで変更する演算子があり、それらの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を値として認識します。

(実際の実装では、数値はおそらく参照型ではありません。変数には、値を指すのではなく、直接値の表現が含まれている可能性が高くなります。ただし、実装の詳細によって、不変型が関係するセマンティクスは変更されません。 。)


1
それは値型が意味するものではありません。値型とは、前の段落で説明した内容(オブジェクトへの参照ではなく値が渡される/コピーされること)を正確に意味し、内部的にはそうではありません(CPythonおよびPyPy sansJITコンパイラー-すべての整数はヒープ割り当てオブジェクト)。不変に固執するだけで、まさにそこに必要な言葉です。

-1

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 ++では、メモリオブジェクトの代わりにメモリポインタが返されます(もちろんポインタ構文を使用する場合)。これにより、参照されるアドレスの変更に関してより柔軟になります。

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