32

Python では、無限にネストされたリストを作成できます。それは明らかであり、人気がなく、間違いなく役に立たないことは既知の事実です。

>>> a = [0]
>>> a[0] = a
>>> a
[[...]]
>>> a[0] == a
True

私の質問は、ここで何が起こっているかです:

>>> a = [0]
>>> b = [0]
>>> a[0], b[0] = b, a
>>> a
[[[...]]]
>>> b
[[[...]]]
>>> a == b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in cmp
>>> a[0] == b
True
>>> a[0][0] == b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in cmp
>>> a[0][0][0] == b
True
>>> 

どちらにしても、もっと深く理解しようとすると、脳が爆発しそうな気がします。なるほど、a には b が含まれている、a には a が含まれている、というように...

今、これについての私の質問。ここにあるのは本当に 2 つのリストですか、それとも 1 つだけですか? このようなものはどのようにメモリに保存されますか? プログラマーがこのような奇妙なものを実装できるようにする目的は何でしょうか?

この質問を非常に深刻に扱わないでください。プログラミングは楽しいこともあることを忘れないでください。

4

5 に答える 5

31

免責事項:私はPythonを使用していないので、私が言ういくつかのことが間違っている可能性があります。Pythonの専門家、お気軽に訂正してください。

素晴らしい質問です。私はあなたが質問をするように促す中心的な誤解(私がそれをそれと呼ぶことさえできないならば;あなたがあなたが使用した思考プロセスに到達した方法は完全に合理的です)はこれだと思います:

私が書くとき、それはそれがあるという意味ではb[0] = aありません。それは、それが指すものを指す参照を含むことを意味します。a bba

変数abそれ自体は、それ自体が「モノ」でさえありません。また、変数自体も、メモリ内の匿名の「モノ」へのポインタにすぎません。

参照の概念は、プログラミング以外の世界からの大きな飛躍であるため、これを念頭に置いてプログラムをステップスルーしてみましょう。

>>> a = [0]

たまたま何かが含まれているリストを作成します(今は無視してください)。重要なのはリストです。そのリストはメモリに保存されます。それがメモリ位置1001に格納されているとしましょう。次に、割り当てにより、プログラミング言語で後で使用できる=変数が作成されます。aこの時点で、メモリ内にいくつかのリストオブジェクトと、その名前でアクセスできるリストオブジェクトへの参照がありますa

>>> b = [0]

これは、に対して同じことを行いbます。メモリ位置1002に格納される新しいリストがあります。プログラミング言語はb、メモリ位置を参照し、次にリストオブジェクトを参照するために使用できる参照を作成します。

>>> a[0], b[0] = b, a

これは同じことを2つ行うので、1つに焦点を当てましょうa[0] = b。これが行うことはかなり空想です。それは最初に等式の右側を評価し、変数を見て、それへの参照であるbため、メモリ内の対応するオブジェクト(メモリオブジェクト#1002)をフェッチしますb。左側で起こることは同様に空想です。aはリスト(メモリオブジェクト#1001)を指す変数ですが、メモリオブジェクト#1001自体には独自の参照がいくつかあります。a使用するandのような名前を持つ参照の代わりに、これらのb参照には。のような数値インデックスがあり0ます。だから、今、これは何をするのですかaインデックス付き参照の山であるメモリオブジェクト#1001をプルアップし、インデックス0の参照に移動し(以前は、この参照は実際の番号を指していました0。これは1行目で行ったことです)、その参照を再ポイントします(つまり、メモリオブジェクト#1001)の最初で唯一の参照であり、方程式の右側にあるものが評価されます。したがって、オブジェクト#1001の0番目の参照はオブジェクト#1002を指します。

>>> a
[[[...]]]
>>> b
[[[...]]]

これは、プログラミング言語によって行われる単なる空想です。評価を依頼するとa、メモリオブジェクト(場所#1001のリスト)が表示され、独自の魔法を使用して無限であることが検出され、そのようにレンダリングされます。

>>> a == b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in cmp

このステートメントの失敗は、Pythonが比較を行う方法と関係があります。オブジェクトをそれ自体と比較すると、すぐにtrueと評価されます。別のオブジェクトと比較して異議を唱える場合、「魔法」を使用して、等式が真であるか偽であるかを判断します。Pythonのリストの場合、各リスト内のすべてのアイテムを調べて、それらが等しいかどうかをチェックします(次に、アイテム自体の等価性チェックメソッドを使用します)。だから、あなたがしようとするとa == b。それが行うことは、最初にb(オブジェクト#1002)とa(オブジェクト#1001)を掘り起こし、次にそれらがメモリ内で異なるものであることに気付くので、再帰リストチェッカーに行きます。これは、2つのリストを反復処理することによって行われます。オブジェクト#1001には、オブジェクト#1002を指すインデックス0の要素が1つあります。オブジェクト#1002には、オブジェクト#1001を指すインデックス0の要素が1つあります。したがって、プログラムは、すべての参照が同じものを指している場合、オブジェクト#1001と#1002は等しいと結論付け、#1002(#1001の唯一の参照点)と#1001(#1002の唯一の参照点)が同じこと。この同等性チェックは決して止まることはありません。同じことが、止まらないリストでも起こります。同じエラーが発生する可能性がc = [0]; d = [0]; c[0] = d; d[0] = cあります。a == c

>>> a[0] == b
True

前の段落で示唆したように、Pythonはショートカットを使用するため、これはすぐにtrueに解決されます。a[0]オブジェクト#1002を指し、オブジェクト#1002を指すため、リストの内容を比較する必要はありませんb。Pythonは、それらが文字通りの意味で同一であることを検出し(それらは同じ「もの」です)、内容をチェックすることさえしません。

>>> a[0][0] == b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in cmp

a[0][0]オブジェクト#1001を指すようになるため、これはエラーに戻ります。IDチェックは失敗し、再帰的なコンテンツチェックにフォールバックします。再帰的なコンテンツチェックは終了しません。

>>> a[0][0][0] == b
True

もう一度、a[0][0][0]オブジェクト#1002を指しますb。再帰チェックはスキップされ、比較はすぐにtrueを返します。


特定のコードスニペットに直接関係しない高レベルのジバージャバー:

  • 他のオブジェクトを参照する参照があるので、「無限の」ネストのように見えるものがありますが、a(オブジェクト#1001と呼んでいるように)によって参照されるオブジェクトとb(#1002)によって参照されるオブジェクトは両方ともメモリ内の同じサイズ。そして、それらはすべて他のそれぞれのメモリ位置を指すリストであるため、そのサイズは実際には信じられないほど小さいです。
  • また、あまり「寛大でない」言語では、両方の参照がメモリ内の同じ場所を指しているという意味で、それらが指すメモリオブジェクトが同じである場合にのみ==、2つの参照をリターンと比較することにも注意してください。Javaはその一例です。このような言語で出現した文体の慣習は、カスタムの同等性テストを実行するために、オブジェクト自体(Javaの場合は通常呼ばれます)にメソッド/関数を定義することです。Pythonは、リストに対してこれをすぐに実行します。特にPythonについてはわかりませんが、少なくともRubyでは、実行すると、呼び出されたメソッド(上書き可能)を実際に呼び出すという意味でオーバーロードされています。理論的には、あなたが作るのを妨げるものは何もないでしょうtrue equals()==someobject == otherobject==someobjectsomeobject == otherobjectブール値以外のものを返します。
于 2011-10-06T20:32:19.693 に答える
12

私はのことが起こると思います:

a[0]==b: Python は値a[0]を調べて、 への何らかの参照を見つけるbので、 と表示されますTrue

a[0][0]==b: Python は を検索し、a[0]を見つけbて、今度は を検索します。これで、が への何らかの参照を保持していることがわかりますが、これは とまったく同じではありません。したがって、python は要素を比較する必要があります。つまり、 と比較する必要があります。今、無限再帰が始まります...a[0][0]a[0]bb[0]b[0]aba[0]b[0]

これは、割り当て時に Python が実際にリストをコピーしないためにのみ機能することに注意してくださいa[0]=b。Python はむしろ、bに格納されているへの参照を作成しa[0]ます。

于 2011-10-06T13:17:10.660 に答える
10

a[0]を参照しbb[0]参照しaます。これは循環参照です。glglgl が述べたように、==演算子を使用すると、値の比較が行われます。

これを試してみてください。これにより、物事がより明確になる可能性があります-

>>> id(a)
4299818696
>>> id(b)
4299818768
>>> id(a[0])
4299818768
>>> 
>>> id(b[0])
4299818696
于 2011-10-06T13:24:24.480 に答える
7

なるほど、a には b が含まれており、a には a が含まれています。

A はリストへの参照であり、このリストの最初のものは B への参照であり、その逆も同様です。

>>> a[0] == b
True
>>> a[0][0] == b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in cmp
>>> a[0][0][0] == b
True

ここでの [0] の数は問題ではありません。リスト検索は好きなだけ行うことができます。重要なのは、例 #1 と #3 (およびすべての奇数の検索) で、" B is B equal to B" であり、その時点で Python はメモリ アドレスを比較し、それらが同じものであることを確認するので、「はい」と答えます。例#2(およびすべてのルックアップ)では、「AはBに等しい」と言っています。Pythonは、それらが異なるメモリアドレスであることを認識し、(無限の)データ構造全体をメモリにロードして、より多くの処理を実行しようとします-奥行き比較。

于 2011-10-06T13:23:39.003 に答える
4

これらは 2 つのリストです。まず、それらを作成します。

a = [0]
b = [0]

次に、それぞれを他の要素の最初の要素に割り当てます。

a[0], b[0] = b, a

だからあなたは言うことができます

a[0] is b

b[0] is a

これは最初の例と同じ状況ですが、オベレベルがさらに深くなります。

isさらに、同一性 ( ) ではなく、同等性 ( )を比較します==。これは、それらを比較しようとすることにつながります-深く、再帰につながります。

于 2011-10-06T13:18:40.130 に答える