引言
像大多數人一樣,我在對一直傳統的面向過程語言C一知半解之后,走進了面向對象的世界,盡管對OOP一無所知,還好Python還保留有函數式編程,這使得我才不那么抵觸,直到現在,習慣了面向對象之后,也習慣了接口這些叫法,而不是函數。
在看到len(collection)與collection.len(),也越來越習慣后者,他所代表的強大的思想,(其實是調用的collection對象的內部__len__方法),這種設計思想完全體現在 Python 的數據模型上,而數據模型所描述的 API,為使用最地道的語言特性來構建你自己的
對象提供了工具。數據模型其實是對 Python 框架的描述,它規范了這門語言自身構建模塊的接口,這些模塊包括但不限於序列、迭代器、函數、類等。
一、私有和被保護的屬性
類的私有屬性:
__private_attrs:兩個下划線開頭,聲明該屬性為私有,不能在類地外部被使用或直接訪問。 在類內部的方法中使用時 self.__private_attrs。
類的方法:
在類地內部,使用def關鍵字可以為類定義一個方法,與一般函數定義不同,類方法必須包含參數self,且為第一個參數
類的私有方法 :
__private_method:兩個下划線開頭,聲明該方法為私有方法,不能在類地外部調用。 在類的內部調用 self.__private_methods
默認情況下,Python中的成員函數和成員變量都是公開的(public),在python中沒有類似public,private等關鍵詞來修飾成員函數和成員變量。
在python中定義私有變量只需要在變量名或函數名前加上 ”__“兩個下划線,那么這個函數或變量就是私有的了。
在內部,python使用一種 name mangling 技術,將 __membername替換成 _classname__membername,也就是說,類的內部定義中,
所有以雙下划線開始的名字都被"翻譯"成前面加上單下划線和類名的形式。
例如:為了保證不能在class之外訪問私有變量,Python會在類的內部自動的把我們定義的__spam私有變量的名字替換成為
_classname__spam(注意,classname前面是一個下划線,spam前是兩個下划線),因此,用戶在外部訪問__spam的時候就會
提示找不到相應的變量。 python中的私有變量和私有方法仍然是可以訪問的;訪問方法如下:
私有變量:實例._類名__變量名 私有方法:實例._類名__方法名()

1 class people(): 2 __place = "nanjing" 3 _age1 = 20 4 5 def __init__(self, name): 6 self.name = name 7 8 def __sayhello(self): 9 print("%s say hello" % self.name) 10 11 class teacher(people): 12 pass 13 14 t1 = teacher("cmz") 15 print(t1._people__place) # 訪問私有變量 16 t1._people__sayhello() # 訪問私有方法 17 18 結果是 19 nanjing 20 cmz say hello 21 22 python私有屬性和方法案例
其實,Python並沒有真正的私有化支持,但可用下划線得到偽私有。 盡量避免定義以下划線開頭的變量!
(1)_xxx "單下划線" 開始的成員變量叫做保護變量,意思是只有類實例和子類實例能訪問到這些變量, 需通過類提供的接口進行訪問;不能用'from module import *'導入 (2)__xxx 類中的私有變量/方法名 (Python的函數也是對象,所以成員方法稱為成員變量也行得通。), " 雙下划線 " 開始的是私有成員,意思是只有類對象自己能訪問,連子類對象也不能訪問到這個數據。 (3)__xxx__ 系統定義名字,前后均有一個“雙下划線” 代表python里特殊方法專用的標識,如 __init__()代表類的構造函數。

1 class people(): 2 __place = "nanjing" 3 _age = 20 4 5 def __init__(self, name): 6 self.name = name 7 8 def _test(self): 9 print("from people test") 10 11 def __sayhello(self): 12 print("%s say hello" % self.name) 13 14 class teacher(people): 15 pass 16 17 t1 = teacher("cmz") 18 print(t1._age) 19 print(people._age) 20 t1._test() 21 people._test(t1) # 傳入對象t1 22 23 結果是 24 20 25 20 26 from people test 27 from people test

1 class people(): 2 __place = "nanjing" 3 _age = 20 4 5 def __init__(self, name): 6 self.name = name 7 8 def __sayhello(self): 9 print("%s say hello" % self.name) 10 11 class teacher(people): 12 pass 13 14 t1 = teacher("cmz") 15 print(t1._people__place) 16 t1._people__sayhello() 17 18 結果是 19 nanjing 20 cmz say hello
二、搭建一摞pythonic的紙牌
python的另一強大之處就是豐富的標准庫,還有許許多多的第三方庫,這使得不用重復造輪子
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
deck = FrenchDeck()
for i in deck[:10]: # 其實這里調用的是deck這個可迭代對象背后其實用的是 iter(x),而這個函數的背后則是 x.__iter__() 方法
print(i)
#打印十張紙牌
Card(rank='2', suit='spades') Card(rank='3', suit='spades') Card(rank='4', suit='spades') Card(rank='5', suit='spades') Card(rank='6', suit='spades') Card(rank='7', suit='spades') Card(rank='8', suit='spades') Card(rank='9', suit='spades') Card(rank='10', suit='spades') Card(rank='J', suit='spades')
# 對紙牌進行排序
suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0) def spades_high(card): rank_value = FrenchDeck.ranks.index(card.rank) return rank_value * len(suit_values) + suit_values[card.suit] for card in sorted(deck, key=spades_high): print(card) Card(rank='2', suit='clubs') Card(rank='2', suit='diamonds') Card(rank='2', suit='hearts') ... (46 cards ommitted) Card(rank='A', suit='diamonds') Card(rank='A', suit='hearts') Card(rank='A', suit='spades')
三、特殊方法
下面來看看特殊方法
beer_card = Card('7', 'diamonds') >>> beer_card Card(rank='7', suit='diamonds')
len()方法與特殊方法__len__,
特殊方法的存在是為了被 Python 解釋器調用的,你自己並不需要調用它們。也就是說沒有 my_object.__len__() 這種寫法,而應該使用 len(my_object)。在執行 len(my_object) 的時候,如果my_object 是一個自定義類的對象,那么 Python 會自己去調用其中由
你實現的 __len__ 方法。abs也是同理,
如果是 Python 內置的類型,比如列表(list)、字符串(str)、字節序列(bytearray)等,那么 CPython 會抄個近路,__len__ 實際上會直接返回 PyVarObject 里的 ob_size 屬性。PyVarObject 是表示內存中長度可變的內置對象的 C 語言結構體。直接讀取這
個值比調用一個方法要快很多。
>>> deck = FrenchDeck() >>> len(deck) 52
從一疊牌中抽取特定的一張紙牌,比如說第一張或最后一張,是很容易的:deck[0] 或 deck[-1]。這都是由 __getitem__ 方法提供的
字典中也有這種用法,類似dic[k], 其背后也是__getitem__在默默支持,不過這里返回的值而是鍵k所對應的值value
>>> deck[0] Card(rank='2', suit='spades') >>> deck[-1] Card(rank='A', suit='hearts'
3.1、運算符重載
說到特殊方法來看一下運算符重載,運算符重載只是意味着在類方法中攔截內置的操作——當類的實例出現在內置操作中,Python自動調用你的方法,並且你的方法的返回值變成了相應操作的結果。
# Number類提供了一個方法來攔截實例的構造函數(__init__),此外還有一個方法捕捉減法表達式(__sub__)。這種特殊的方法是鈎子,可與內置運算綁定。
>>> class Number: def __init__(self,start): self.data = start def __sub__(self,other): return Number(self.data - other) >>> X = Number(5) >>> X.data 5 >>> Y = X - 2 >>> Y.data 3
3.2、 方法重載
overloading method:是在一個類里面,方法名字相同,而參數不同。返回類型呢?可以相同也可以不同。重載是讓類以統一的方式處理不同類型數據的一種手段。
函數重載主要是為了解決兩個問題。
1.可變參數類型。
2.可變參數個數。
對於情況 2 ,函數功能相同,但參數個數不同,python 如何處理?大家知道,答案就是缺省參數。對那些缺少的參數設定為缺省參數即可解決問題。因為你假設函數功能相同,那么那些缺少的參數終歸是需要用的。
鑒於情況 1 跟 情況 2 都有了解決方案,python 自然就不需要函數重載了。
子類不想原封不動地繼承父類的方法,而是想作一定的修改,這就需要采用方法的重寫。方法重寫(overriding method)又稱方法覆蓋。
3.3、常見的特殊方法
當然也可以使用dir內置函數來查看常見並比較的數據結構的特殊方法,如list,dict等。

dir(list) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

dir(tuple) ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']