1. 程式人生 > >Python3學習筆記(二):__repr__和__str__的思考和理解

Python3學習筆記(二):__repr__和__str__的思考和理解

    最近一下子學了很多的知識點,導致我有點沒反應過來,粗略的在草稿紙記了點自己的想法,趁休息的時間將它敲到部落格裡面去,免得丟失,這一篇寫的挺廢話的,有點囉嗦,本篇的重點是第二段程式後開始的總結和後面的幾個細節問題。

    關於__repr__和__str__這連個東西,我最開始就感覺有點難以理解,百度來的東西看了很多遍,定義都能背下來了,隨口一說就是一個給機器看的一個給人看的,但是感覺只理解了最表面,當然不是網上大佬給的答案不夠好,而是某些東西還是需要自己去思考,然後有一番自己的理解,然後寫點程式驗證下,這才是最好的。    

    首先,我把這兩個的東西對例項物件的操作稱為“渲染”,可能不太準確,但是我覺這麼叫方便我去理解。

    第一步先寫一個簡單的類:MyNumber,先來理解下在輸出例項的時候,str和repr的操作方式

   class MyNumber:
        def __init__(self,value):
            self.data = value
        def __str__(self):
            print('正在呼叫__str__方法,轉換為普通字串')
            s = '自定義資料%d'%self.data
            return s
        def __repr__(self):

             print('正在呼叫__str__方法,轉換為普通字串')
             s = 'MyNumber(%d)'%self.data

             return s

         n1 = MyNumber(100),

    這個類裡面除了init方法還有str和repr,首先,我們在用print輸出任何東西的時候,都會有一個渲染步驟,而且預設的就是用str進行渲染,因為任何一樣東西都可以看做一個物件,那麼它必有一個型別,如果它的類裡面沒有定義str和repr也沒關係,object裡面定義了str和repr,object是一切類的父類,所以輸出的物件一定會是渲染過的。這個類裡面自己寫了str和repr,它覆蓋了object裡面的str和repr,相當於print的重定向。

    接下來就是輸出了,print(n1)和print(str(n1))是一樣的效果的,因為他們都會呼叫類裡面的str方法,其中print(n1)是預設呼叫str的。print(repr(n1))的結果就不一樣了,它會呼叫這個類裡面的repr方法。

    接下來再弄一段來記下repr的用法和兩者的區別。

    class MyInteger:
        def __init__(self,v):
            self.data = v
        def __repr__(self):
            return 'MyInteger(%d)'%self.data
        def __abs__(self): 
        '''此方法用於制定abs(obj)函式取值時返回的結果'''
            if self.data < 0:
                return MyInteger(-self.data) #用-self.data建立一個新的物件返回回去
            return MyInteger(-self.data)
    i1 = MyInteger(-100)
    print(i1)  #等同與print(str(i1))
    n = abs(i1)

    print(n)

    這一段程式比較有意思,先來配合第一段程式來總結下str和repr的呼叫規則。

    呼叫 print(i1)  (#等同與print(str(i1)))的時候,直譯器第一個尋找的就是i1這個類的方法裡面有沒有重新定義str,如果沒有,那麼它第二步會去尋找這個類裡面有沒有重新定義repr,如果有則會用類方法的repr,如果還沒有,那麼直譯器會找這個類的上一層父類,按同樣的規則進行尋找,直到最後找到了object,然後用object的str方法,將該物件的內容轉成字串,最後輸出到終端。

    呼叫print(repr(i1))的時候就不一樣了,repr只會呼叫repr方法,當自定義的類中沒有重寫repr方法的時候,它會直接找上一級的父類中有沒有repr方法,而不會考慮呼叫str方法。

    總的來說,repr方法比較傲嬌,而str方法就比較隨意,所以repr的用法就會像這一段程式一樣,當我要輸出一個需要自己加工的資料的時候,用object的str和repr顯然不夠,那麼就需要在自己的類中重新寫一個repr的方法,這樣,呼叫print(XXX)的時候,這個類裡面的repr方法就會被呼叫,這段程式裡面,repr呼叫的意義就輸輸出了一個段字串用做提示,這一是一般比較常見的用法。

    最後再來總結一些東西,除了順序之外還有一些細節。

        1.幾乎所有的函式重構會遵循一些返回值規則,str和repr也不例外,自己重構這個函式的時候寫得返回值必須是字串型別,這個規則被寫在瞭解釋器的骨子裡,試想下,object裡定義這兩個東西就是為了輸出字串給人或機器看,結果自己重構了一遍返回了個int型的值,直譯器也會很苦惱怎麼把int的值顯示在終端上,乾脆就報錯了。

        2.所謂給人看和給機器看的意思最直觀的就是用eval函式進行測試,eval函式裡是需要一個表示式,經過測試就能明白,str返回的是個字串,而repr返回一個能代表此物件的表示式字串,這個表示式會被eval翻譯,結果就是呼叫repr時傳入的物件,eval(repr(obj))=obj。而str這麼做就會報錯。

        3.以前經常會有'hello %s'%word  一類的寫法,這裡%s就是代表了str的型別,其實repr型別對應的是%r,但是都用%s貌似也不會出錯,不過還是區分一下,顯得更專業一些。

        4.一個小細節,算是比較容易出問題的細節,以第二段程式為例,如果我把print(i1)寫成print(i1.data)會怎麼樣,結果是會直接輸出這個例項的屬性的值,而且不會呼叫這個類裡面的str和repr方法,因為print裡面放的不是一個例項物件,而是該例項的一個屬性,所以直譯器會直接呼叫object裡面的str,將值轉成字串並輸出到了終端,所以一般自己寫的類裡面重構的repr,一般都是用來自定義的去描述一個例項物件的,如果需要帶上例項屬性,那就像這一段程式一樣,在返回的時候把例項屬性插進字串裡面好了。