很多時候我們自己編寫一個類,在將它的實例在終端上列印或查看的時候,我們往往會看到一個不太滿意的結果。
類默認轉化的字符串基本沒有我們想要的一些東西,僅僅包含了類的名稱以及實例的 ID (理解為 Python 對象的內存地址即可)。雖說這總比沒有好,但確實是沒什麼用處啊。
所以,我們可能會手動列印對象的一些屬性或者是在類裡自己實現一個方法來返回我們需要的信息。
這沒有什麼不對的地方,但是我們可以使用更 Pythonic 的方式來解決這個問題。
使用 __str__ 實現類到字符串的轉化
不用自己另外定義一個方法,和 JAVA 的 toString() 方法類似,你可以在類裡實現__str__ 和 __repr__ 方法從而自定義類的字符串描述,這兩種都是比較 Pythonic 的方式去控制對象轉化為字符串的方式。
下面我們通過做實驗慢慢的來看這兩種方式是怎麼工作的。首先,我們先加一個 __str__ 方法到前面的類中看看情況。
當你重新列印和查看這個類的實例的時候,你會看到一個稍微不同的結果
查看 my_car 的時候的輸出仍然和之前一樣,不過列印 my_car 的時候返回的內容和新加的 __str__ 方法的返回一致。類的 __str__ 方法會在某些需要將對象轉為字符串的時候被調用。比如下面這些情況
有了 __str__ 這個方法,你就不用手動去列印對象的一些信息或者添加額外的方法去達到目的。類到字符串的轉化使用 __str__ 這種 Pythonic 的方式實現即可。
使用 __repr__ 也有類似的效果
有的朋友可能發現,上面我們查看 my_car 對象的時候,輸出的仍是類似 <__main__.Car object at 0x10b142128> 這樣比較奇怪的結果。這是因為 Python 3 中一共有 2 中方式控制類到字符串的轉化,第一種就是我們前面提到的 __str__ 方法,另一個就是 __repr__ 方法。後者的工作方式與前者類似,但是它被調用的時機不同。
Python 2 中還有一個 __unicode__ 方法,後面我會說明,暫時跳過。
這裡有個簡單的例子,同樣是在之前的類上作改動
我們通過下面的操作來感覺下什麼時候調用 __str__ ,什麼時候調用的 __repr__ 。
從上面可以看出,當我們查看對象的時候(上圖的最後一個操作)調用的是 __repr__ 方法。
另外,列表以及字典等容器總是會使用 __repr__ 方法。即使你顯式的調用 str 方法,也是如此。
如果我們需要顯示的指定以何種方式進行類到字符串的轉化,我們可以使用內置的 str() 或 repr() 方法,它們會調用類中對應的雙下劃線方法。(當然,上面的情況除外)
當然,如果你直接調用 __str__ 或 __repr__ 方法,也能達到同樣的方法,但是不推薦這麼做。
那麼 __str__ 和 __repr__ 的差別是什麼
現在你可能在想,__str__ 和 __repr__ 的差別究竟在哪裡,它們的功能都是實現類到字符串的轉化,它們的特定並沒有體現出用途上的差異。
帶著這個這個問題,我們試著去 Python 的標準庫中找找答案。我們就來看看 datetime.date 這個類是怎麼在使用這兩個方法的。
因此,我們有個初步的答案。
__str__ 的返回結果可讀性強。也就是說,__str__ 的意義是得到便於人們閱讀的信息,就像上面的 '2018-04-03' 一樣。
__repr__ 的返回結果應更準確。怎麼說,__repr__ 存在的目的在於調試,便於開發者使用。細心的讀者會發現將 __repr__ 返回的方式直接複製到命令行上,是可以直接執行的。
上面應該就是這兩個方法的意義所在吧(便於描述,後面我稱這為通常的原則吧)。
但是於個人來說,如果按照通常的原則去編寫代碼會做很多額外的工作,兩個方法的返回結果只需要對開發者友好就可以了,並不一定需要存儲某個對象的完整狀態。後面我會根據這一點,寫部分有實踐意義的代碼實例,並不完全按照通常的原則。
為什麼每個類都最好有一個 __repr__ 方法
如果你沒有添加 __str__ 方法,Python 在需要該方法但找不到的時候,它會去調用 __repr__ 方法。因此,我推薦在寫自己的類的時候至少添加一個 __repr__ 方法,這能保證類到字符串始終有一個有效的自定義轉換方式。
我們為 Car 類添加一個 __repr__ 方法
注意,我們這裡用了 !r 標記,是為了保證 self.color 與 self.mileage 在轉化為字符串的時候使用 repr(self.color) 和 repr(self.mileage) ,而不是 str(self.color) 和 str(self.mileage) 。
這個能正常工作,不過有個缺點,就是我們把類的名稱寫死了。這有一個小技巧可以改進這種方式,就是使用對象的 __class__.__name__ 屬性,該屬性總代表著類的名稱。
這樣做的話,當類名被修改的時候,我們不需要修改 __repr__ 方法,這也符合軟體開發的 DRY 原則( Don’t Repeat Yourself )。
這種寫法也有一個不好的地方,就是格式化字符串太長了。當然,我們好好調整一個格式也能符合 PEP 8 的代碼規範。
實現了 __repr__ 方法後,當我們查看類的實例或者直接調用 repr() 方法,就能得到一個比較滿意的結果了。
列印或直接調用 str() 方法也能得到相同的結果,因為 __str__ 的默認實現就是調用 __repr__ 方法。
這樣就能以比較少的工作量,讓兩個方法都能工作,並且也有一定的可讀性,所以一般情況下,我都推薦至少添加一個 __repr__ 方法。
下面是比較全的代碼示例
Python 2 中的 __unicode__ 方法
Python 3 中字符串用 str 類型表示,代表 unicode 字符串。而 Python 2 中字符串有兩種類型,一是 str ,只能存儲 ASCII 碼,另一種是 unicode ,與 Python 3 中的 str 等同。
通常來說,用 __unicode__ 來控制類到字符串的轉化更容易被大家接受。和 __str__ 和 __repr__ 類似,你可以使用內置的 unicode() 來顯示調用 __unicode__ 方法。
列印語句和 str() 會調用 __str__ 方法,unicode() 會先找 __unicode__ 方法,找不到的話會調用 __str__ 方法,並將其結果按當時的編碼方式解碼返回。
相對於Python 3 ,Python 2 中的類到字符串的轉化,顯得稍微複雜一些。不過,下面我給了個便於實踐的思路。由於使用 unicode 處理字符串更方便,這也是趨勢,所以我們總會實現自己的 __unicode__ 方法。同時,__str__ 方法的實現則依靠於 __unicode__ ,主要邏輯是調用 __unicode__ 方法並將其結果使用 UTF-8 編碼後返回。
所以,大部分情況下,__str__ 方法都不需要做修改,對於新建的類,可以直接把這個 __str__ 方法複製進去,而把關注點只放在 __unicode__ 方法的實現上。
下面是在 Python 2 中一段比較完整的示例
小結
* 我們可以使用 __str__ 和 __repr__ 方法定義類到字符串的轉化方式,而不需要手動列印某些屬性或是添加額外的方法。
* 一般來說,__str__ 的返回結果在於強可讀性,而 __repr__ 的返回結果在於準確性。
* 我們至少需要添加一個 __repr__ 方法來保證類到字符串的自定義轉化的有效性,__str__ 是可選的。因為默認情況下,在需要卻找不到 __str__ 方法的時候,會自動調用 __repr__ 方法。
* 在 Python 2 中,我們可能更在意類的 __unicode__ 方法的實現。