私はもともとPythonは純粋な参照渡し言語だと思っていました。
C/C++から来た私は、メモリ管理について考えざるを得ず、頭の外に出すのは困難です。だから私はJavaの観点からそれを考えて、参照によるパスとしてプリミティブ以外をすべて考えようとしています。
問題:ユーザー定義クラスのインスタンスの束を含むリストがあります。
For-each構文を使用する場合、つまり:
for member in my_list:
print(member.str);
member
はオブジェクトへの実際の参照と同等ですか?
それは次のことと同等ですか:
i = 0
while i < len(my_list):
print(my_list[i])
i += 1
私はそれはないと思います、なぜなら私が置換を行うことを探しているとき、それは機能しない、つまり、これは機能しないからです:
for member in my_list:
if member == some_other_obj:
member = some_other_obj
リスト内の単純な検索と置換。それをfor-eachループで実行できますか?それ以外の場合、単にランダムアクセス構文(角かっこ)を使用する必要がありますか、それとも機能しますが、エントリを削除して新しいエントリを挿入する必要がありますか?つまり:
i = 0
for member in my_list:
if member == some_other_obj:
my_list.remove(i)
my_list.insert(i, member)
i += 1
コメントがPython変数の私自身の理解の改善につながったので、これに答えることは良かったです。
コメントに記載されているように、for member in my_list
などのリストをループすると、member
変数が連続する各リスト要素にバインドされます。ただし、ループ内でその変数を再割り当てしても、リスト自体には直接影響しません。たとえば、次のコードはリストを変更しません。
my_list = [1,2,3]
for member in my_list:
member = 42
print my_list
出力:
[1、2、3]
不変の型を含むリストを変更する場合は、次のようにする必要があります。
my_list = [1,2,3]
for ndx, member in enumerate(my_list):
my_list[ndx] += 42
print my_list
出力:
[43、44、45]
リストに可変オブジェクトが含まれている場合、現在のmember
オブジェクトを直接変更できます。
class C:
def __init__(self, n):
self.num = n
def __repr__(self):
return str(self.num)
my_list = [C(i) for i in xrange(3)]
for member in my_list:
member.num += 42
print my_list
[42、43、44]
リスト内のオブジェクトを変更するだけで、リストは変更されないことに注意してください。
Naming and Binding を読むとメリットがあります。
PythonはJavaでもC/C++でもありません。Pythonの力を実際に活用するには、そのように考えるのをやめる必要があります。
Pythonには値による受け渡しも参照による受け渡しもありませんが、代わりに名前による受け渡し(またはオブジェクトによる受け渡し)を使用します-つまり、ほぼすべてが名前にバインドされます使用します(2つの明らかな例外は、タプルとリストのインデックス付けです)。
spam = "green"
を実行すると、名前spam
が文字列オブジェクト"green"
にバインドされました。その後eggs = spam
を実行しても、何もコピーしていない場合は、参照ポインターを作成していません。別の名前eggs
を同じオブジェクト(この場合は"green"
)にバインドしただけです。 spam
を他の何か(spam = 3.14159
)にバインドすると、eggs
は引き続き"green"
にバインドされます。
Forループが実行されると、指定した名前が使用され、ループの実行中にiterableの各オブジェクトに順番にバインドされます。関数を呼び出すとき、関数ヘッダーの名前を取得し、渡された引数にバインドします。名前の再割り当ては、実際には名前の再バインドです(これを吸収するには時間がかかることがあります-とにかく私にとってはそうでした)。
リストを利用するforループでは、リストに割り当てる2つの基本的な方法があります。
for i, item in enumerate(some_list):
some_list[i] = process(item)
または
new_list = []
for item in some_list:
new_list.append(process(item))
some_list[:] = new_list
その最後の[:]
のsome_list
に注目してください-名前some_list
をnew_list
に再バインドする代わりに、some_list
の要素の変異(全体をnew_list
の要素に設定)を引き起こしています。これは重要ですか?場合によります! some_list
以外の名前が同じリストオブジェクトにバインドされていて、それらに更新を表示させたい場合は、スライシングメソッドを使用する必要があります。しない場合、または行う場合not更新を表示する場合は、some_list = new_list
に再バインドします。
アイテムとともにインデックスを取得することで、そこにあるものを置き換えることができます。
>>> foo = ['a', 'b', 'c', 'A', 'B', 'C']
>>> for index, item in enumerate(foo):
... print(index, item)
...
(0, 'a')
(1, 'b')
(2, 'c')
(3, 'A')
(4, 'B')
(5, 'C')
>>> for index, item in enumerate(foo):
... if item in ('a', 'A'):
... foo[index] = 'replaced!'
...
>>> foo
['replaced!', 'b', 'c', 'replaced!', 'B', 'C']
リストから何かを削除したい場合は、リストのコピーを反復処理する必要があります。そうしないと、反復処理しているもののサイズを変更しようとしているためエラーが発生します。これは、スライスを使用して非常に簡単に実行できます。
違う:
>>> foo = ['a', 'b', 'c', 1, 2, 3]
>>> for item in foo:
... if isinstance(item, int):
... foo.remove(item)
...
>>> foo
['a', 'b', 'c', 2]
リストのサイズを変更したので、2はまだそこにあります。正しい方法は次のとおりです。
>>> foo = ['a', 'b', 'c', 1, 2, 3]
>>> for item in foo[:]:
... if isinstance(item, int):
... foo.remove(item)
...
>>> foo
['a', 'b', 'c']