1. 程式人生 > 實用技巧 >第七章 物件應用、可變性和垃圾回收

第七章 物件應用、可變性和垃圾回收

7.1 python中的變數是什麼

python和java中的變數本質不一樣,java裡的變數看做一個盒子(有大有小)。java中宣告一個變數,首先要宣告變數型別是什麼,比如int、str或者某個類。宣告這個類之後,虛擬機器就會在記憶體中給我們申請一塊空間,空間大小跟型別有關。這個盒子能裝什麼東西,在開始時就已經指定了。

但是python變數實質是一個指標,這個指標可以指向int型別的資料,也可以指向str型別的資料。也可以理解為便利貼,大小固定,可以貼在任何地方。

a = 1
a = "abc"
#1. a貼在1上面
#2. 先生成物件 然後貼便利貼

過程是

  1. 首先去記憶體裡申請一個空間放1這個int物件
  2. 然後讓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。這裡就有一個結論,儘量不要使用可變型別作為引數。