第041講:魔法方法:構造和析構
目錄
0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!
2. 什麼時候我們需要在類中明確寫出 __init__ 方法?
0. 小李做事常常丟三落四的,寫程式碼也一樣,常常打開了檔案又忘記關閉。你能不能寫一個 FileObject 類,給檔案物件進行包裝,從而確認在刪除物件時檔案能自動關閉?
1. 按照以下要求,定義一個類實現攝氏度到華氏度的轉換(轉換公式:華氏度 = 攝氏度*1.8+32)
2. 定義一個類繼承於 int 型別,並實現一個特殊功能:當傳入的引數是字串的時候,返回該字串中所有字元的 ASCII 碼的和(使用 ord() 獲得一個字元的 ASCII 碼值)。
0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!
我們接下來幾節課的主要內容是魔法方法,此前我們已經接觸過Python中最常用的魔法方法 __init__。
構造與析構
魔法方法總是被雙下劃線包圍,例如__init__
魔法方法是面向物件的Python的一切,如果你不知道魔法方法,說明你還沒能意識到面向物件的Python的強大
魔法方法的“魔力”體現在它們總能夠在適當的時候被自動呼叫
(1)__init__(self[ , ...])
它就相當於其它面向物件的程式語言的構造方法,也就是類在例項化物件時首先會呼叫的一個方法,在前面關於類的課程中,有人會問:“有時候在類定義時寫__init__方法,有時候卻沒有,這是為什麼呢?”
這是因為“需求”,因為我們有時候需要重寫__init__方法
我們在這裡定義一個矩形(Retangle)類:
>>> class Retangle: def __init__(self, x, y): self.x = x self.y = y def getPeri(self): return(self.x + self.y) * 2 def getArea(self): return self.x * self.y >>> rect = Retangle(3, 4) >>> rect.getPeri() 14 >>> rect.getArea() 12
需要注意的是,__init__方法的返回值一定是None,不能試圖在__init__方法中返回一個什麼東西,以在例項化的時候返回給變數。
>>> class A:
def __init__(self):
return "try return"
>>> a = A()
Traceback (most recent call last):
File "<pyshell#18>", line 1, in <module>
a = A()
TypeError: __init__() should return None, not 'str'
一般我們在需要對一個物件進行初始化操作的時候,我們才會重寫__init__方法。
(2)__new__(cls[, ...])
其實__init__方法並不是例項化物件時第一個被呼叫的魔法方法,第一個被呼叫的應該是__new__(cls[, ...])方法,它跟其他的魔法方法不同,第一個引數不是self,而是class,它在init之前被呼叫,它後邊有引數的話,會原封不動的傳給__init__方法,new方法需要一個例項物件作為返回值,需要返回一個物件,通常是返回cls這個類的例項物件,也可以返回其它類的物件,需要說明的是,new方法平時是極少去重寫的,一般用Python預設的方案即可,但是用一種情況我們需要重寫new魔法方法,就是繼承一個不可變型別的時候,又需要進行些改的時候,那麼它的特性就顯得尤為重要了。
>>> class CapStr(str):
def __new__(cls, string):
string = string.upper()
return str.__new__(cls, string)
>>> a = CapStr("I love fichc")
>>> a
'I LOVE FICHC'
這裡繼承的父類 str 是不可修改的,我們就不能在init中對其自身進行修改,所以我們就需要在new的時候進行一個替換,然用替換後的呼叫str的new方法做一個返回。如果這裡的new沒有做重寫的話,就會自動呼叫父類str的new。
(3)__del__(self)
如果我們說init和new方法是物件的構造器的話,那麼Python也提供了一個析構器,就是del方法,當物件需要被銷燬的時候,這個方法就會自動的被呼叫,但要注意的是,並非我們寫 del x ,就會呼叫 x.__del__(),del方法是當垃圾回收機制,我們知道Python有一個垃圾回收機制,當沒有任何變數去引用這個物件時,垃圾回收機制就會自動將其銷燬,這時候才會呼叫物件的del方法。
>>> class C:
def __init__(self):
print("我是__init__方法,我被呼叫了")
def __del__(self):
print("我是__del__方法,我被呼叫了")
>>> c1 = C()
我是__init__方法,我被呼叫了
>>> c2 = c1
>>> del c1
>>> del c2
我是__del__方法,我被呼叫了
例項化的時候就會呼叫__init__方法,然後有c1 和 c2 兩個變數指向這個物件,但並不是 del c1 或者 del c2 的時候就會呼叫__del__方法,而是當指向該物件的變數都被del的時候,才會被自動呼叫,來銷燬這個物件。
測試題
0. 是哪個特徵讓我們一眼就能認出這貨是魔法方法?
答:魔法方法總是被雙下劃線包圍,例如 __init__<
1. 類例項化物件所呼叫的第一個方法是什麼?
答:__new__ 是在一個物件例項化的時候所呼叫的第一個方法。它跟其他魔法方法不同,它的第一個引數不是 self 而是這個類(cls),而其他的引數會直接傳遞給 __init__ 方法的。
2. 什麼時候我們需要在類中明確寫出 __init__ 方法?
答:當我們的例項物件需要有明確的初始化步驟的時候,你可以在 __init__ 方法中部署初始化的程式碼。
舉個例子:
# 我們定義一個矩形類,需要長和寬兩個引數,擁有計算周長和麵積兩個方法。
# 我們需要物件在初始化的時候擁有“長”和“寬”兩個引數,因此我們需要重寫__init__方法
# 因為我們說過,__init__方法是類在例項化成物件的時候首先會呼叫的一個方法,大家可以理解嗎?
class Rectangle:
def __init__(self, x, y):
self.x = x
self.y = y
def getPeri(self):
return (self.x + self.y) * 2
def getArea(self):
return self.x * self.y
>>> rect = Rectangle(3, 4)
>>> rect.getPeri()
14
>>> rect.getArea()
12
3. 請問下邊程式碼存在什麼問題?i
class Test:
def __init__(self, x, y):
return x + y
答:程式設計中需要主要到 __init__ 方法的返回值一定是None,不能是其它!
4. 請問 __new__ 方法是負責什麼任務?
答:__new__ 方法主要任務時返回一個例項物件,通常是引數 cls 這個類的例項化物件,當然你也可以返回其他物件。
5. __del__ 魔法方法什麼時候會被自動呼叫?
答:如果說 __init__ 和 __new__ 方法是物件的構造器的話,那麼 Python 也提供了一個析構器,叫做 __del__ 方法。當物件將要被銷燬的時候,這個方法就會被呼叫。
但一定要注意的是,並非 del x 就相當於自動呼叫 x.__del__(),__del__ 方法是當垃圾回收機制回收這個物件的時候呼叫的。
動動手
0. 小李做事常常丟三落四的,寫程式碼也一樣,常常打開了檔案又忘記關閉。你能不能寫一個 FileObject 類,給檔案物件進行包裝,從而確認在刪除物件時檔案能自動關閉?
答:只要靈活搭配 __init__ 和 __del__ 魔法方法,即可做到收放自如。
程式碼清單:
class FileObject:
'''給檔案物件進行包裝從而確認在刪除時檔案流關閉'''
def __init__(self, filename='sample.txt'):
#讀寫模式開啟一個檔案
self.new_file = open(filename, 'r+')
def __del__(self):
self.new_file.close()
del self.new_file
1. 按照以下要求,定義一個類實現攝氏度到華氏度的轉換(轉換公式:華氏度 = 攝氏度*1.8+32)
要求:我們希望這個類儘量簡練地實現功能,如下
>>> print(C2F(32))
89.6
答:為了儘量簡練地實現功能,我們採取了“偷龍轉鳳”的小技巧。在類進行初始化之前,通過“掉包” arg 引數,讓例項物件直接返回計算後的結果。
程式碼清單:
class C2F(float):
"攝氏度轉換為華氏度"
def __new__(cls, arg=0.0):
return float.__new__(cls, arg * 1.8 + 32)
2. 定義一個類繼承於 int 型別,並實現一個特殊功能:當傳入的引數是字串的時候,返回該字串中所有字元的 ASCII 碼的和(使用 ord() 獲得一個字元的 ASCII 碼值)。
實現如下:
>>> print(Nint(123))
123
>>> print(Nint(1.5))
1
>>> print(Nint('A'))
65
>>> print(Nint('FishC'))
461
程式碼清單:
注意:ord()的引數只能是單字串。
class Nint(int):
def __new__(cls, arg=0):
if isinstance(arg, str):
total = 0
for each in arg:
total += ord(each)
arg = total
return int.__new__(cls, arg)
另有不明白的,可參閱->Python魔法方法詳解