1. 程式人生 > >第041講:魔法方法:構造和析構

第041講:魔法方法:構造和析構

目錄

0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!

構造與析構

測試題

0. 是哪個特徵讓我們一眼就能認出這貨是魔法方法?

1. 類例項化物件所呼叫的第一個方法是什麼?

2. 什麼時候我們需要在類中明確寫出 __init__ 方法?

3. 請問下邊程式碼存在什麼問題?i

4. 請問 __new__ 方法是負責什麼任務?

5. __del__ 魔法方法什麼時候會被自動呼叫?

動動手

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魔法方法詳解