最近、Python のオブジェクト参照メカニズムについて少し調べて、参考のためにメモを残しました。
まず、明らかなことが 1 つあります。「Python のすべてのものはオブジェクトである」ということです。
それで、これはどういう意味ですか?
次のコード:
#!/usr/bin/env python a = [0, 1, 2] # 来个简单的list # 最初,list 和其中各个元素的id 是这样的。 print 'origin' print id(a),a for x in a: print id(x), x print '----------------------' # 我们把第一个元素改改 print 'after change a[0]' a[0] = 4 print id(a),a for x in a: print id(x), x print '----------------------' # 我们再把第二个元素改改 print 'after change a[1]' a[1] = 5 print id(a),a for x in a: print id(x), x print '----------------------' # 回头看看直接写个0 ,id是多少 print 'how about const 0?' print id(0), 0
実行結果は次のとおりです:
PastgiftMacbookPro:python pastgift$ ./refTest.py
Origin
4299760200 [0, 1, 2]
4298181328 0
4298181304 1
4298181280 2
----------------------
変更後 a[0]
4299760200 [4, 1, 2]
4298181232 4
4298181304 1
4298181280 2
-------------------------------------
変更後a[1]
4299760200 [4, 5] , 2]
4298181232 4
4298181208 5
4298181280 2
-------------------------------------
const 0はどうでしょうか
4298181328 0
「Origin」部分を見ると、リスト内の各要素のアドレスは正確に 24 離れており、それぞれのデータを順番に指しています。これは配列を思い出させます。
a[0]の値を変更したところ、a[0]のアドレスが変更されていることが分かりました。言い換えれば、代入ステートメントは実際には、a[0] が再び別のオブジェクトを指すようにするだけです。さらに、a[0] のアドレスと a[2] のアドレスは 48 (24 が 2 つ) 異なることに注意してください。
a[1] を再度変更すると、a[1] のアドレスも変更されます。興味深いことに、今回は a[1] のアドレスと a[0] のアドレスが 24 異なり、元のアドレスとは異なります。 a[2] は 72 (24 が 3 つ) 異なります。
最後に、数値 0 のアドレスを直接出力すると、そのアドレスは元の a[0] のアドレスとまったく同じであることがわかります。
この時点で、リスト内の要素も実際には参照であることが基本的に説明できます。リスト内の要素を変更すると、実際には参照が変更されます。
Python のクラス属性について、「クラス属性は同じクラスとそのサブクラス間で共有され、クラス属性を変更すると同じクラスとそのサブクラスのすべてのオブジェクトに影響を与える」と誰かが言っていました。
怖く聞こえますが、よく調べてみると、実際には大したことではありません。
次のコード:
#!/usr/bin/env python class Bird(object): name = 'bird' talent = ['fly'] class Chicken(Bird): pass bird = Bird(); bird2 = Bird(); # 同类实例 chicken = Chicken(); # 子类实例 # 最开始是这样的 print 'Original attr' print id(bird.name), bird.name print id(bird.talent), bird.talent print id(bird2.name), bird2.name print id(bird2.talent), bird2.talent print id(chicken.name), chicken.name print id(chicken.talent), chicken.talent print '----------------------------' # 换个名字看看 bird.name = 'bird name changed!' print 'after changing name' print id(bird.name), bird.name print id(bird.talent), bird.talent print id(bird2.name), bird2.name print id(bird2.talent), bird2.talent print id(chicken.name), chicken.name print id(chicken.talent), chicken.talent print '----------------------------' # 洗个天赋试试(修改类属性中的元素) bird.talent[0] = 'walk' print 'after changing talent(a list)' print id(bird.name), bird.name print id(bird.talent), bird.talent print id(bird2.name), bird2.name print id(bird2.talent), bird2.talent print id(chicken.name), chicken.name print id(chicken.talent), chicken.talent print '----------------------------' # 换个新天赋树(整个类属性全换掉) bird.talent = ['swim'] print 'after reassign talent' print id(bird.name), bird.name print id(bird.talent), bird.talent print id(bird2.name), bird2.name print id(bird2.talent), bird2.talent print id(chicken.name), chicken.name print id(chicken.talent), chicken.talent print '----------------------------' # 洗掉新天赋树(对新来的类属性中的元素进行修改) bird.talent[0] = 'dance' print 'changing element after reassigning talent' print id(bird.name), bird.name print id(bird.talent), bird.talent print id(bird2.name), bird2.name print id(bird2.talent), bird2.talent print id(chicken.name), chicken.name print id(chicken.talent), chicken.talent print '----------------------------'
実行結果:
PastgiftMacbookPro:python pastgift$ ./changeAttributeTest.py
オリジナルt r
4301998000 鳥
4301857352 ['飛ぶ']
4301998000 鳥
4301857352 ['飛ぶ']
4301998000 鳥
4301857352 ['飛ぶ']
---------------------- - ---
名前変更後
4301986984 鳥の名前が変更されました!
4301857352 ['フライ']
4301998000 鳥
4301857352 ['フライ']
43019 98000 鳥
4301857352 [「飛ぶ」]
----------------------------
タレント変更後(一覧)
4301986984 鳥の名前変更しました!
4301857352 ['walk ']
4301998000 鳥
4301857352 ['散歩']
4301998000 鳥
4301857352 ['散歩']
---------------------- -- ------
タレントの再割り当て後
4301986984 鳥の名前が変更されました!
4301859512 ['泳ぐ']
4301998000 鳥
4301857352 ['散歩']
4鳥
4301857352 ['ウォーク']
----------------------------
タレントの再割り当て後の要素の変更
4301986984 鳥の名前が変更されました!
4301859512 ['ダンス']
4301998000 鳥
4301857352 ['散歩']
4301998000 鳥
4301857352 ['散歩']
------ ----- ------
「Origin」では、同じ型のオブジェクトやサブクラスのオブジェクトの同じクラス属性のアドレスが同じ、いわゆる「共有」です。
名前を変更すると、変更されたオブジェクトの name 属性のみが変更されます。これは、name への代入操作が実際には文字列を変更し、それを再引用しているためです。文字列自体は変更されていません。したがって、同じクラスとサブクラスの間には相互影響はありません。
次に、タレントの要素を変更します。このとき、状況が変わりました。同じクラスとそのサブクラスのタレント属性がすべて一緒に変更されました。これらはすべて同じメモリ アドレスと同じオブジェクトを参照しているため、これは理解しやすいです。
次に、タレントを再割り当てします。つまり、別のオブジェクトを参照するように変更します。その結果、このインスタンスのタレント属性のみが変更されます。メモリ アドレスから、このインスタンスと他のインスタンスのタレント属性が同じオブジェクトを指していないことがわかります。つまり、「この時点で、この例はすでに部外者である」ということです。
そして、最終的にタレントの要素を再度修正すると、他のインスタンスには影響がないことが容易に理解できます。もう「部外者」だから、いくらいじっても、すべて自分のことだ。
所以,「类属性在同类及其子类之间互相影响」必须有一个前提条件:实例建立后,其类属性从来没有被重新赋值过,即类属性依然指向最初所指向的内存地址。
最后提一下对象属性
如下代码:
#!/usr/bin/env python class Bird(object): def __init__(self): self.talent = ['fly'] bird = Bird() bird2 = Bird() # 刚开始的情形 print 'Origin' print id(bird.talent), bird.talent print id(bird2.talent), bird2.talent print '--------------------' # 修改其中一个对象的属性 bird.talent[0] = 'walk' print 'after changing attribute' print id(bird.talent), bird.talent print id(bird2.talent), bird2.talent print '--------------------' # 作死:两个对象的属性指向同一个内存地址,再修改 bird.talent = bird2.talent bird.talent[0] = 'swim' print 'assign to another attribute and change it' print id(bird.talent), bird.talent print id(bird2.talent), bird2.talent print '--------------------'
运行结果:
PastgiftMacbookPro:python pastgift$ ./changeAttributeTest2.py
Origin
4299867632 ['fly']
4299760200 ['fly']
--------------------
after changing attribute
4299867632 ['walk']
4299760200 ['fly']
--------------------
assign to another attribute and change it
4299760200 ['swim']
4299760200 ['swim']
--------------------
由于对象属性就算内容完全一样(刚初始化后的属性内容一般都是一样的),也会分配到完全不同的内存地址上去。所以不存在「同类对象之间影响」的情况。
但如果让一个对象的属性和另一个对象的属性指向同一个地址,两者之间(但也仅限两者之间)便又互相牵连起来。