このコードはPythonのドキュメントからのものです。私は少し混乱しています。
words = ['cat', 'window', 'defenestrate']
for w in words[:]:
if len(w) > 6:
words.insert(0, w)
print(words)
そして、私が最初に考えたことは次のとおりです。
words = ['cat', 'window', 'defenestrate']
for w in words:
if len(w) > 6:
words.insert(0, w)
print(words)
このコードはなぜ無限ループを作成し、最初のループは作成しないのですか?
これは、落とし穴の1つです!初心者から逃れることができるpythonの。
words[:]
はここの魔法のソースです。
観察する:
>>> words = ['cat', 'window', 'defenestrate']
>>> words2 = words[:]
>>> words2.insert(0, 'hello')
>>> words2
['hello', 'cat', 'window', 'defenestrate']
>>> words
['cat', 'window', 'defenestrate']
そして今、[:]
なしで:
>>> words = ['cat', 'window', 'defenestrate']
>>> words2 = words
>>> words2.insert(0, 'hello')
>>> words2
['hello', 'cat', 'window', 'defenestrate']
>>> words
['hello', 'cat', 'window', 'defenestrate']
ここで注意すべき主なことは、words[:]
が既存のリストのcopy
を返すため、変更されていないコピーを反復処理していることです。
id()
を使用して、同じリストを参照しているかどうかを確認できます。
最初の場合:
>>> words2 = words[:]
>>> id(words2)
4360026736
>>> id(words)
4360188992
>>> words2 is words
False
2番目の場合:
>>> id(words2)
4360188992
>>> id(words)
4360188992
>>> words2 is words
True
[i:j]
はスライス演算子と呼ばれ、インデックスi
から始まる(ただし含まない)リストの新しいコピーを返すことに注意してください)インデックスj
。
したがって、words[0:2]
は以下を提供します
>>> words[0:2]
['hello', 'cat']
開始インデックスを省略すると、デフォルトは0
になり、最後のインデックスを省略すると、デフォルトはlen(words)
になり、最終結果は全体リストのコピーを受け取ることになります。
コードをもう少し読みやすくしたい場合は、copy
モジュールをお勧めします。
from copy import copy
words = ['cat', 'window', 'defenestrate']
for w in copy(words):
if len(w) > 6:
words.insert(0, w)
print(words)
これは基本的に最初のコードスニペットと同じことを行い、はるかに読みやすくなります。
別の方法として(DSMのコメントで言及されているように)およびpython> = 3で、同じことを行うwords.copy()
を使用することもできます。
words[:]
は、words
のすべての要素を新しいリストにコピーします。したがって、words[:]
を反復処理すると、実際にwords
が現在持っているすべての要素を反復処理していることになります。したがって、words
を変更すると、それらの変更の効果はwords[:]
に表示されません(words
を変更する前にwords[:]
を呼び出したため)
後者の例では、words
を繰り返し処理しています。つまり、words
に加えた変更は、実際にイテレーターに表示されます。その結果、words
のインデックス0に挿入すると、words
の他のすべての要素が1つのインデックスで「バンプ」されます。したがって、forループの次の反復に移動すると、words
の次のインデックスにある要素を取得しますが、それは今見たばかりの要素です(最初に要素を挿入したため)リストの他のすべての要素をインデックスだけ上に移動します)。
これを実際に見るには、次のコードを試してください。
words = ['cat', 'window', 'defenestrate']
for w in words:
print("The list is:", words)
print("I am looking at this Word:", w)
if len(w) > 6:
print("inserting", w)
words.insert(0, w)
print("the list now looks like this:", words)
print(words)
(@Coldspeedの答えに加えて)
以下の例を見てください:
words = ['cat', 'window', 'defenestrate']
words2 = words
words2 is words
結果:True
名前Word
とwords2
は同じオブジェクトを参照することを意味します。
words = ['cat', 'window', 'defenestrate']
words2 = words[:]
words2 is words
結果:False
この場合、新しいオブジェクトを作成しました。
イテレータとイテラブルを見てみましょう。
反復可能オブジェクトは、イテレータを返す
__iter__
メソッドを持つオブジェクト、またはゼロから始まる連続インデックスを取得できる__getitem__
メソッドを定義するオブジェクトです(インデックスが無効になったときにIndexError
を発生させます) 。そのため、反復可能オブジェクトは、反復子を取得できるオブジェクトです。
イテレータは、next
(Python 2)または__next__
(Python 3)メソッドを持つオブジェクトです。
iter(iterable)
はイテレータオブジェクトを返し、list_obj[:]
は新しいリストオブジェクト、list_objectの正確なコピーを返します。
最初の場合:
for w in words[:]
for
ループは、元の単語ではなく、リストの新しいコピーを反復処理します。単語の変更はループの繰り返しには影響せず、ループは正常に終了します。
これが、ループの動作方法です。
ループはiterableでiter
メソッドを呼び出し、イテレーターを反復処理します
ループは、イテレータオブジェクトでnext
メソッドを呼び出して、イテレータから次のアイテムを取得します。このステップは、要素がなくなるまで繰り返されます
StopIteration
例外が発生すると、ループは終了します。
2番目の場合:
words = ['cat', 'window', 'defenestrate']
for w in words:
if len(w) > 6:
words.insert(0, w)
print(words)
元のリストの単語を繰り返し処理し、単語に要素を追加すると、反復子オブジェクトに直接影響します。したがって、単語が更新されるたびに、対応する反復子オブジェクトも更新されるため、無限ループが作成されます。
これを見てください:
>>> l = [2, 4, 6, 8]
>>> i = iter(l) # returns list_iterator object which has next method
>>> next(i)
2
>>> next(i)
4
>>> l.insert(2, 'A')
>>> next(i)
'A'
StopIteration
の前に元のリストを更新するたびに、更新された反復子を取得し、それに応じてnext
が返されます。それがループが無限に実行される理由です。
反復と反復プロトコルの詳細については、 here を参照してください。