Python Tricks - Classes & OOP(1)

String Conversion (Every Class Needs a repr)

When you define a custom class in Python and then try to print one of its instances to the console (or inspect it in an interpreter session), you get a relatively unsatisfying result. The default “to string” conversion behavior is basic and lacks detail:

class Car:
  def __init__(self, color, mileage):
    self.color = color
    self.mileage = mileage

>>> my_car = Car('red', 37281)
>>> print(my_car)
<__console__.Car object at 0x109b73da0>
>>> my_car
<__console__.Car object at 0x109b73da0>

By default all you get is a string containing the class name and the id of the object instance (which is the object’s memory address in CPython.) That’s better than nothing, but it’s also not very useful.

默認情況下,我們可以得到的是一個字符串,包含著類名稱和對象示例的id。雖然聊勝于無。

You might find yourself trying to work around this by printing attributes of the class directly, or even by adding a custom to_string() method to your classes:

>>> print(my_car.color, my_car.mileage)
red 37281

你會發現你自己總是忙著通過直接打印類的屬性或者自定義添加一個字符串轉換的方法去解決這個問題。

The general idea here is the right one—but it ignores the conventions and built-in mechanisms Python uses to handle how objects are represented as strings.

這種一般觀點是正確的,但是它忽略了python用來解決對象如何像字符串一樣表達的慣例和內置機制。

Instead of building your own to-string conversion machinery, you’ll be better off adding the str and repr “dunder” methods to your class. They are the Pythonic way to control how objects are converted to strings in different situations.

我們最好添加str和repr的魔術方法搭配我們的類。它們是更加python風格的辦法去控制在不同的場景里對象如何轉換成字符串。

Let’s take a look at how these methods work in practice. To get started, we’re going to add a str method to the Car class we defined earlier:

class Car:
  def __init__(self, color, mileage):
    self.color = color
    self.mileage = mileage
  def __str__(self):
    return f'a {self.color} car'

上面我們添加了一個str的魔術方法。
When you try printing or inspecting a Car instance now, you’ll get a different, slightly improved result:

>>> my_car = Car('red', 37281)
>>> print(my_car)
'a red car'
>>> my_car
<__console__.Car object at 0x109ca24e0>

當你嘗試著打印或者查看car這個實例的時候,會得到一個不同的,有一些提升的結果。

Inspecting the car object in the console still gives us the previous result containing the object’s id. But printing the object resulted in the string returned by the str method we added.

在控制臺中查看car這個實例給了我們包含對象id的之前的結果,但是用字符串的方式打印對象結果返回的是str魔法方法的結果。

str is one of Python’s “dunder” (double-underscore) methods and gets called when you try to convert an object into a string through the various means that are available:

>>> print(my_car)
a red car
>>> str(my_car)
'a red car'
>>> '{}'.format(my_car)
'a red car'

str是一個前后雙下劃線的魔法方法,而且在你通過有效的各種各樣的方法將一個對象轉換成一個字符串的時候被調用。

With a proper str implementation, you won’t have to worry about printing object attributes directly or writing a separate to_string() function. It’s the Pythonic way to control string conversion.

一個恰當的str魔法方法的操作,你不用去擔心直接打印一個對象屬性或者寫一個分離的to_string的函數。這就是控制字符串轉換的很python的方法。

By the way, some people refer to Python’s “dunder” methods as “magic methods.” But these methods are not supposed to be magical in any way. The fact that these methods start and end in double underscores is simply a naming convention to flag them as core Python features. It also helps avoid naming collisions with your own methods and attributes. The object constructor init follows the same convention, and there’s nothing magical or arcane about it.

順便提一句,魔法方法不是魔法方法,前后雙下劃線只是去標志它們是核心python特性的一個命名的習慣。它也避免了和你自己的方法和屬性發生命名沖突。init魔法方法也是同樣的慣例,沒有什么魔法或者神秘可言。

Don’t be afraid to use Python’s dunder methods—they’re meant to
help you.
不要害怕使用它們,它們是來幫助你的。

str vs repr

Now, our string conversion story doesn’t end there. Did you see how inspecting my_car in an interpreter session still gave that odd <Car object at 0x109ca24e0> result?

現在,我們的字符串轉換的故事還沒有結束。你看到之前我們在交互模式中查看my_car類還是給了原來的結果嗎?

This happened because there are actually two dunder methods that control how objects are converted to strings in Python 3. The first one is str, and you just learned about it. The second one is repr, and the way it works is similar to str, but it is used in different situations. (Python 2.x also has a unicode method that I’ll touch on a little later.)

實際上有兩種方法控制著在python3中對象如何轉換到字符串。第一個方法是str魔法方法。第二個就是repr魔法方法,而且它工作的方式接近于str魔法方法,但是它用在不同的場景里。

Here’s a simple experiment you can use to get a feel for when str or repr is used. Let’s redefine our car class so it contains both tostring dunder methods with outputs that are easy to distinguish:

class Car:
  def __init__(self, color, mileage):
    self.color = color
    self.mileage = mileage
  def __repr__(self):
    return '__repr__ for Car'
  def __str__(self):
    return '__str__ for Car'

做了一個小實驗。現在Car類中有兩個將對象轉換成字符串的方法了。

Now, when you play through the previous examples you can see which method controls the string conversion result in each case:

>>> my_car = Car('red', 37281)
>>> print(my_car)
__str__ for Car
>>> '{}'.format(my_car)
'__str__ for Car'
>>> my_car
__repr__ for Car

試一下。

This experiment confirms that inspecting an object in a Python interpreter session simply prints the result of the object’s repr.

這個實驗證明,在python交互模式中查看一個對象只是簡單的輸出對象的repr魔法方法的結果。

Interestingly, containers like lists and dicts always use the result of repr to represent the objects they contain. Even if you call str on the
container itself:

str([my_car])
'[__repr__ for Car]'

有趣的是,像列表和字典這樣的容器總是使用repr魔法方法的結果去代表他們所包含的對象。就算你在容器本身去調用str方法。

To manually choose between both string conversion methods, for example, to express your code’s intent more clearly, it’s best to use the built-in str() and repr() functions. Using them is preferable over calling the object’s str or repr directly, as it looks nicer and gives the same result:

>>> str(my_car)
'__str__ for Car'
>>> repr(my_car)
'__repr__ for Car'

在選擇字符串轉換方法去清晰地表達我們代碼的目標的時候,我們最好使用內置的str和repr函數。用他們比直接調用對象的str和repr的魔法方法還是更好的,因為它們看上去更美觀,而且結果是一樣的。

Even with this investigation complete, you might be wondering what the “real-world” difference is between str and repr. They both seem to serve the same purpose, so it might be unclear when to use each.

With questions like that, it’s usually a good idea to look into what the Python standard library does. Time to devise another experiment. We’ll create a datetime.date object and find out how it uses repr and str to control string conversion:

>>> import datetime
>>> today = datetime.date.today()

讓我們看看python的標準庫是怎么干的。我們創建了datetime.date對象而且看它怎么使用repr和str方法去控制字符串的轉換。

The result of the date object’s str function should primarily be readable. It’s meant to return a concise textual representation for human consumption—something you’d feel comfortable displaying to a user. Therefore, we get something that looks like an ISO date format when we call str() on the date object:

>>> str(today)
'2017-02-02'

str方法調用的結果是更適合人類查看的精確的文本內容。

With repr, the idea is that its result should be, above all, unambiguous. The resulting string is intended more as a debugging aid for developers. And for that it needs to be as explicit as possible about what this object is. That’s why you’ll get a more elaborate result calling repr() on the object. It even includes the full module and class name:

>>> repr(today)
'datetime.date(2017, 2, 2)'

使用repr方法,結果是清晰的。這個返回的字符串更像是給開發者的調試幫助。這個結果需要盡量清晰地展示這個對象是什么。這就是為什么為什么你調用repr函數是會得到一個更加豐富的結果,它甚至包含全模塊和類名稱。datetime模塊,date類。

We could copy and paste the string returned by repr and execute it as valid Python to recreate the original date object. This is a neat approach and a good goal to keep in mind while writing your own reprs.
我們可以復制和粘貼repr返回的字符串并且執行去創造一個原始的date對象。這是一個簡潔的方法,也是一個好的目標,在編寫自己的報告時要牢記在心。

On the other hand, I find that it is quite difficult to put into practice. Usually it won’t be worth the trouble and it’ll just create extra work for you. My rule of thumb is to make my repr strings unambiguous and helpful for developers, but I don’t expect them to be able to restore an object’s complete state.
另外一個方面,我發現在實際使用中確實不同。我的原則是讓我的repr字符串清晰和有用,但是我們不期待它們可以存儲一個對象的完整狀態。

Why Every Class Needs a repr

If you don’t add a str method, Python falls back on the result of repr when looking for str. Therefore, I recommend that you always add at least a repr method to your classes. This will guarantee a useful string conversion result in almost all cases, with a minimum of implementation work.

如果沒有str方法,python找不到的時候就會返回repr方法。因此我建議你們至少添加有一個repr方法到自己的類。這將保證在最小的工作量下,在幾乎所有的情況下有一個有用的字符串轉換的結果。

Here’s how to add basic string conversion support to your classes quickly and efficiently. For our Car class we might start with the following repr:

def __repr__(self):
  return f'Car({self.color!r}, {self.mileage!r})'

添加一個字符串轉化。

Please note that I’m using the !r conversion flag to make sure the output string uses repr(self.color) and repr(self.mileage) instead of str(self.color) and str(self.mileage).
請注意我用!r標志來確保輸出的字符串用repr(self.color) 和repr(self.mileage)而不是str(self.color) 和str(self.mileage)。

This works nicely, but one downside is that we’ve repeated the class name inside the format string. A trick you can use here to avoid this repetition is to use the object’s class.name attribute, which will always reflect the class’ name as a string.
這個方法不錯,但是一個缺陷是我們在格式化字符串的時候重復了類的名字。在這里你可以用的一個技巧去避免這個重復就是去用對象的class.name屬性,該屬性總是反應類的名字的字符串。

The benefit is you won’t have to modify the repr implementation when the class name changes. This makes it easy to adhere to the Don’t Repeat Yourself (DRY) principle:

def __repr__(self):
  return (f'{self.__class__.__name__}('
          f'{self.color!r}, {self.mileage!r})')

這樣做的好處就是當類名稱改變的時候我們不需要修改repr魔法方法。這讓我們更加容易去執行“不要重復你自己”的原則。

The downside of this implementation is that the format string is quite long and unwieldy. But with careful formatting, you can keep the code nice and PEP 8 compliant.
這樣做的壞處就是格式化字符串太長了也太笨拙了。但是認證的格式化的話,你可以保持代碼的美觀和遵守PEP8的規則。

With the above repr implementation, we get a useful result when we inspect the object or call repr() on it directly:

>>> repr(my_car)
'Car(red, 37281)'

Printing the object or calling str() on it returns the same string because
the default str implementation simply calls repr:

>>> print(my_car)
'Car(red, 37281)'
>>> str(my_car)
'Car(red, 37281)'

在沒有str魔法時就用repr魔法來代替了。

I believe this approach provides the most value with a modest amount of implementation work. It’s also a fairly cookie-cutter approach that can be applied without much deliberation. For this reason, I always try to add a basic repr implementation to my classes.
我相信這樣的辦法有很不錯的性價比。這是一個千篇一律的方法,可以不用經過太多考慮就使用。因為這個原因,我總是添加一個基礎的repr方法到我的類中。

Here’s a complete example for Python 3, including an optional
__str__ implementation:

class Car:
  def __init__(self, color, mileage):
    self.color = color
    self.mileage = mileage
  def __repr__(self):
    return (f'{self.__class__.__name__}('
            f'{self.color!r}, {self.mileage!r})')
  def __str__(self):
    return f'a {self.color} car'

這是在python3中的一個例子。

Python 2.x Differences: unicode

In Python 3 there’s one data type to represent text across the board: str. It holds unicode characters and can represent most of the world’s writing systems.
在python 3中,有一種數據類型可以跨板表示文本:它包含Unicode字符,可以代表世界上大多數的書寫系統。

Python 2.x uses a different data model for strings. There are two types to represent text: str, which is limited to the ASCII character set, and unicode, which is equivalent to Python 3’s str.
python2用一個不同的數據模型給字符串。這有兩個不同類型去代表文本,str用的是ASCII符號集,和unicode,這個相當于python3中的str。
也就是說python2的str用的是ASCII字符集,py3用的Unicode字符集。

Due to this difference, there’s yet another dunder method in the mix for controlling string conversion in Python 2: unicode. In Python 2, str returns bytes, whereas unicode returns characters.
因為這個不同,在py2中就有另外一個魔法方法去控制字符串轉化。在py2中,str魔法返回的是比特值,而unicode魔法返回字符。

For most intents and purposes, unicode is the newer and preferred method to control string conversion. There’s also a built-in unicode() function to go along with it. It calls the respective dunder method, similar to how str() and repr() work.
為了大多數的目的和目標,unicode魔法是新的和更受青睞的控制字符串轉換的方法。這里有一個內置的unicode函數干這個活。它分別調用,像str函數和repr函數一個工作。

So far so good. Now, it gets a little more quirky when you look at the rules for when str and unicode are called in Python 2:

The print statement and str() call str. The unicode() built-in calls unicode if it exists, and otherwise falls back to str and decodes the result with the system text encoding.

Compared to Python 3, these special cases complicate the text conversion rules somewhat. But there is a way to simplify things again for practical purposes. Unicode is the preferred and future-proof way of handling text in your Python programs.
Unicode是一個更好的更有前景的處理文本的方法。

So generally, what I would recommend you do in Python 2.x is to put all of your string formatting code inside the unicode method and then create a stub str implementation that returns the unicode representation encoded as UTF-8:

def __str__(self):
  return unicode(self).encode('utf-8')

The str stub will be the same for most classes you write, so you can just copy and paste it around as needed (or put it into a base class where it makes sense). All of your string conversion code that is meant for non-developer use then lives in __unicode__.
所有用于非開發人員使用的字符串轉換代碼都以unicode格式存在.

Here’s a complete example for Python 2.x:

class Car(object):
  def __init__(self, color, mileage):
    self.color = color
    self.mileage = mileage
  def __repr__(self):
    return '{}({!r}, {!r})'.format(
              self.__class__.__name__,
              self.color, self.mileage)
  def __unicode__(self):
    return u'a {self.color} car'.format(self=self)
  def __str__(self):
    return unicode(self).encode('utf-8')
Key Takeaways
  • You can control to-string conversion in your own classes using the __str__ and __repr__ “dunder” methods.
  • The result of __str__ should be readable. The result of __repr__ should be unambiguous.
  • Always add a __repr__ to your classes. The default implementation for __str__ just calls __repr__.
  • Use __unicode__ instead of __str__ in Python 2.
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,482評論 2 379

推薦閱讀更多精彩內容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,413評論 0 10
  • pyspark.sql模塊 模塊上下文 Spark SQL和DataFrames的重要類: pyspark.sql...
    mpro閱讀 9,484評論 0 13
  • **2014真題Directions:Read the following text. Choose the be...
    又是夜半驚坐起閱讀 9,767評論 0 23
  • 2019年4月21日分享 最近忙的焦頭爛額,每天都有很多事沒有做,恨不得一天有48個小時,如何利用有限的時間提升自...
    傳媒人李艷春閱讀 492評論 0 1
  • 第一件事:有史以來第一次走步過三萬 第二件事:今日資源量達標 第三件事:明天繼續挑戰 第四件事:回家泡個熱水腳 第...
    木木夕1020閱讀 119評論 0 0