Python3 CookBook|類與對象(一)

1、改變對象的字符顯示

【問題】
你想改變對象實例的打印或顯示輸出,讓它們根據可讀性

【解決方案】
要改變一個實例的字符串表示,可以重新定義它的__str__()__repr__()方法。例如:

class Pair:
	def __init__(self,x,y):
		self.x = x
		self.y = y
	
	def __repr__(self):
		return 'Pair({0.x!r},{0.y!r})'.format(self)
	
	def __str__(self):
		return ({0.x!s},{0.y!s}).format(self)
	

__repr__()方法返回一個實例的代碼表示形式,通常用來重新構造這個實例。內置repr() 函數返回這個字符串,跟我們使用交互解釋器顯示的值是一樣的。
__str__()方法將實例轉化爲一個字符串,使用 str() 或 print() 函數會輸出這個字符串。

2、自定義字符串格式化

【問題】
你想通過 format()函數和字符串方法使得一個對象能夠支持自定義的格式化。

【解決方法】
爲了自定義字符串的格式化,我們需要在類上面定義__format__()方法。例如:

_format = {
   'ymd':'{d.year}--{d.month}--{d.day}',
   'mdy':'{d.month}/{d.day}/{d.year}',
   'dmy':'{d.day}/{d.month}/{d.year}',
}

class Date:
   def __init__(self,year,month,day):
       self.year = year
       self.month = month
       self.day = day

   def __format__(self, code):
       if code == '':
           code = 'ymd'
       fmt = _format[code]
       return fmt.format(d=self)


d = Date(2012,12,21)
print(format(d))
#2012--12--21
print(format(d,'mdy'))
#12/21/2012
print('The date is {:ymd}'.format(d))
#The date is 2012--12--21

__format__()方法給python的字符串格式化功能提供一個鉤子,這裏需要着重強調的是格式化代碼的解析工作完全由類自己決定的。因此,格式化代碼可以是任何值。

2、調用父類的方法

【問題】
你想在子類中調用父類的某個已經被覆蓋的方法

【解決方案】
爲了調用父類(超類)的一個方法,可以使用super()函數,比如:

class A:
    def spam(self):
        print('A.spam')

class B(A):
    def spam(self):
        print('B.spam')
        super(B, self).spam()
b = B()
b.spam()
#B.spam
#A.spam

super()函數的一個常見的方法是在 __init__()方法中確保父類被正確的初始化

class A:
    def __init__(self):
        self.x = 0
        
class B(A):
    def __init__(self):
        super(B, self).__init__()
        self.y = 1

super()的另外一個常見的用法出現在覆蓋 Python 特殊方法的代碼中,比如:

class Proxy:
    def __init__(self, obj):
        self._obj = obj

    def __getattr__(self, name):
        return getattr(self._obj, name)

    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name, value)
        else:
            setattr(self._obj, name, value)

在上面代碼中,__setattr__()的實現包含一個名字的檢查。如果某個屬性名以下劃線(_)開頭,就同過super()調用原始的__setattr__(),否則的話就委派給內部的代理對象self._obj去處理。這看上去有點意思,因爲就算沒有顯式的指明某個類的父類,super()仍然可以有效的工作。

實際上,大家對於Python中如何正確使用 super()函數普遍知之甚少。有時會看到像下面這樣直接調用父類的一個方法:

class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        Base.__init__(self)
        print('A.__init__')

儘管對於大部分代碼而言這麼做什麼問題,但是在更復雜的涉及到多繼承的代碼中就有可能導致很奇怪的問題發生。比如,考慮如下情況:

class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        Base.__init__(self)
        print('A.__init__')

class B(Base):
    def __init__(self):
        Base.__init__(self)
        print('B.__init__')


class C(A,B):
    def __init__(self):
        A.__init__(self)
        B.__init__(self)
        print('C.__init__')

# Base.__init__
# A.__init__
# Base.__init__
# B.__init__
# C.__init__

如果你運行這段代碼就會發現 Base.__init__()被調用兩次,可能兩次調用Base.__init__()沒什麼壞處,但有時候卻不是。另一方面,假設你在代碼中換成是super(),結果就完美了

class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        super().__init__()
        print('A.__init__')

class B(Base):
    def __init__(self):
        super(B, self).__init__()
        print('B.__init__')


class C(A,B):
    def __init__(self):
        super().__init__()
        print('C.__init__')

# Base.__init__
# B.__init__
# A.__init__
# C.__init__

運行這個新版本後,你會發現每個__init__()方法只會調用一次了。

未完待續 …

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章