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 = yearself.month = monthself.day = daydef __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的字符串格式化功能提供了一個(gè)鉤子。 這里需要強(qiáng)調(diào)的是格式化代碼的解析工作由類決定。
2. 在類中封裝屬性名
如果想封裝類的實(shí)例上面的“私有”數(shù)據(jù),但是Python語(yǔ)言并沒(méi)有訪問(wèn)控制。Python不依賴語(yǔ)言特性去封裝數(shù)據(jù),而是通過(guò)遵循一定的屬性和方法命名約定來(lái)達(dá)到這個(gè)效果。
- 任何以單下劃線_開(kāi)頭的名字都應(yīng)該是內(nèi)部實(shí)現(xiàn)
Python并不會(huì)真的阻止訪問(wèn)內(nèi)部名稱。但是這么做肯定是不好的,可能會(huì)導(dǎo)致脆弱的代碼(所以說(shuō)對(duì)于初級(jí)程序員而言,python的嚴(yán)密性與可靠性都是遠(yuǎn)不如C++的)。 同時(shí)注意到,使用下劃線開(kāi)頭的約定同樣適用于模塊名和模塊級(jí)別函數(shù)。 例如,以單下劃線開(kāi)頭(比如_socket)的模塊,就是內(nèi)部實(shí)現(xiàn)。 類似的,模塊級(jí)別函數(shù)比如 sys._getframe() 在使用的時(shí)候就得加倍小心了(因?yàn)閮?nèi)部方法的調(diào)用,原則上外部不應(yīng)該隨意使用)。
- 任何以雙下劃線__開(kāi)頭的名字會(huì)導(dǎo)致訪問(wèn)名稱變成其他形式
使用雙下劃線開(kāi)始會(huì)導(dǎo)致訪問(wèn)名稱變成其他形式。 比如,在類B中,私有屬性會(huì)被分別重命名為 _B__private 和 _B__private_method 。 這樣重命名的目的就是繼承——這種屬性通過(guò)繼承是無(wú)法被覆蓋的。例如:
class C(B):def __init__(self):super().__init__()self.__private = 1 # 沒(méi)有覆蓋B.__private# 沒(méi)有覆蓋B.__private__method()def __private_method(self):pass這里,私有名稱 __private 和 __private_method 被重命名為 _C__private 和 _C__private_method ,這個(gè)跟父類B中的名稱是完全不同的。
Note:提到單下劃線和雙下劃線來(lái)命名私有屬性,到底哪種方式好呢? 大多數(shù)而言,應(yīng)該讓非公共名稱以單下劃線開(kāi)頭。但是,如果代碼涉及到子類, 并且有些內(nèi)部屬性應(yīng)該在子類中隱藏起來(lái),那么才考慮使用雙下劃線方案。
3. 創(chuàng)建可以管理的屬性
如果想給某個(gè)實(shí)例attribute增加除訪問(wèn)與修改之外的其他處理邏輯,比如類型檢查或合法性驗(yàn)證。自定義某個(gè)屬性的一種簡(jiǎn)單方法是將它定義為一個(gè)property。property的一個(gè)關(guān)鍵特征是它看上去跟普通的attribute沒(méi)什么兩樣, 但是訪問(wèn)它的時(shí)候會(huì)自動(dòng)觸發(fā)?getter?、setter?和?deleter?方法。下面增加對(duì)一個(gè)屬性簡(jiǎn)單的類型檢查:
class Person:def __init__(self, first_name):self.first_name = first_name# Getter function : 使得first_name成為一個(gè)屬性@propertydef first_name(self):return self._first_name# setter function : 關(guān)聯(lián)屬性的裝飾器@first_name.setterdef first_name(self, value):if not isinstance(value, str):raise TypeError('Expected a string')self._first_name = value# deleter function (optional) : 關(guān)聯(lián)屬性的裝飾器@first_name.deleterdef 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 必須是字符串才會(huì)收集 print(p.first_name) # >>> xiaohe del p.first_name # >>> AttributeError: Can't delete attribute在實(shí)現(xiàn)一個(gè)property的時(shí)候,底層數(shù)據(jù)需要存儲(chǔ)在某個(gè)地方。 因此,在get和set方法中,會(huì)看到對(duì) _first_name 屬性的操作,這也是實(shí)際數(shù)據(jù)保存的地方。
4. 調(diào)用父類方法
為了調(diào)用父類(超類)的一個(gè)已經(jīng)覆蓋的方法,使用 super() 函數(shù)。super() 函數(shù)的一個(gè)常見(jiàn)用法是在 __init__() 方法中確保父類被正確的初始化。
class A:def __init__(self, x):self.x = xclass B(A):def __init__(self, x, y):super().__init__(x)self.y = yobj = B(2017, 2018) print(obj.x) # 2017 print(obj.y) # 2018實(shí)際上,如何正確使用 super() 函數(shù)還需要知道更多,比如說(shuō)在復(fù)雜的多繼承中直接調(diào)用父類和使用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()執(zhí)行結(jié)果:
Base.__init__
A._init__
Base.__init__
B.__init__
C.__init__
Comment:Base.__init__調(diào)用了兩次!!!
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()執(zhí)行結(jié)果:
Base.__init__
B.__init__
A._init__
C.__init__
為了弄清原理,需要解釋Python是如何實(shí)現(xiàn)繼承的。 對(duì)于定義的每一個(gè)類,Python會(huì)計(jì)算出一個(gè)方法解析順序(MRO)列表。 這個(gè)MRO列表就是一個(gè)簡(jiǎn)單的所有基類的線性順序表。例如:
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)
當(dāng)使用 super() 函數(shù)時(shí),Python會(huì)在MRO列表上搜索下一個(gè)類。 只要每個(gè)重定義的方法統(tǒng)一使用 super() 并只調(diào)用它一次, 那么控制流最終會(huì)遍歷完整個(gè)MRO列表,每個(gè)方法也只會(huì)被調(diào)用一次。
文章參考《python3-cookbook》
總結(jié)
以上是生活随笔為你收集整理的Python类与对象技巧(1):字符串格式化、封装属性名、可管理的属性、调用父类方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C++实现的队列queue
- 下一篇: Python类与对象技巧(2):拓展子类