第七章 物件應用、可變性和垃圾回收
7.1 python中的變數是什麼
python和java中的變數本質不一樣,java裡的變數看做一個盒子(有大有小)。java中宣告一個變數,首先要宣告變數型別是什麼,比如int、str或者某個類。宣告這個類之後,虛擬機器就會在記憶體中給我們申請一塊空間,空間大小跟型別有關。這個盒子能裝什麼東西,在開始時就已經指定了。
但是python變數實質是一個指標,這個指標可以指向int型別的資料,也可以指向str型別的資料。也可以理解為便利貼,大小固定,可以貼在任何地方。
a = 1
a = "abc"
#1. a貼在1上面
#2. 先生成物件 然後貼便利貼
過程是
- 首先去記憶體裡申請一個空間放1這個int物件
- 然後讓a指向1這個物件(貼在1上面)
為進一步說明便利貼的特性,我們宣告一個數組a,然後讓b=a,對b修改看結果
a = [1,2,3]
b = a
b.append(4)
print(a)
print(a is b)
print(id(a),id(b))
[1, 2, 3, 4]
True
2527462106944 2527462106944
可以看到,修改b的同時(修改了b對應的物件),a也變化了。a,b貼在同一個物件上面。
7.2 ==和is的區別
is是判斷兩個物件的id()是否一致,用一個例子來看==是什麼
a = [1,2,3,4] b = [1,2,3,4] print(a is b) print(a == b)
False
True
2192327583744 2192327582784
a和b物件不一致(id 不同),所以is判斷就是False,但兩個物件的值是一樣的,==判斷就是True。
但是有些特殊情況,需要注意下
a = 1
b = 1
print(a is b)
print(id(a),id(b))
True
140724036245152 140724036245152
可以看到這兩個物件是一致的,在python內部有一個intern機制,會把一定範圍內的小整數建立一個全域性唯一物件(常量池),如上面a=1,當執行b=1時,會指向同一個物件。這個對小串的字串來說,也是一樣的。
a = "abc" b = "abc" print(a is b) print(id(a),id(b))
True
2815963014896 2815963014896
使用==可以看到是True
a = [1,2,3,4]
b = [1,2,3,4]
print(a==b)
print(a is b)
True
False
這個其實和魔法函式有關,list裡面有一個魔法函式__eq__,當使用==時,會呼叫list裡的__eq__魔法函式,從而判斷值是否相等。
所以判斷一個例項是否屬於一個類時,要用is進行判斷(但是用isinstance更好)
class People:
pass
person = People()
if type(person) is People: # 類也是一個物件,People也是全域性唯一的
print ("yes")
yes
7.3 del語句和垃圾回收
1.python中垃圾回收的演算法是採用引用計數。
a = 1 # 1這個物件就會有一個計數器,a=1時會在引用計數器上+1
b = a # b指向同一個物件時,計數器再+1
del a # 會將引用計數器-1,當計數器=0時,會將1物件回收,防止一直佔用記憶體
python的del和c++中不同,c++是直接回收物件,而python del直到計數器=0時,才會對物件進行回收。
a = object()
b = a # b,a指向同一個物件
del a # del a,計數器-1,但>0,未被回收
print(b) # 可以列印b
print(a) # a無法列印
<object object at 0x000001CE31B34E10>
Traceback (most recent call last):
File "E:/pyproject/AdvancePython-master/chapter06/test.py", line 5, in <module>
print(a)
NameError: name 'a' is not defined
在cpython2.0中就不是計數器。
2.自己定義一個物件時,可以在__del__中寫自己的垃圾回收邏輯
class A:
def __del__(self):
pass
7.4 一個經典的引數錯誤
第一種情況:
def add(a, b):
a += b
return a
if __name__ == "__main__":
a = 1
b = 2
c = add(a,b)
print(c)
print(a,b)
3
1 2
來看第二種情況:
def add(a, b):
a += b
return a
if __name__ == "__main__":
a = 1
b = 2
a = [1,2]
b = [3,4]
c = add(a,b)
print(c)
print(a,b)
[1, 2, 3, 4]
[1, 2, 3, 4] [3, 4]
c是[1, 2, 3, 4],a也變成[1, 2, 3, 4]了。
再看第三種情況:
def add(a, b):
a += b
return a
if __name__ == "__main__":
a = 1
b = 2
a = (1, 2)
b = (3, 4)
c = add(a, b)
print(c)
print(a, b)
(1, 2, 3, 4)
(1, 2) (3, 4)
c是(1, 2, 3, 4),a還是(1, 2)。
在第1、3種情況下,a沒有受到影響。a,b為list的時候,a受到了影響。
再add函式中,a,b是list這種可變型別時,由於+=是就地修改,所以a+=b修改了a。
舉另一個例子加深理解:
class Company:
def __init__(self, name, staffs=[]):
self.name = name
self.staffs = staffs
def add(self, staff_name):
self.staffs.append(staff_name)
def remove(self, staff_name):
self.staffs.remove(staff_name)
if __name__ == "__main__":
com1 = Company("com1", ["bobby1", "bobby2"])
com1.add("bobby3")
com1.remove("bobby1")
print(com1.staffs)
['bobby2', 'bobby3']
這裡看起來沒有什麼問題,我們再加兩個操作。
class Company:
def __init__(self, name, staffs=[]):
self.name = name
self.staffs = staffs
def add(self, staff_name):
self.staffs.append(staff_name)
def remove(self, staff_name):
self.staffs.remove(staff_name)
if __name__ == "__main__":
com1 = Company("com1", ["bobby1", "bobby2"])
com1.add("bobby3")
com1.remove("bobby1")
print(com1.staffs)
com2 = Company("com2") # 不傳list
com2.add("bobby")
com3 = Company("com3") # 不傳list
com3.add("bobby5")
print (com2.staffs)
print (com3.staffs)
print (com2.staffs is com3.staffs)
print(Company.__init__.__defaults__)
['bobby2', 'bobby3']
['bobby', 'bobby5']
['bobby', 'bobby5']
True
(['bobby', 'bobby5'],)
com2裡面出現了'bobby5',com3裡出現了'bobby',com2和com3的值是一樣的,這就有問題了。
造成這種情況的原因是:
1.__init__傳遞的一個列表是可變物件
2.com2和com3都沒有傳遞list進去,所以使用的是同一個預設的list
這個預設list可以獲取到,但是com1傳入了一個list,就不會指向預設list。這裡就有一個結論,儘量不要使用可變型別作為引數。