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

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

#搬運自FishC論壇,該系列已完結,共有00~96節,本人學習過程中的記錄等。

#FishC論壇:http://bbs.fishc.com/forum.php

#小甲魚課程規劃帖:http://bbs.fishc.com/thread-1053-1-1.html 此教程適合完全零基礎的朋友學習,

課堂筆記

-魔法方法總是被雙下劃線包圍,例如:__init__

-魔法方法是面向物件的Python的一切,如果你不知道魔法方法,說明你還沒意識到面向物件的Python的強大。(不是平時寫的Python指令碼)

-魔法方法的“魔力”體現在他們總能在適當的時候被自動呼叫

__init__(self,[, ...])

這個就是其他語言的構造的方法 。也就是類在例項化物件的時候,首先會呼叫的一個方法。

需求!舉個例子,假設我們現在要定義一個矩形類,在 定義的時候我們需要它有長和寬,長和寬這2個引數就在例項化的時候傳入2個引數;在例項化的時候它會呼叫這個__init__方法,所以我們在這裡才需要對__init__方法進行重寫,因為預設它是沒有引數的,需要傳入兩個引數,這就叫做“需求”。self.x是類例項化之後的例項物件的一個區域性變數,而右邊的x是傳入的引數x。self代表類的例項,而非類

>>> 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 >>>

這裡主要,__init__的返回值一定是返回一個 None,

>>> class A:
    def __init__(self):
        return "A for A.Cup"

    
>>> a = A()
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    a = A()
TypeError: __init__() should return None, not 'str'
>>> 

其實__init__()並不是例項化物件時第一個被呼叫的魔法方法。第一個被呼叫的應該是這個__new__(cls[, ...])。它跟其它的魔法方法不同,第一個引數不是self,而是class,也就是這個類在__init__()之前被呼叫,如果後面有引數,那後邊的引數會原封不動地傳給這個__init__()方法。(new在記憶體給物件開盤儲存空間地址,init初始化物件屬性,str直接呼叫物件返回內容)

那new方法需要一個示例物件作為返回值,它需要返回一個物件。通常是返回cls 這個類的示例物件。(new方法只接受 cls 作為它的第一個引數,__init__一個引數是self,因為呼叫__new__之前連示例都還沒有,因此那時根本沒有self的存在)

python3中類的重點與難點:__new__方法與__init__方法,來自連結:https://blog.csdn.net/qq_41020281/article/details/79638370

通俗的講解Python中的__new__()方法,來自,原文出處連結:https://blog.csdn.net/sj2050/article/details/81172022

>>> class CapStr(str):
    def __new__(cls, string):
        string = string.upper()
        return str.__new__(cls, string)

    
>>> a = CapStr("I love FishC.com!")
>>> a
'I LOVE FISHC.COM!'

#解釋:我們這個CapStr類是繼承於str這個類的,而這個str類是一個不可改變的型別,(字串是一個不可改變的型別),我們就不能用__init__()對它自身進行修改,所以我們要做new的時候把它進行一個替換(string = string.upper()),然後將替換後的呼叫這個str.__new__()
return str.__new__(cls, string)  #返回的是一個新的str類,還要new一次把它賦值給原來的類。不寫的話會呼叫它基類的那個new


#理一下:首先,先宣告一個類叫做CapStr,在裡面重寫魔法方法__new__(),然後在__new__()方法中將傳入的string變成大寫,然後將變成大寫的string交給str類中的__new__方法處理,生成一個字串

class A:
    pass

>>> class B(A):
    def __new__(cls):
        print("__new__方法被執行")
        return super().__new__(cls)
    def __init__(self):
        print("__init__方法被執行")

        
>>> b = B()
__new__方法被執行
__init__方法被執行
>>> 

__del__(self)

析構器,當物件將要被銷燬時,這個方法就會自動的被呼叫,但一定要注意的是,並非說是。這個方法是當垃圾回收機制的時候,這時候才會呼叫這個物件的del方法。並不是發生del就會觸發del方法,當這個物件生成後,所有對它的引用都被刪除後,才會啟動這個垃圾回收機制,銷燬物件時就會呼叫__del__()方法。

>>> class  C:
    def __init__(self):
        print('我是__init__方法,我被呼叫了。。。')
    def __del__(self):
        print('我是__del__方法,我被呼叫了。。。')

        
>>> c1 = C()
我是__init__方法,我被呼叫了。。。
>>> c2 = c1
>>> c3 = c2
>>> del c3
>>> del c2
>>> del c1
我是__del__方法,我被呼叫了。。。
>>> 

課後習題

測試題:

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

前後有兩個下劃線包圍的時候,就是魔法方法。

答:魔法方法總是被雙下劃線包圍,例如 __init__

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

__new__()這個方法。

答:__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. 請問下邊程式碼存在什麼問題?

class Test:
        def __init__(self, x, y):
                return x + y

__init__()這個方法是不需要返回值的,這裡 報typeerror吧

答:程式設計中需要注意到 __init__ 方法的返回值一定是None,不能是其它!

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

複製給init建立一些需要的引數、方法;或者說是修改類的方法,new是負責買材料、零部件的,init負責製造東西

答:__new__ 方法主要任務時返回一個例項物件,通常是引數 cls 這個類的例項化物件,當然你也可以返回其他物件。R

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

當回收機制啟動的時候,要銷燬一個變數時候,這個類的del方法就會自動呼叫。

答:如果說 __init__ 和 __new__ 方法是物件的構造器的話,那麼 Python 也提供了一個析構器,叫做 __del__ 方法。當物件將要被銷燬的時候,這個方法就會被呼叫。"9Dx}XV
^+2 ,{Z
但一定要注意的是,並非 del x 就相當於自動呼叫 x.__del__(),__del__ 方法是當垃圾回收機制回收這個物件的時候呼叫的。

動動手

0. 小李做事常常丟三落四的,寫程式碼也一樣,常常打開了檔案又忘記關閉。你能不能寫一個 FileObject 類,給檔案物件進行包裝,從而確認在刪除物件時檔案能自動關閉?

要用到del方法嗎,還是說new一個新的類方法。

答:只要靈活搭配 __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

# 當傳入的引數是字串的時候,返回該字串中所有字元的 ASCII 碼的和(使用 ord() 獲得一個字元的 ASCII 碼值)。

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)