Python類與物件技巧(1):字串格式化、封裝屬性名、可管理的屬性、呼叫父類方法
1. 自定義字串的格式化
_formats = { '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 = _formats[code] return fmt.format(d=self) d = Date(2018, 11, 4) print(d.year, d.month, d.day) # >>> 2018 11 4 print(format(d)) # >>> 2018-11-4 print('This day is {:mdy}'.format(d)) # >>> This day is 11/4/2018
__format__()方法給Python的字串格式化功能提供了一個鉤子。 這裡需要強調的是格式化程式碼的解析工作由類決定。
2. 在類中封裝屬性名
如果想封裝類的例項上面的“私有”資料,但是Python語言並沒有訪問控制。Python不依賴語言特性去封裝資料,而是通過遵循一定的屬性和方法命名約定來達到這個效果。
- 任何以單下劃線_開頭的名字都應該是內部實現
class A: def __init__(self): self._internal = 0 # 內部屬性 self.public = 1 # 共有屬性 def public_method(self): pass def _internal_method(self): pass
Python並不會真的阻止訪問內部名稱。但是這麼做肯定是不好的,可能會導致脆弱的程式碼(所以說對於初級程式設計師而言,python的嚴密性與可靠性都是遠不如C++的)。 同時注意到,使用下劃線開頭的約定同樣適用於模組名和模組級別函式。 例如,以單下劃線開頭(比如_socket)的模組,就是內部實現。 類似的,模組級別函式比如 sys._getframe() 在使用的時候就得加倍小心了(因為內部方法的呼叫,原則上外部不應該隨意使用)。
- 任何以雙下劃線__開頭的名字會導致訪問名稱變成其他形式
class B: def __init__(self): self.__private = 0 # 訪問名稱會發生變化 def __private_method(self): pass def public_method(self): pass self.__private_method()
使用雙下劃線開始會導致訪問名稱變成其他形式。 比如,在類B中,私有屬性會被分別重新命名為 _B__private 和 _B__private_method 。 這樣重新命名的目的就是繼承——這種屬性通過繼承是無法被覆蓋的。例如:
class C(B):
def __init__(self):
super().__init__()
self.__private = 1 # 沒有覆蓋B.__private
# 沒有覆蓋B.__private__method()
def __private_method(self):
pass
這裡,私有名稱 __private 和 __private_method 被重新命名為 _C__private 和 _C__private_method ,這個跟父類B中的名稱是完全不同的。
Note:提到單下劃線和雙下劃線來命名私有屬性,到底哪種方式好呢? 大多數而言,應該讓非公共名稱以單下劃線開頭。但是,如果程式碼涉及到子類, 並且有些內部屬性應該在子類中隱藏起來,那麼才考慮使用雙下劃線方案。
3. 建立可以管理的屬性
如果想給某個例項attribute增加除訪問與修改之外的其他處理邏輯,比如型別檢查或合法性驗證。自定義某個屬性的一種簡單方法是將它定義為一個property。property的一個關鍵特徵是它看上去跟普通的attribute沒什麼兩樣, 但是訪問它的時候會自動觸發 getter
、setter
和 deleter
方法。下面增加對一個屬性簡單的型別檢查:
class Person:
def __init__(self, first_name):
self.first_name = first_name
# Getter function : 使得first_name成為一個屬性
@property
def first_name(self):
return self._first_name
# setter function : 關聯屬性的裝飾器
@first_name.setter
def first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# deleter function (optional) : 關聯屬性的裝飾器
@first_name.deleter
def first_name(self):
raise AttributeError("Can't delete attribute")
p = Person('ziheng')
print(p.first_name) # calls the getter
# >>> ziheng
p.first_name = 'xiaohe' # calls the setter 必須是字串才會收集
print(p.first_name)
# >>> xiaohe
del p.first_name
# >>> AttributeError: Can't delete attribute
在實現一個property的時候,底層資料需要儲存在某個地方。 因此,在get和set方法中,會看到對 _first_name 屬性的操作,這也是實際資料儲存的地方。
4. 呼叫父類方法
為了呼叫父類(超類)的一個已經覆蓋的方法,使用 super() 函式。super() 函式的一個常見用法是在 __init__() 方法中確保父類被正確的初始化。
class A:
def __init__(self, x):
self.x = x
class B(A):
def __init__(self, x, y):
super().__init__(x)
self.y = y
obj = B(2017, 2018)
print(obj.x) # 2017
print(obj.y) # 2018
實際上,如何正確使用 super() 函式還需要知道更多,比如說在複雜的多繼承中直接呼叫父類和使用super()存在很大差別。
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__')
obj = C()
執行結果:
Base.__init__
A._init__
Base.__init__
B.__init__
C.__init__
Comment:Base.__init__呼叫了兩次!!!
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().__init__()
print('B.__init__')
class C(A,B):
def __init__(self):
super().__init__()
print('C.__init__')
obj = C()
執行結果:
Base.__init__
B.__init__
A._init__
C.__init__
為了弄清原理,需要解釋Python是如何實現繼承的。 對於定義的每一個類,Python會計算出一個方法解析順序(MRO)列表。 這個MRO列表就是一個簡單的所有基類的線性順序表。例如:
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)
當使用 super() 函式時,Python會在MRO列表上搜索下一個類。 只要每個重定義的方法統一使用 super() 並只調用它一次, 那麼控制流最終會遍歷完整個MRO列表,每個方法也只會被呼叫一次。
文章參考《python3-cookbook》